InfluxDB 入门(下):读懂 Line Protocol,并写入第一批时序数据

Kaku Lv4

上一篇里,我们把一条时序数据拆成了四部分:

1
Point = Measurement + Tag Set + Field Set + Timestamp

写入 InfluxDB 时,这四部分通常会挤进一行 Line Protocol

1
weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i,status="normal" 1724659200000000000

它没有 JSON 的花括号,也没有 SQL 的 INSERT INTO,全靠逗号、空格和等号划分结构。把上一篇的模型放回来,这一行并不难读:

1
2
3
4
weather                  Measurement
city=Beijing,sensor=s01 Tag Set
temperature=26.5,... Field Set
1724659200000000000 Timestamp

这篇把容易写错的地方拆开讲清,并用 Docker 启动 InfluxDB 3 Core,走完创建 Database、写入和 SQL 查询这一小圈。

先把版本说清楚

本文的环境固定为:

1
2
3
4
InfluxDB 3 Core 3.11.2
Docker
SQL
InfluxDB v3 HTTP API

示例固定使用镜像:

1
influxdb:3.11.2-core

固定版本是为了给命令划清边界。InfluxDB 1.x、2.x 和 3.x 的存储容器、认证、CLI 与查询语言都有差异;正在使用 2.x 的话,不要直接复制这里的 3.x 命令。

Line Protocol 只描述待写入的 Point。创建 Database 和 Token 交给 influxdb3 CLI,数据通过 CLI 或 HTTP API 发送,查询使用 SQL。它和 SQL 各管一段链路。

一行协议是怎样分段的

Line Protocol 的基本结构如下:

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

这串语法实际只有三个分区:

1
Measurement 和 Tag Set | Field Set | Timestamp

它们之间使用未转义的空格分隔:

1
2
3
weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i 1724659200000000000
└─────────────────────────────┘ └─────────────────────────────┘ └─────────────────┘
Measurement + Tag Set Field Set Timestamp

第一个空格结束 Measurement 和 Tag Set,第二个空格结束 Field Set。Timestamp 可以省略,因此第二个空格不一定存在;第一个不能少,Field 也至少要有一个:

1
weather temperature=26.5

这行没有 Tag,只有一个 Float Field;Timestamp 由服务端按接收数据时的当前 UTC 时间补上。

空格不是为了排版

这种写法在人看来更松快,却会破坏分区:

1
weather, city=Beijing temperature = 26.5

解析器会把第一个未转义空格当成分区边界,逗号或等号旁不能随意留空。应写成:

1
weather,city=Beijing temperature=26.5

格式虽然简单,分隔符本身就是语法。

空格前:Measurement 和 Tag Set

一行最前面的 weather 是 Measurement,在 InfluxDB 3 中对应一张 Table:

1
2
weather,city=Beijing temperature=26.5
^^^^^^^
1
SELECT * FROM weather;

Measurement 后有 Tag 时,第一个 Tag 前加逗号;没有 Tag,就直接用空格进入 Field Set:

1
2
3
4
weather,city=Beijing temperature=26.5
^
weather temperature=26.5
^

citysensor 是两个 Tag:

1
2
3
weather,city=Beijing,sensor=s01 temperature=26.5
└──────┬─────┘ └────┬────┘
Tag 1 Tag 2

每个 Tag 都是 tag_key=tag_value,多个 Tag 之间只用逗号:

1
city=Beijing,sensor=s01

Tag Value 一律按字符串处理,不要照着 JSON 加双引号。引号可能成为值本身的一部分:

1
2
weather,city="Beijing" temperature=26.5
weather,city=Beijing temperature=26.5

在 InfluxDB 3 中,Tag 和时间列共同构成表的主键,Tag 的先后还会影响物理排序。第一次写入新表前应定好 Schema。若查询通常先看 city、再看 sensor,就统一写成:

1
weather,city=Beijing,sensor=s01 ...

别让一部分客户端写 city,sensor,另一部分写 sensor,city。Series 的逻辑身份不依赖这段文本的顺序,但首次写入顺序会进入 InfluxDB 3 的表结构和物理排序。

空格后:Field 类型写在值里

第一个空格后面是 Field Set:

1
2
3
weather,city=Beijing temperature=26.5,humidity=58i,status="normal",online=true
└───────────────────────────────────────────────────┘
Field Set

多个 Field 也用逗号分隔。它们的类型没有单独声明,值怎么写,决定了这一列是什么类型:

1
2
3
4
5
6
temperature=26.5   # Float64
load=1 # 仍是 Float64
humidity=58i # Int64
bytes_sent=4096u # UInt64
status="normal" # String
online=true # Boolean

最容易漏掉的是整数后缀。5858i 看起来只差一个字母,落到 Schema 里却是两种类型:

1
2
value=58    # Float64
value=58i # Int64

有符号整数用 i,无符号整数用 u;无符号整数不能表示负数。String Field 必须用双引号,Boolean 则不加:

1
2
3
4
status="normal"
message="sensor online"
online=true
healthy=false

online="true" 保存的是字符串。类似地,status=normal 可以出现在 Tag Set,却不是合法的 String Field Value。位置和写法都参与数据建模。

同一列的类型要保持一致。第一次写入:

1
weather temperature=26.5

InfluxDB 3 会把 temperature 建成浮点数列。后续若改成:

1
weather temperature="26.5"

它就会与已有 Schema 冲突,写入可能被拒绝。从 JSON、CSV 或消息队列转换数据时,采集端需要先把类型统一好。

Timestamp:别让单位靠猜

一行最后的整数是 Timestamp:

1
2
weather,city=Beijing temperature=26.5 1724659200000000000
^^^^^^^^^^^^^^^^^^^

它是 Unix 时间戳,但数字本身没有单位。秒、毫秒、微秒还是纳秒,要由写入接口的 precision 参数决定。

常见精度是:

精度CLI 参数v3 HTTP API 参数
ssecond
毫秒msmillisecond
微秒usmicrosecond
纳秒nsnanosecond

InfluxDB 3 Core 的 v3 写入接口还支持 auto,按时间戳数量级推测精度。手工试验可以用,采集程序最好明确指定。例如 Shell 的:

1
date +%s

产生秒级时间戳,CLI 应配 --precision s/api/v3/write_lp 则配 precision=second。两套拼写不能混用。

如果 Timestamp 省略,InfluxDB 使用服务端接收数据时的当前 UTC 时间。测试时可以这样做,采集程序则最好明确传入事件真正发生的时间。

每分钟采一次温度,秒或毫秒已经足够。官方建议使用能满足需求的最粗精度,这也有利于压缩。按纳秒解释时,64 位时间戳可表达的日期大致在 1677 年到 2262 年之间;日常更常见的故障仍是单位配错,数据因此落到意外的日期。

转义:反斜杠用在哪

名称和值只含字母、数字、短横线和下划线时,通常无需转义。遇到分隔字符,Line Protocol 用反斜杠保住它们。

Measurement 中需要转义逗号和空格:

1
2
environment\ sensors temperature=26.5
weather\,indoor temperature=26.5

它们分别表示 environment sensorsweather,indoor

Tag Key、Tag Value 和 Field Key 多一个等号要处理,因此逗号、等号、空格都要转义:

1
2
3
weather,room=Living\ Room temperature=26.5
weather,device=id\=01 temperature=26.5
weather room\ temperature=26.5

第三行的 room temperature 是一个 Field Key。String Field Value 则转义双引号和反斜杠:

1
2
event note="sensor said \"hot\""
event path="C:\\data\\sensor"

换行用于分隔 Point,不能直接塞进字符串。也别沿用 SQL 的习惯给名称套引号,下面这些引号可能成为名称或值的一部分:

1
"weather","city"="Beijing" "temperature"=26.5

简单、稳定的命名能省掉大部分转义;只有名称确实带分隔字符时才使用反斜杠。

多行就是一批数据

一行表示一个 Point,多行就是多个 Point:

1
2
3
weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i 1724659200
weather,city=Shanghai,sensor=s02 temperature=24.8,humidity=72i 1724659200
weather,city=Beijing,sensor=s01 temperature=26.8,humidity=57i 1724659500

换行既是 Point 的边界,也是批量写入的基础。相比每条数据都发一个 HTTP 请求,合成一批通常更高效。InfluxDB 3 Core 的写入优化文档建议从约 10000 行或 10 MB 的批次开始考虑,以先达到者为准;这不是最低门槛,只是提醒高频采集程序不要每来一个 Point 就新建一次请求。

重复 Point 不是可靠的更新操作

一个 Point 的逻辑身份由三部分决定:

1
Table + 完整 Tag Set + Timestamp

这两次写入的身份完全相同:

1
2
weather,city=Beijing temperature=26.5 1724659200
weather,city=Beijing temperature=27.1 1724659200

重复写入中不重名的 Field 会按并集合并;上面两行都写了 temperature,才形成同名 Field 冲突。InfluxDB 2.x 的 TSM 引擎有确定的覆盖规则;InfluxDB 3 Core 官方文档明确提醒,冲突值的覆盖结果并不确定,查询和最终持久化都不保证留下“最后写入”的值。

因此第二次写入不能当作可靠的 UPDATE。两次不同观测应使用各自真实的 Timestamp;增加 Field 也无法让 Point 唯一,因为 Field 不属于主键。

用 Docker 启动 InfluxDB 3 Core

拉取本文固定使用的镜像:

1
docker pull influxdb:3.11.2-core

启动容器:

1
2
3
4
5
6
7
8
9
docker run -d \
--name influxdb3-core \
--publish 127.0.0.1:8181:8181 \
--volume influxdb3-data:/var/lib/influxdb3/data \
influxdb:3.11.2-core \
influxdb3 serve \
--node-id=node0 \
--object-store=file \
--data-dir=/var/lib/influxdb3/data

服务监听本机 8181,数据写进 Docker Volume,并以本地文件作为 Object Store。node-id 也固定下来。

端口只绑定 127.0.0.1。首个管理员 Token 创建前,初始化接口允许未认证调用;刚启动就暴露到不可信网络,别人可能抢先创建拥有完整权限的 Token。

可以检查容器是否还在运行:

1
docker ps --filter name=influxdb3-core

若容器已经退出,查看日志:

1
docker logs influxdb3-core

这里不粘贴固定输出:镜像补丁版本、Docker 版本和启动时间都会改变日志。只需确认容器保持运行,并监听本机 8181 端口。

创建管理员 Token

首次初始化 InfluxDB 3 Core,需要创建 Admin Token:

1
2
docker exec influxdb3-core \
influxdb3 create token --admin

Token 明文只在创建时显示一次,要立即保存。这个 Token 拥有完整管理权限且不会过期,不能硬编码进脚本、镜像或 Git 仓库,也不该分发给所有采集程序。本地实验可先放进当前 Shell 的环境变量:

1
export INFLUXDB3_AUTH_TOKEN='把刚才返回的 Token 粘贴到这里'

直接输入可能让 Token 留在 Shell 历史中。生产环境应交给 Secret 管理工具,并配合网络隔离。InfluxDB 3 Core 3.11.2 暂不提供资源级的最小权限 Token;可以为应用创建带过期时间的 Named Admin Token,但它仍有完整管理权限。

创建第一个 Database

创建一个名为 tutorial 的 Database:

1
2
3
4
docker exec influxdb3-core \
influxdb3 create database \
--token "$INFLUXDB3_AUTH_TOKEN" \
tutorial

若实验数据最多保留 30 天,可在创建时指定 Retention Period:

1
2
3
4
5
docker exec influxdb3-core \
influxdb3 create database \
--retention-period 30d \
--token "$INFLUXDB3_AUTH_TOKEN" \
tutorial

两条创建命令选一条即可。Retention Period 决定数据能保留多久,和 SQL 的查询时间范围无关:查询最近一小时不会删除旧数据,保留 30 天也不等于每次都扫描 30 天。

InfluxDB 3 Core 默认把单次查询可访问的 Parquet 文件数限制为 432。按默认每 10 分钟生成一代文件估算,大约对应 72 小时的数据范围;它可以是近期或历史区间,并非“只能查最近 72 小时”。--query-file-limit 可以调高,但会增加内存、对象存储请求和 OOM 风险。本文只查最近一小时,不会碰到这个边界。

用 CLI 写入第一条数据

生成当前的秒级 Unix 时间戳:

1
NOW=$(date +%s)

把它放进一个天气 Point:

1
2
3
4
5
6
docker exec influxdb3-core \
influxdb3 write \
--database tutorial \
--token "$INFLUXDB3_AUTH_TOKEN" \
--precision s \
"weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i ${NOW}"

--precision sdate +%s 对应,明确告诉 InfluxDB 最后的 Timestamp 按秒解释。

如果命令成功但没有打印一张数据表,这很正常。写入操作的目标是接收数据,是否真的写对了,要通过查询验证。

用 SQL 查询刚刚写入的数据

用 SQL 读回来:

1
2
3
4
5
6
7
8
9
10
11
12
13
docker exec influxdb3-core \
influxdb3 query \
--database tutorial \
--token "$INFLUXDB3_AUTH_TOKEN" \
"SELECT
time,
city,
sensor,
temperature,
humidity
FROM weather
ORDER BY time DESC
LIMIT 10"

这里可以看到 Line Protocol 与 SQL 的对应关系:

Line ProtocolSQL 中看到的内容
Measurement weatherTable weather
Tag citycity
Tag sensorsensor
Field temperaturetemperature
Field humidityhumidity
Timestamptime

Line Protocol 写入时没有列清单,但各部分最终都会落到对应的表结构中。

通过 HTTP API 批量写入

采集程序通常通过 HTTP API 写入。为后面的聚合查询,在上一个完整的 5 分钟窗口内准备两个时间点:

1
2
3
4
NOW=$(date +%s)
WINDOW_START=$((NOW - NOW % 300 - 300))
T0=$((WINDOW_START + 60))
T1=$((WINDOW_START + 120))

WINDOW_START 是上一个完整窗口的起点。用 /api/v3/write_lp 一次发送四个 Point:

1
2
3
4
5
6
7
8
9
10
curl --request POST \
"http://localhost:8181/api/v3/write_lp?db=tutorial&precision=second" \
--header "Authorization: Bearer ${INFLUXDB3_AUTH_TOKEN}" \
--header "Content-Type: text/plain; charset=utf-8" \
--data-binary @- <<EOF
weather,city=Beijing,sensor=s01 temperature=25.8,humidity=60i ${T0}
weather,city=Shanghai,sensor=s02 temperature=24.2,humidity=73i ${T0}
weather,city=Beijing,sensor=s01 temperature=26.2,humidity=59i ${T1}
weather,city=Shanghai,sensor=s02 temperature=24.8,humidity=72i ${T1}
EOF

HTTP API 的精度写作 precision=second,Token 放在 Authorization: Bearer ... 请求头中。@- 从标准输入读取数据,<<EOF 会展开 Shell 变量。这里应保留 --data-binary:普通 --data @- 可能改动换行,而批量 Line Protocol 正靠换行分隔 Point。

/api/v3/write_lp 默认允许部分写入。如果某一行格式错误,请求会返回 400,但合法行可能已经落库。此时先看响应里的逐行错误,不要直接假设整批都没写;希望包含错误行时整批拒绝,可以在 URL 中加上 accept_partial=false

查询一个城市最近一小时的数据

先只看北京:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
docker exec influxdb3-core \
influxdb3 query \
--database tutorial \
--token "$INFLUXDB3_AUTH_TOKEN" \
"SELECT
time,
city,
sensor,
temperature,
humidity
FROM weather
WHERE
city = 'Beijing'
AND time >= now() - INTERVAL '1 hour'
AND time <= now()
ORDER BY time"

city Tag 负责过滤,time 限定范围,两个 Field 负责承载查询结果。若结果为空,可以先用 ORDER BY time DESC LIMIT 10 看最新记录;常见原因是 Timestamp 精度不一致,数据落到了错误的日期。

每 5 分钟计算一次平均温度

这条 SQL 把最近一小时的数据划入 5 分钟窗口,并按城市计算平均温度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker exec influxdb3-core \
influxdb3 query \
--database tutorial \
--token "$INFLUXDB3_AUTH_TOKEN" \
"SELECT
DATE_BIN(INTERVAL '5 minutes', time) AS window_start,
city,
AVG(temperature) AS avg_temperature
FROM weather
WHERE
time >= now() - INTERVAL '1 hour'
AND time <= now()
GROUP BY window_start, city
ORDER BY city, window_start"

DATE_BIN 把 Timestamp 对齐到 5 分钟窗口的起点:

1
2
[17:00:00, 17:05:00)  → 17:00:00 窗口
[17:05:00, 17:10:00) → 17:05:00 窗口

GROUP BY window_start 合并同一窗口,city 继续区分城市,AVG(temperature) 对每组温度求平均。这对应上一篇的查询心智模型:

1
时间范围 + Tag 分组 + Field 聚合

别名不要写成 AS timeGROUP BY time,否则 SQL 会优先指向原表的 time 列。单独使用 window_start 可以避开这层歧义。

这批数据留下的 Schema

写入完成后,weather 表的结构是:

1
2
3
4
5
6
weather
├── time Timestamp
├── city Tag
├── sensor Tag
├── temperature Float Field
└── humidity Int Field

需要按传感器型号过滤时,可以增加 model Tag:

1
weather,city=Beijing,sensor=s01,model=x100 temperature=26.5,humidity=58i <timestamp>

不参与查询的备注则放进 String Field:

1
weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i,note="window opened" <timestamp>

每一列都该有明确用途。InfluxDB 3 中的高基数 Tag 已不是旧版 TSM 架构里的同一种性能禁区,设备 ID、跟踪 ID 若用于精确筛选,可以成为 Tag;从不参与查询的长随机字符串,仍没必要放进主键。

几个常见问题怎样排查

写入时报协议解析错误

先检查分区空格:Measurement/Tag 与 Field 之间必须有一个;写了 Timestamp 时,Field 和 Timestamp 之间再有一个。

1
2
Measurement,TagSet FieldSet Timestamp
^ ^

其余常见原因包括:

  • Tag 和 Field 之间是否遗漏空格;
  • 逗号或等号旁是否多了空格;
  • String Field 是否使用双引号;
  • 整数是否带有 iu 后缀;
  • 名称中的空格、逗号和等号是否正确转义。

写入成功,但查询不到数据

先查询 ORDER BY time DESC LIMIT 10,不要马上加复杂条件。

仍然看不到时,逐项确认:

  • CLI 和 HTTP 请求是否写进同一个 Database;
  • Token 是否有对应权限;
  • 时间戳是秒还是纳秒;
  • 查询的时间范围是否覆盖数据时间;
  • 时钟是否严重偏差。

返回认证错误

确认请求头使用了当前 InfluxDB 3 Core 接受的格式:

1
Authorization: Bearer <token>

还要确认 Shell 变量确实存在:

1
test -n "$INFLUXDB3_AUTH_TOKEN" && printf 'Token variable is set\n'

这条检查只判断变量是否为空,不会把 Token 打印到终端。

InfluxDB 2.x 和 3 Core 不要混着抄

网上大量教程仍然使用 InfluxDB 1.x 或 2.x。它们不是全部失效,但需要先认出版本。

概念InfluxDB OSS 2.xInfluxDB 3 Core
外层组织Organization没有对应的 Organization 层
主要存储容器BucketDatabase
数据类别MeasurementTable,Line Protocol 中仍写 Measurement
主要查询语言Flux,也有兼容的 InfluxQLSQL 和 InfluxQL,不支持 Flux
CLIinfluxinfluxdb3
原生写入 API/api/v2/write/api/v3/write_lp
常见存储架构TSM / TSIArrow、DataFusion、Parquet 与 Object Store

InfluxDB 3 仍然提供部分 v1、v2 兼容 API,所以偶尔会看到这样的地址:

1
/api/v2/write?bucket=...

它可以与 InfluxDB 3 的兼容层一起使用,但这不代表 3.x 又恢复了完整的 Organization 和 Bucket 数据模型。兼容的是请求形态,不是完整数据模型;通过兼容 API 写入时,表的 Tag 定义不可变,同一张表里的 Tag 和 Field 也不能同名。

判断一篇旧教程能不能直接用,至少先看四个地方:

  1. 它启动的是 influxd 还是 influxdb3
  2. 它写的是 Bucket 还是 Database;
  3. 它查询时使用 Flux、InfluxQL 还是 SQL;
  4. 它访问的是 /api/v2/write 还是 /api/v3/write_lp

只要这几项不在同一版本里,就不要继续无脑复制。

实验结束后怎样清理

只想停止容器,可以执行:

1
docker stop influxdb3-core

之后还可以重新启动,数据仍然保存在 Docker Volume 中:

1
docker start influxdb3-core

如果确认整个实验都不再需要,可以删除容器:

1
docker rm -f influxdb3-core

docker rm -f 只删除容器,不会删除命名卷。如果连数据也不再需要,再执行下面的命令。它会删除 Volume 中的 InfluxDB 数据,无法通过重新启动容器找回;执行前务必确认 influxdb3-data 只存放本文的实验数据:

1
docker volume rm influxdb3-data

误删仍有用的数据卷,里面的数据就无法找回。

总结:Line Protocol 就是心智模型的文本形式

回到开头的 Line Protocol:

1
weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i,status="normal" 1724659200000000000

第一个空格前是 Measurement 和 Tag,第二段是有类型的 Field,末尾是与 precision 对应的 Timestamp。逗号分隔同一区域里的 Tag 或 Field,换行分隔一批数据里的 Point。

整个写入和查询链路可以收成这样:

1
2
3
4
5
6
7
8
9
10
11
一次业务观测

设计 Measurement、Tag、Field 和 Timestamp

编码成 Line Protocol

通过 CLI 或 HTTP 批量写入 Database

按时间范围和 Tag 查询

对 Field 做窗口聚合

不必背下所有转义规则。先记住分区空格、整数后缀和 Timestamp 精度:至少一个空格把 Field 分出来,存在 Timestamp 时才需要第二个;后两项决定列类型和数据落在时间轴的什么位置。重复写入同一个 Tag Set 与 Timestamp,也不能拿来冒充可靠的更新。

参考资料

  • 标题: InfluxDB 入门(下):读懂 Line Protocol,并写入第一批时序数据
  • 作者: Kaku
  • 创建于 : 2026-08-26 17:51:00
  • 更新于 : 2026-08-26 18:33:46
  • 链接: https://www.kakunet.top/2026/08/26/InfluxDB-入门(下):读懂-Line-Protocol,并写入第一批时序数据/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论