1. 为什么是时序库:InfluxDB解决的问题和适用边界
1.1 在选型前先搞清楚时序数据与普通数据的区别
接触InfluxDB之前,我最常被问的问题其实是“我可以用MySQL存监控数据吗”。答案是可以,但会很别扭。普通数据库在设计时面向的是“当前状态”,比如用户表、订单表,一条记录就是一个实体。而时序数据面向的是“状态随时间的演变”,比如每台服务器每隔5秒采集一次的CPU使用率、物联网设备上报的温度、App埋点的请求耗时。这类数据的特征非常鲜明:写入量大、持续产生、按时间排序、几乎不做更新、删除通常是批量过期。
InfluxDB就是为了这个场景设计的。它处理的是“按时间序列组织的高吞吐写入与聚合查询”,典型的应用包括监控告警、IoT设备数据采集、性能分析、实时报表等。早期版本(也就是0.x到1.x系列)最实用的地方在于:部署非常简单,单机解压就能跑,不需要单独依赖外部存储或组件。我这篇文章里涉及的命令,都是基于1.x版本的实际操作积累,2.x开始自带Web界面和bucket概念,但底层influx命令行工具的操作思路仍然相通,很多命令还能直接使用。
1.2 核心数据模型:measurement、tag、field、time
InfluxDB的命令和SQL非常像,但如果带着MySQL的思维去用,第一周大概率在踩坑。它跟传统关系库一个最大的区别,就是数据模型由四部分组成:measurement(类似表名)、tag(索引字段)、field(实际存储数值的字段)、time(时间戳)。
tag和field的区别是需要底层理解的。tag是“有索引的、字符串为主”的元数据,适合放那些取值集合较小的维度,比如host、region、device_id。因为tag会被自动建索引,作为WHERE条件过滤时查询速度非常快。field是真正存数值的字段,比如cpu_usage、temperature、request_count,field是没有索引的,在WHERE里直接过滤field会触发布全表扫描,数据量大了会明显变慢。
time则是必须存在的字段,InfluxDB的每条记录都要带时间戳,没有带就自动使用服务器当前时间。这个模型用一句话理解:InfluxDB存储的是某个时间点、某个标签组合下、一组数值字段的取值快照。这也是为什么写数据时 CSV 导入、批量写入等场景很方便的原因。
除了模型,还有一个日常操作中绝对绕不开的概念:保留策略(Retention Policy,简称RP)。这是InfluxDB相对传统数据库的典型增强——每条数据写入时都会关联一个RP,RP决定了数据的保留时长和副本数。默认会有一个叫autogen的RP,保留时间无限大,shard group的时长是7天。后续我会重点讲这块的常用命令,因为大部分人建完库不配置RP,等磁盘爆了才回头看,往往已经晚了。
2. 库、表、写入三板斧:最常用的一批管理命令
2.1 创建、查看、切换数据库
进入influx命令行客户端的方式是直接在终端输入influx。默认连接本机的8086端口,如果服务在远程,可以带host和port参数,比如influx -host 192.168.1.10 -port 8086。进去之后第一件事,就是看有哪些数据库:
SHOW DATABASES这个命令的作用等同于MySQL的SHOW DATABASES。创建数据库:
CREATE DATABASE mydb在1.x版本里,CREATE DATABASE mydb创建完之后不会自动切换过去,还需要执行:
USE mydbUSE之后,下面的所有查询和写入都默认针对这个库。
这里想额外提醒一个细节:1.x版本里CREATE DATABASE支持带策略参数,比如:
CREATE DATABASE mydb WITH DURATION 30d REPLICATION 1 NAME myrp这条命令在创建库的同时创建了一个保留30天、副本数为1、名字叫myrp的保留策略。如果你在建库之前就已经明确了自己要保留多久,最好在建库语句里一并写掉,省得后面再改。2.x版本则改成了bucket概念,但命令行里influx仍然可用,核心逻辑类似。
查看某个库下的所有measurement:
SHOW MEASUREMENTS如果只想看匹配某些前缀的measurement,可以直接加过滤条件。比如设备监控数据中不同的设备类型建了不同前缀的表:
SHOW MEASUREMENTS WITH MEASUREMENT =~/cpu.*/这里用到了正则匹配,是InfluxDB命令里一个比较实用的小技巧。
2.2 建表与数据写入:没有建表语句这件事
按传统SQL的思路,建表应该有个CREATE TABLE。但在InfluxDB 1.x里,不需要显式建表。当你第一次往某个measurement写入数据时,这个表会被自动创建。这就是InfluxDB所谓的“无模式设计”。
写入数据用INSERT语句,最基础的格式是:
INSERT cpu,host=server01,region=cn usage=72.5,idle=27.5 1587312345000000000拆解一下这条语句的结构:cpu是measurement名称;host=server01,region=cn是tag,多个tag用逗号分隔;usage=72.5,idle=27.5是field,多个field也用逗号分隔;最后那串很长的数字是时间戳。这里的1587312345000000000是纳秒级时间戳,InfluxDB默认的精度就是纳秒。
如果不想自己算纳秒,也可以在插入时不写时间戳,让InfluxDB自动取服务器当前时间,比如:
INSERT cpu,host=server01 usage=72.5字段类型方面有个需要特别注意的点:同一个field,在不同时间点写入的值类型必须一致。比如第一次写的是浮点数72.5,后面再写整数90,InfluxDB不会自动做类型兼容,会直接报错写不进去。这一条在自动化采集脚本里特别容易踩中,尤其是用不同语言写采集端的时候,类型定义不统一就会导致部分数据丢失。
2.3 批量写入与常见报错
生产环境的数据不会一条一条insert进去,永远是批量写入效率高。在命令行里可以一次多行写入,每一行一条数据:
INSERT cpu,host=server01 usage=72.5 INSERT cpu,host=server02 usage=68.3 INSERT cpu,host=server03 usage=80.1在InfluxDB的HTTP API里,批量写入用的是行协议(Line Protocol),本质就是上面这样的文本行,多个行放在请求体里一起POST。这个格式建议命令行操作的人也必须了解,因为排查问题的时候,很多报错信息都是在解析行协议时暴露出来的。
常见的报错信息有这几类:
unable to parse:行协议写错了,可能是字段和类型不对,也可能是少了等号。field type conflict:同一个field存在两种不同的类型,这种是数据写入时类型不一致导致的。points beyond retention policy:写入的时间戳早于RP过期时间,数据被丢弃。
遇到最后一条要特别小心,它的意思是数据保留周期已经过了,这个数据点根本不会被保存下来。这种情况不是单纯报错,而是数据悄悄被吞掉了,查数据时发现缺一块,最容易被忽略。
3. 查询命令的实用组合:从基础select到高级聚合
3.1 基础查询:select、where、order by、limit
查询是用InfluxDB时最耗精力的部分,因为命令表面上像SQL,实际执行逻辑完全不同。一个最基础的查询:
SELECT * FROM cpu这条命令会把cpu表里的所有数据列出来,注意输出结构:第一列是time,然后依次是field字段,最后是tag字段。在输出结果里,tag会被单独列出来,这一点和MySQL直接输出整行记录的观感不同。
如果只想取某些字段:
SELECT usage, idle FROM cpuWHERE条件过滤时,最基本的是按时间过滤:
SELECT * FROM cpu WHERE time > now() - 1h这段命令表示取最近1小时的数据。now() - 1h是InfluxDB里非常常用的相对时间表达式,类似的还有now() - 30m、now() - 7d。如果要取绝对时间范围,可以写time >= '2020-04-19T00:00:00Z' AND time <= '2020-04-19T23:59:59Z',注意字符串格式,必须是RFC3339标准时间格式。
按tag过滤则是这样的:
SELECT * FROM cpu WHERE host = 'server01'这里有个实用技巧:如果同时对tag和field做过滤,在1.x版本中,最好先按tag过滤,再叠加field条件,因为tag走索引,能大幅减少需要扫描的数据量。例如:
SELECT * FROM cpu WHERE host = 'server01' AND usage > 90要限制返回数量就加LIMIT:
SELECT * FROM cpu LIMIT 10注意,这个LIMIT限制的是返回的记录条数,不是扫描的行数。
3.2 聚合查询:GROUP BY time窗口的妙用
时序数据库最重要的玩法就是时间窗口聚合,用GROUP BY time来按固定时间窗口统计,比如每5分钟的平均CPU使用率:
SELECT mean(usage) FROM cpu WHERE time >= now() - 1h GROUP BY time(5m)这条命令会把过去1小时的数据切分成以5分钟为粒度的窗口,每个窗口算一个平均值输出。输出的每一行代表了5分钟窗口内的聚合结果。
如果想在同一个查询里同时拿到均值、最大值、最小值、总和:
SELECT mean(usage), max(usage), min(usage), sum(usage) FROM cpu GROUP BY time(10m)时间窗口聚合时,有个容易被忽略的参数是fill。默认情况下,某个窗口内没有数据,结果里就不会有这一行,这在绘图时会导致曲线断裂。加fill(0)可以把空窗口填成0:
SELECT mean(usage) FROM cpu WHERE time >= now() - 1h GROUP BY time(5m) fill(0)如果不想填0,想用上一个窗口的值顺延填充,用fill(linear)或者fill(previous)。按我的经验,做监控图表时fill(previous)最贴近实际需求,因为没有数据说明大概率是设备断连,用上一个值补上比直接填0更符合设备状态。
3.3 按tag分组、正则匹配与子查询
GROUP BY除了按时间窗口分组,还经常按tag分组。比如想同时看多台机器的CPU使用趋势:
SELECT mean(usage) FROM cpu WHERE time >= now() - 1h GROUP BY host如果要按时间窗口+tag双重分组:
SELECT mean(usage) FROM cpu WHERE time >= now() - 1h GROUP BY time(5m), host正则表达式在查询里很好用。比如只查主机名以web开头的:
SELECT * FROM cpu WHERE host =~/^web.*/注意是=~,不是LIKE,这个符号才是正则匹配。否定匹配用!~。
子查询是解决“聚合结果再做聚合”这个场景的利器。举个例子,每台机器每5分钟的CPU平均使用率算出来后,想再算所有机器的整体平均,那就必须先按机器和时间分组,再对结果求平均:
SELECT mean(mean_usage) FROM (SELECT mean(usage) AS mean_usage FROM cpu WHERE time >= now() - 1h GROUP BY time(5m), host)这里的内层查询给均值起了个别名mean_usage,外层再对别名做聚合。子查询在内层查询嵌套时,时间范围和分组的写法都容易乱,建议先单独跑内层确认输出,再包外层。
3.4 时间相关函数和辅助操作
日常排查经常会用到几个时间函数。比如把纳秒时间戳转成可读格式,用now()配合加减:
SELECT * FROM cpu WHERE time > now() - 15m如果想看时间转换后的可读形态,可以在SELECT里用time配合格式化函数,比如time列默认显示的就是RFC3339格式。
还有几个不常用的函数实际排查时偶尔能救命,例如DERIVATIVE()可以算变化率,看CPU使用率的趋势变化是不是在持续上升。比如每5分钟采集一次的使用率差值变化:
SELECT derivative(mean(usage), 1m) FROM cpu WHERE time >= now() - 1h GROUP BY time(5m)最后提一下INTO子句,这个功能可以把查询结果写回另一个measurement,被称作“降采样”。在做历史数据归档时非常常用,后面讲到连续查询时会再派上用场:
SELECT mean(usage) AS mean_usage INTO cpu_1h FROM cpu GROUP BY time(1h)这条命令把cpu表中1小时粒度的平均使用率,写进了一个新的表cpu_1h。注意,连续查询CQ本质上就是定时自动执行这样的INTO查询。
4. 保留策略与连续查询:让数据自动“瘦身”
4.1 保留策略的核心机制和操作命令
If you仅把InfluxDB当普通数据库用,可能很久都不会碰保留策略,但对于监控系统,这属于必须配置的项。保留策略的作用是自动删除超过指定时间的数据。例如磁盘空间有限,监控数据保留30天就够了,这时候写一个30天的RP,数据在超过30天后自动被后台清理。
查看当前数据库的保留策略:
SHOW RETENTION POLICIES ON mydb输出一般包含name、duration、shardGroupDuration、replicaN、default这几列。默认那条通常是autogen,duration是0,表示永久保留。
创建一条新的保留策略:
CREATE RETENTION POLICY rp_30d ON mydb DURATION 30d REPLICATION 1 DEFAULT这个语句创建了一个叫rp_30d的保留策略,数据保留30天,副本数为1(单机部署副本数只能是1),并且设为默认策略。需要注意:如果省略了DEFAULT关键字,新策略不会成为默认策略,往这个库里写入数据时用的还是旧策略。
修改保留策略的时长:
ALTER RETENTION POLICY rp_30d ON mydb DURATION 45d有时候线上临时觉得原来配置的30天不够,想延长到45天,用ALTER直接改就行,对已有数据不会产生影响,只会影响后续的过期删除判断。
删除保留策略:
DROP RETENTION POLICY rp_30d ON mydb这里有个危险操作需要重点提醒:删除一条RP会导致使用该RP的所有measurement数据全部不可访问,而且这是一条瞬间完成、不能回滚的操作。在测试环境我试过几次,确认只要RP被删除,对应数据就好比被清空了一样,重新创建同名RP也找不回之前的数据。所以执行DROP前务必确认。
4.2 连续查询(Continuous Query)的创建与使用
连续查询是InfluxDB里一个极其实用的自动化机制,它的核心作用是“周期性地把原始数据做一次聚合计算,然后把结果写入另一张表”。典型场景是:原始数据保存30天,用CQ每5分钟聚合一次写入cpu_1m表;然后再用另一个CQ每1小时聚合cpu_1m的结果写入cpu_1h表;最后把cpu_1h表保留一年。这样既能保留长时间的历史数据,又不会占用太大的磁盘空间。
创建一条连续查询的命令格式是:
CREATE CONTINUOUS QUERY cq_1h ON mydb BEGIN SELECT mean(usage) AS mean_usage INTO cpu_1h FROM cpu GROUP BY time(1h) END这个CQ的意思是在mydb库上,每隔1小时自动执行一次查询,把cpu表最近1小时的均值写入cpu_1h表。注意GROUP BY time(1h)里的时间窗口,决定了CQ的执行周期和聚合粒度。
查看已有的连续查询:
SHOW CONTINUOUS QUERIES删除连续查询:
DROP CONTINUOUS QUERY cq_1h ON mydb使用CQ时有几个细节想分享。首先,CQ里写的查询必须包含INTO子句,而且GROUP BY time(...)的时间窗口会被用作执行周期,也就是说你写的窗口是5m,系统大约每5分钟跑一次,但不会严格准时,会有执行偏移。其次,CQ自动执行时会覆盖最近的某个时间段,即使某次执行失败了,下一轮执行也会把缺失时间段的窗口数据重新聚合,不需要手动补跑,这点在实际运维中省了不少事。
CQ对老版本还有一个坑:如果原始表数据量很大,CQ的执行时间可能超过采集周期,导致任务堆积。在1.x版本,CQ的执行是由内部调度的,没法像任务队列那样去配置并发上限。数据量大的时候可以检查SHOW CONTINUOUS QUERIES的输出,看CQ的耗时和上一次执行的偏移情况来判断是否堆积。若发现堆积,比较实用的方案是把时间窗口继续调大,比如5m改成10m,或者对原始数据先做一层更粗粒度的预聚合。
4.3 RP和CQ的组合玩法
保留策略和连续查询往往是配合使用的。最常见的组合方式:
CREATE DATABASE monitor WITH DURATION 30d REPLICATION 1 SHARD DURATION 1h NAME rp_raw CREATE CONTINUOUS QUERY cq_1h ON monitor BEGIN SELECT mean(usage) AS mean_usage INTO monitor.rp_30d.cpu_1h FROM monitor.rp_raw.cpu GROUP BY time(1h) END这里演示了跨保留策略写入数据。第一行创建monitor库,指定默认策略是rp_raw(30天)。然后CQ读的还是默认策略里的cpu表,但写入时指定写到monitor.rp_30d.cpu_1h,表示写入到monitor库里、rp_30d策略下、名为cpu_1h的表中。跨RP写入数据时,必须用“数据库名.保留策略名.表名”全限定写法,否则容易写错策略导致数据过期时间不对。
还有一个独立但常跟CQ混用的机制是capacity和shard。WHERE条件里经常用时间范围,底层其实跟shard有直接关联。shard group的时间范围由RP决定,默认情况下,如果DURATION小于2天,shard group时长为1小时;如果DURATION在2天到6个月之间,shard group时长1天;更久则7天。但也可以在创建RP时通过SHARD DURATION手动指定,如上面命令里的SHARD DURATION 1h,这样能细化分片,方便数据过期后的分片删除。不过这个参数在数据量不大时没必要动,保持默认就好。
5. 查状态、做运维:系统监控与数据清理
5.1 SHOW系列状态命令
运维环境里少了不了一系列查看状态、表结构和统计信息的命令。我挑几个高频使用的说。
查看当前InfluxDB用户列表:
SHOW USERS查看measurement对应的tag key和field key,调试数据格式时非常常用:
SHOW TAG KEYS FROM cpu SHOW FIELD KEYS FROM cpu当你忘了表结构,或者别人建的表字段命名不规范时,这两条命令能帮你快速摸清数据有哪些字段、哪些字段是tag、类型是什么。这点在接手别人的监控系统时尤其救命,我靠SHOW FIELD KEYS梳理过好几套混乱的采集逻辑。
查看一个measurement的tag取值分布情况:
SHOW TAG VALUES FROM cpu WITH KEY = host在排查“为什么查不到某台机器的数据”时,先跑这个命令确认tag值是否写对。很多时候不是数据丢了,而是host的取值和查询条件不一致,比如多了个空格、大小写不同。
查看全库的统计信息:
SHOW STATSSHOW STATS会输出一堆关于写入点数、查询耗时、HTTP请求数、内存使用等内部指标,是排查性能问题的起点。还有一个SHOW DIAGNOSTICS命令可以看到版本和运行参数。
5.2 删除数据的正确姿势
InfluxDB没有MySQL里那种DELETE FROM WHERE的通用语法。在1.x版本里,删除数据主要走以下方式:
清理过期数据靠保留策略自动处理,这个已经在前面详述。手动删除某段时间范围内的数据,用DELETE系列语法:
DELETE FROM cpu WHERE time < '2020-04-19T00:00:00Z'需要注意,DELETE只能按时间范围删,不能直接WHERE某个field值等于多少。想删除某个tag条件下面的数据,可以借助tag过滤:
DELETE FROM cpu WHERE host = 'server01' AND time < '2020-04-19T00:00:00Z'删除整个measurement的所有数据,用DROP MEASUREMENT:
DROP MEASUREMENT cpu如果只是想清空数据库,那就直接:
DROP DATABASE mydb以上三个操作都是不可逆的,执行前最好先确认备份。数据备份可以用influxd backup,恢复用influxd restore,这两个命令属于运维工具的范畴,但做任何可能误删数据的操作之前,建议务必知道能回来。
5.3 日常运维的几个常见坑
第一个坑是时区问题。InfluxDB的时间戳存储的是UTC纳秒时间,命令行查询时显示的时间也是UTC。如果你的服务器是东八区,查询结果和你本地时间相差8小时是完全正常的。想用本地时间显示,可以在influx命令行启动时加-precision rfc3339参数,或者在查询时用tz('Asia/Shanghai')子句,如:
SELECT * FROM cpu WHERE time >= now() - 1h tz('Asia/Shanghai')第二个坑是时间精度问题。通过HTTP API写数据时,默认时间精度是纳秒。如果采集端脚本生成的是毫秒或秒级时间戳,而没设置precision参数,数据会被当作纳秒时间戳解析,产生很大偏差。命令行里可以用-precision ms来指定写入精度。这个问题非常隐蔽,尤其是数据看起来能写入、查询看起来有值,但时间完全错位的时候,第一反应就要联想到精度。
第三个坑是场景问题:InfluxDB的SHOW MEASUREMENTS不区分大小写。表名是区分大小写的,但查询时不注意大小写会导致结果为空,比如建表用的Cpu,查询时写cpu则可能什么都查不到。大部分采集脚本的命名规范统一用小写,就是为了避开这个问题。
6. 用户权限管理:多环境协作的基础
最后聊聊用户和权限。单机开发时可以完全不用管这个,但一旦多人共用一个InfluxDB实例(比如几个项目共用一个开发环境),或者在生产环境接入多个采集端,用户管理就是必需的。
InfluxDB的用户分为普通用户和管理员。管理员可以做一切操作,普通用户只能操作被授权的数据库。创建用户:
CREATE USER "dev_user" WITH PASSWORD 'dev_password'创建管理员用户:
CREATE USER "admin_user" WITH PASSWORD 'admin_password' WITH ALL PRIVILEGES注意,InfluxDB的密码存储在系统内部,没有明文查看的途径,忘记密码只能靠管理员重新设置。给某个用户授权某个库:
GRANT ALL ON mydb TO "dev_user"只授予读权限:
GRANT READ ON mydb TO "dev_user"撤销权限:
REVOKE ALL ON mydb FROM "dev_user"查看用户权限:
SHOW GRANTS FOR "dev_user"修改用户密码:
SET PASSWORD FOR "dev_user" = 'new_password'删除用户:
DROP USER "dev_user"如果部署时开启了认证(默认是关闭),那么连入influx需要账号密码。开启认证的方式是在配置文件里设置auth-enabled = true,修改后重启服务,然后所有客户端命令都要带用户名密码参数,否则会直接拒绝连接。
用户权限这块有个容易忽略的点:在1.x版本中,CREATE DATABASE这类管理操作在关闭认证时任何人都能做,所以即使是内网环境,也建议开启认证,否则任何一个拿到网络访问权限的人都能drop掉整个库。这个教训我在一个共享测试环境里遇到过不止一次,一台机器上多个项目的采集数据因为某人误操作被清空,重建的成本远比配置认证高。
还有一个细节是:对于HTTP API方式连接,认证信息通过HTTP Basic Auth传递。在用Prometheus、Telegraf等采集器写入InfluxDB时,配置里填的username和password就是走这个机制。写采集器配置文件时,建议把密码放到环境变量或密钥管理里,不要直接明文放在配置文件并提交到代码仓库。
关于连续查询、保留策略、用户权限的组合管理,我的习惯是在部署新项目时先把这些一次性规划好,不建议上线后等出问题了再补。建库时把RP的时长、CQ的聚合粒度、用户授权范围都定好,这样后续日常使用基本都在查数据和分析数据层面,不会频繁去动结构。