4. InfluxDB深度解析(上):数据模型与写入查询API
各位同学,今天我们来啃一块硬骨头——InfluxDB。说实话,这个数据库在时序监控领域,地位有点像MySQL在关系型数据库里的感觉。我最早接触它是在2016年,那时候公司要搭建一套容器监控平台,选型时在Prometheus和InfluxDB之间纠结了很久。最后我们选了InfluxDB,原因很简单:它的写入性能太猛了,而且查询语法对运维人员特别友好。
这一讲,我们先聚焦两个核心:数据模型和API操作。搞懂了这两块,你基本就能上手干活了。
4.1 数据模型:Measurement、Tag、Field
InfluxDB的数据模型,说白了就是一张「带时间戳的键值表」。但它跟传统数据库最大的区别在于——它把数据分成了三部分:Measurement、Tag、Field。我刚开始学的时候,总觉得这设计有点多余,后来踩了坑才明白,这恰恰是时序数据库的精髓。
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
这里 host、region、service 就是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当关系数据库用,结果性能惨不忍睹。记住三条原则:
- Tag存元数据,Field存指标。别把指标值放Tag里,也别把元数据放Field里。
- Tag基数要低。像IP地址、容器ID这种高基数字段,尽量别当Tag。实在要用,考虑用哈希或者分桶。
- 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的核心逻辑。你看一眼就能明白整体架构:
这张图把数据模型和API的关系讲得很清楚。左边是数据模型,右边是API操作。数据通过API写入和查询,而API的效率和正确性,又依赖于数据模型的设计是否合理。
4.5 实战避坑指南
最后,我分享几个实战中遇到的坑,希望能帮你少走弯路:
- Tag值不要包含空格和逗号:行协议里空格和逗号是分隔符,如果Tag值里有这些字符,记得用反斜杠转义。我刚开始写脚本时没注意,结果数据全写歪了。
- 时间戳精度要统一:写入时用秒,查询时也用秒。混用不同精度,时间对不上。
- 不要频繁创建数据库:InfluxDB的数据库创建是轻量的,但如果你每秒创建几百个,元数据服务会扛不住。
- 写入超时处理:如果写入返回超时,不要立即重试。先查一下是不是负载太高,或者批量太大了。我曾经因为盲目重试,把集群搞挂了。
好了,这一讲的内容就到这里。数据模型和API是InfluxDB的基石,搞懂了这些,后面讲存储引擎和查询优化时,你就能理解得更深了。
公众号:蓝海资料掘金营,微信deep3321