4. InfluxDB深度解析(上):数据模型与写入查询API

各位同学,今天我们来啃一块硬骨头——InfluxDB。说实话,这个数据库在时序监控领域,地位有点像MySQL在关系型数据库里的感觉。我最早接触它是在2016年,那时候公司要搭建一套容器监控平台,选型时在Prometheus和InfluxDB之间纠结了很久。最后我们选了InfluxDB,原因很简单:它的写入性能太猛了,而且查询语法对运维人员特别友好。

这一讲,我们先聚焦两个核心:数据模型API操作。搞懂了这两块,你基本就能上手干活了。

4.1 数据模型:Measurement、Tag、Field

InfluxDB的数据模型,说白了就是一张「带时间戳的键值表」。但它跟传统数据库最大的区别在于——它把数据分成了三部分:MeasurementTagField。我刚开始学的时候,总觉得这设计有点多余,后来踩了坑才明白,这恰恰是时序数据库的精髓。

4.1.1 Measurement(测量)

你可以把Measurement理解成关系数据库里的「表名」。比如你要监控服务器的CPU使用率,那就建一个叫 cpu_usage 的Measurement。它描述的是「你在测量什么」。

关键点:Measurement不需要提前创建。你写入数据时指定名字,它自动就生成了。这一点跟MongoDB的集合很像。

4.1.2 Tag(标签)

Tag是用来做索引分组的。它存储的是元数据,比如主机名、机房、服务名等。Tag有一个重要特性:会被自动索引。这意味着你用Tag做查询条件时,速度飞快。

举个例子:

cpu_usage,host=web01,region=beijing,service=api value=78.5 1700000000

这里 hostregionservice 就是Tag。我建议你把那些经常用来过滤的字段设为Tag。比如查「北京机房所有机器的CPU使用率」,那region和host就必须是Tag。

注意:Tag的值只能是字符串。而且Tag的基数(cardinality)不能太高。我曾经见过有人把用户ID直接当Tag,结果几百万个用户ID导致索引爆炸,写入性能直接崩了。Tag基数建议控制在10万以内。

4.1.3 Field(字段)

Field存储的是实际数值。比如CPU使用率78.5、内存占用2048MB、请求延迟200ms。Field不会被索引,所以查询时如果只靠Field过滤,性能会很差。

Field支持多种类型:浮点数、整数、字符串、布尔值。但说实话,我99%的场景用的都是浮点数。

来看一个完整的数据点:

cpu_usage,host=web01,region=beijing,service=api usage_user=45.2,usage_system=33.3,usage_idle=21.5 1700000000

这个数据点里:

  • Measurement:cpu_usage
  • Tag:host、region、service
  • Field:usage_user、usage_system、usage_idle
  • Timestamp:1700000000(Unix时间戳,单位纳秒)

4.1.4 数据模型设计原则

嗯,这里我要分享一个实战经验。我见过太多人把InfluxDB当关系数据库用,结果性能惨不忍睹。记住三条原则:

  1. Tag存元数据,Field存指标。别把指标值放Tag里,也别把元数据放Field里。
  2. Tag基数要低。像IP地址、容器ID这种高基数字段,尽量别当Tag。实在要用,考虑用哈希或者分桶。
  3. Field数量别太多。一个Measurement里Field超过几十个,查询性能会下降。我习惯把不同维度的指标拆到不同的Measurement里。

小技巧:如果你不确定某个字段该放Tag还是Field,问自己一个问题:「我会不会经常用这个字段做WHERE过滤?」如果答案是「会」,那就放Tag。

4.2 写入API

InfluxDB的写入API,说白了就是HTTP POST。它支持两种格式:行协议(Line Protocol)JSON。我个人强烈推荐行协议,因为它更紧凑,写入性能也更好。

4.2.1 行协议写入

行协议的格式很简单:

<measurement>,<tag_key>=<tag_value> <field_key>=<field_value> [timestamp]

来看一个批量写入的例子:

curl -X POST 'http://localhost:8086/write?db=mydb' \
  --data-binary '
cpu_usage,host=web01,region=beijing usage_user=45.2,usage_system=33.3 1700000000
cpu_usage,host=web02,region=shanghai usage_user=52.1,usage_system=28.7 1700000000
memory_usage,host=web01,region=beijing used_percent=67.8 1700000000
'

注意几个要点:

  • 每行一个数据点,用换行符分隔
  • Tag和Field之间用空格隔开
  • 多个Tag用逗号隔开,多个Field也用逗号隔开
  • 时间戳可选,不传就用服务器当前时间

性能建议:批量写入时,建议每批5000-10000个数据点。太少浪费连接,太多容易超时。我一般控制在8000左右,效果最好。

4.2.2 写入参数

写入API支持几个重要参数:

参数 说明 默认值
db 数据库名 必填
rp 保留策略 autogen
precision 时间戳精度(n/u/ms/s/m/h) ns
consistency 写入一致性(any/one/quorum/all) one

这里我特别想提一下 precision 参数。默认是纳秒,但如果你用秒级时间戳,一定要指定 precision=s。否则InfluxDB会把它当成纳秒,那时间就完全不对了。我曾经因为这个坑,排查了整整一个下午。

4.3 查询API

InfluxDB的查询API用的是HTTP GET,查询语言是InfluxQL。它长得跟SQL很像,但有一些时序特有的语法。

4.3.1 基础查询

curl -G 'http://localhost:8086/query?db=mydb' \
  --data-urlencode 'q=SELECT * FROM cpu_usage WHERE time > now() - 1h'

返回结果是JSON格式,包含一个 results 数组。每个结果里有 series 数组,里面是具体的时序数据。

4.3.2 常用查询语法

我整理了几个最常用的查询模式:

-- 按Tag过滤
SELECT * FROM cpu_usage WHERE host='web01'

-- 聚合查询(1分钟平均值)
SELECT MEAN(usage_user) FROM cpu_usage 
WHERE time > now() - 1h 
GROUP BY time(1m)

-- 分组查询
SELECT MEAN(usage_user) FROM cpu_usage 
WHERE time > now() - 1h 
GROUP BY host

-- 降采样
SELECT MEAN(usage_user) AS avg_usage 
INTO cpu_usage_1m 
FROM cpu_usage 
GROUP BY time(1m)

经验之谈:查询时尽量加时间范围。不加时间范围的话,InfluxDB会扫描全表,数据量一大就卡死了。我习惯在查询语句里永远带上 WHERE time > now() - 7d 这样的条件。

4.3.3 查询性能优化

嗯,这里我要说几个实战中遇到的坑:

  • 避免SELECT *:只查你需要的Field。SELECT * 会拉取所有Field,包括那些你根本用不上的。
  • 合理使用GROUP BY time:聚合窗口别太小。比如你存的是秒级数据,但查一周的趋势,用1秒聚合就没意义了,建议用5分钟或1小时。
  • 注意时间精度:查询时的时间范围,最好跟写入时的时间精度一致。否则可能出现边界问题。

4.4 知识体系图

下面这张图,是我自己总结的InfluxDB数据模型和API的核心逻辑。你看一眼就能明白整体架构:

InfluxDB 数据模型与API核心架构 数据模型 Measurement(测量) 相当于关系数据库的表名 Tag(标签) 元数据,自动索引 用于过滤和分组 Field(字段) 实际指标数值 不会被索引 Timestamp(时间戳) API操作 写入API(HTTP POST) 行协议:紧凑高效 JSON:兼容性好 批量写入:5000-10000点/批 参数:db/rp/precision 查询API(HTTP GET) InfluxQL语法 支持聚合、分组、降采样 返回JSON格式 优化:加时间范围 避免SELECT * 数据通过 API写入/查询

这张图把数据模型和API的关系讲得很清楚。左边是数据模型,右边是API操作。数据通过API写入和查询,而API的效率和正确性,又依赖于数据模型的设计是否合理。

4.5 实战避坑指南

最后,我分享几个实战中遇到的坑,希望能帮你少走弯路:

  • Tag值不要包含空格和逗号:行协议里空格和逗号是分隔符,如果Tag值里有这些字符,记得用反斜杠转义。我刚开始写脚本时没注意,结果数据全写歪了。
  • 时间戳精度要统一:写入时用秒,查询时也用秒。混用不同精度,时间对不上。
  • 不要频繁创建数据库:InfluxDB的数据库创建是轻量的,但如果你每秒创建几百个,元数据服务会扛不住。
  • 写入超时处理:如果写入返回超时,不要立即重试。先查一下是不是负载太高,或者批量太大了。我曾经因为盲目重试,把集群搞挂了。

好了,这一讲的内容就到这里。数据模型和API是InfluxDB的基石,搞懂了这些,后面讲存储引擎和查询优化时,你就能理解得更深了。


公众号:蓝海资料掘金营,微信deep3321