Files
lingniu-vehicle-ingest/docs/ops/vehicle-ingest-runbook.md

267 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 车辆接入生产运行手册
## 范围
本文覆盖 ECS `115.29.187.205` 上的 Go 原生车辆接入链路:
- GB32960 TCP 接入
- JT/T 808 TCP 接入
- 宇通 MQTT 接入
- NATS 到 Kafka 桥接
- Kafka 历史、统计、实时消费者
- Redis 实时缓存、MySQL 实时表、TDengine 历史表
运行手册的目标是按自上而下的顺序定位问题:入口、队列、桥接、消费、存储、查询 API。
当前生产服务、topic、表和 Redis key 的清单见 [生产数据面清单](../architecture/production-data-plane-inventory.md)。
10W 车辆容量目标、当前缺口和压测口径见 [100K 车辆接入容量基线](100k-capacity-baseline.md)。
## 服务地图
| 层级 | 服务 | systemd 单元 | 本机端点 |
| --- | --- | --- | --- |
| 接入 | Gateway | `lingniu-go-gateway.service` | `127.0.0.1:20211` |
| 桥接 | NATS Kafka bridge | `lingniu-go-nats-kafka-bridge.service` | `127.0.0.1:20214` |
| 历史 | TDengine writer | `lingniu-go-history-writer.service` | `127.0.0.1:20212` |
| 统计 | MySQL stat writer | `lingniu-go-stat-writer.service` | `127.0.0.1:20213` |
| 实时 | Realtime API/projector | `lingniu-go-realtime-api.service` | `127.0.0.1:20200` |
## Topic 配置基线
生产环境的 env 文件必须和 Go topic 命名空间保持一致:
| 服务 | 必要 topic |
| --- | --- |
| `lingniu-go-history-writer.service` | `vehicle.raw.go.gb32960.v1``vehicle.raw.go.jt808.v1``vehicle.raw.go.yutong-mqtt.v1` |
| `lingniu-go-stat-writer.service` | `vehicle.raw.go.gb32960.v1``vehicle.raw.go.jt808.v1``vehicle.raw.go.yutong-mqtt.v1` |
| `lingniu-go-realtime-api.service` | `vehicle.raw.go.gb32960.v1``vehicle.raw.go.jt808.v1``vehicle.raw.go.yutong-mqtt.v1` |
| `lingniu-go-nats-kafka-bridge.service` | NATS raw subjects 到同名 Kafka topic`vehicle.event.go.unified.v1` 只在兼容开关打开时使用 |
如果 stat-writer 少消费某个 raw topic对应协议的每日里程不会进入 `vehicle_daily_mileage`。修正后可能出现短时间 Kafka lag这是在追补历史 backlog只要 `vehicle_stat_writes_total` 持续增长且 lag 下降,就是健康状态。
Realtime API 是当前态投影,默认在没有已提交 offset 时从 latest 开始消费。切换 topic 或新建 consumer group 后,不应让 realtime 追扫历史 raw backlog需要重建当前态时应使用明确的回放任务或手动 reset offset。
业务端口:
- GB32960 TCP`0.0.0.0:32960`
- JT/T 808 TCP`0.0.0.0:808`
- 实时/API`0.0.0.0:20200`
## 五分钟排查顺序
先回答最核心的问题:数据有没有进来,有没有排队,有没有被消费,最后有没有被查询到。
1. 检查所有服务 `/readyz`
2. 检查 gateway 帧计数和 TCP 活跃连接。
3. 检查 NATS bridge 的 pending 和 ack-pending。
4. 检查 bridge 写 Kafka 与 NATS ack 是否同时增长。
5. 检查 history、stat、realtime 三类 Kafka consumer lag。
6. 检查各 writer 的写入、提交、更新计数。
7. 最后再查存储和业务查询 API。
```bash
for port in 20211 20212 20213 20214 20200; do
curl -fsS "http://127.0.0.1:${port}/readyz"
echo
done
curl -fsS http://127.0.0.1:20211/metrics \
| grep -E 'vehicle_gateway_(active_connections|frames_total|publish_total)|vehicle_async_sink'
curl -fsS http://127.0.0.1:20214/metrics \
| grep -E 'vehicle_bridge_(nats_consumer|kafka_writes_total|nats_acks_total)'
curl -fsS http://127.0.0.1:20212/metrics | grep vehicle_history_kafka_lag
curl -fsS http://127.0.0.1:20212/metrics | grep vehicle_history_batch
curl -fsS http://127.0.0.1:20213/metrics | grep vehicle_stat_kafka_lag
curl -fsS http://127.0.0.1:20200/metrics | grep vehicle_realtime_kafka_lag
/opt/lingniu-go-native/current/capacity-check
```
## 健康基线
当前生产环境的健康特征:
- 所有 `/readyz` 都返回 `status=ok`
- `vehicle_bridge_nats_consumer_ack_pending``0`
- history、stat、realtime 的 Kafka lag 为 `0` 或短时间小幅波动后归零。
- `capacity-check` 返回 `status=ok` 且退出码为 `0`
- gateway 的帧计数持续增长。
- bridge 的 Kafka write 和 NATS ack 计数同时增长。
- writer 的成功计数增长,同时 Kafka lag 不持续扩大。
定时容量巡检由 systemd timer 触发:
```bash
systemctl status lingniu-go-capacity-check.timer
journalctl -u lingniu-go-capacity-check.service --since '10 minutes ago' --no-pager
```
如果 `lingniu-go-capacity-check.service` 失败,先看 journal JSON 里的 `findings`,再按对应层级处理。
GB32960 和 JT/T 808 的活跃连接数受上游平台连接方式影响,不能直接等同于车辆数。突然归零或持续异常下降才是信号。
## 压测入口
Go 版本提供 `cmd/load-sim` 用于阶段性连接和帧写入压测。压测生产入口前必须先确认上游真实数据窗口,避免和业务流量混淆。
```bash
cd /opt/lingniu-go-native/current/go/vehicle-gateway
go run ./cmd/load-sim \
-protocol jt808 \
-addr 127.0.0.1:808 \
-connections 100 \
-connect-rate 100 \
-send-interval 10s \
-duration 2m \
-template 0200 \
-send=false
```
## 告警阈值建议
| 信号 | 建议阈值 | 含义 |
| --- | --- | --- |
| `/readyz` 非 ok | 立即处理 | 服务或依赖不可用。 |
| Gateway 活跃连接 | 预期有流量时某协议降为 `0` | 上游网络、监听端口或进程可能异常。 |
| `vehicle_gateway_connection_closes_total{reason="read_error"}` | 连续增长 | 入口 TCP 读失败,优先查网络、客户端断连和内核连接状态。 |
| `vehicle_gateway_connection_closes_total{reason="extract_error"}` | 连续增长 | 报文边界或协议提取异常,优先抽查 raw 日志和协议 extractor。 |
| `vehicle_gateway_connection_closes_total{reason="read_timeout"}` | 突然高于历史基线 | 车辆长时间无上报或链路空闲超时,需结合在线数和上游平台状态判断。 |
| `vehicle_gateway_frames_total{status!="OK"}` | 连续 2 分钟增长 | 解析器或上游报文质量异常。 |
| `vehicle_gateway_frame_duration_ms_histogram_bucket` | p99 连续 5 分钟超过容量目标 | Gateway parse、identity resolve、publish enqueue 或响应链路变慢。 |
| `vehicle_async_sink_queue_depth{sink="nats"}` | 持续增长且不回落 | Gateway 到 NATS/Kafka 的异步 publish 队列开始积压。 |
| `vehicle_async_sink_enqueue_total{status="timeout"}` | 任意增长 | Gateway publish 队列已满或 worker 长时间阻塞,入口可能开始丢实时性。 |
| `vehicle_async_sink_publish_total{status="error"}` | 连续增长 | NATS/Kafka publish 失败,需要先查中间件连接和日志。 |
| `vehicle_async_sink_publish_duration_ms_histogram_bucket` | p99 连续 5 分钟上升 | Gateway async worker 写 NATS/Kafka 变慢,通常会带动 queue depth 增长。 |
| `vehicle_history_batch_flush_total{status="error"}` | 任意增长 | TDengine 批写失败Kafka offset 不会提交,应先查 TDengine 和 SQL 错误。 |
| `vehicle_history_batch_pending_messages` | 持续非 0 或 burst 后不回落 | history-writer 已拉取但未完成写入/提交,可能卡在 TDengine 或 Kafka commit。 |
| `vehicle_history_batch_pending_rows` | 持续非 0 或 burst 后不回落 | TDengine 有批量写入积压,通常早于 Kafka lag 放大。 |
| `vehicle_history_batch_flush_duration_ms{status="ok"}` | 持续上升 | TDengine 写入延迟增加,可能需要降低 batch size 或扩容 TDengine。 |
| `vehicle_bridge_nats_consumer_ack_pending` | 连续 2 分钟 `> 0` | 消息已投递给 bridge但 Kafka 写入后未完成 ack。 |
| `vehicle_bridge_nats_consumer_pending` | 持续增长且 `> 10000` | bridge 消费 NATS 的速度跟不上生产速度。 |
| `vehicle_bridge_batch_pending_messages` | 持续非 0 或 burst 后不回落 | bridge 已拉取 NATS 消息但尚未完成 Kafka 写入和 NATS ack。 |
| `vehicle_bridge_batch_duration_ms_histogram_bucket` | p99 连续 5 分钟上升 | Kafka 写入或 NATS ack 开始变慢,通常会先于 ack-pending 扩大。 |
| `vehicle_fast_writer_nats_consumer_ack_pending` | 连续 2 分钟 `> 0` | 消息已投递给 fast-writer但 TDengine/Redis 写入后未完成 ack。 |
| `vehicle_fast_writer_nats_consumer_pending` | 持续增长且 `> 10000` | fast-writer 消费 NATS 的速度跟不上入口写入速度。 |
| `vehicle_fast_writer_batch_pending_messages` | 持续非 0 或 burst 后不回落 | fast-writer 已拉取 NATS 消息但尚未完成 TDengine/Redis 写入和 ack。 |
| `vehicle_fast_writer_batch_pending_envelopes` | 持续非 0 或 burst 后不回落 | fast-writer 当前批次已有有效 envelope 在等待落库或 ack。 |
| `vehicle_fast_writer_stage_duration_ms_histogram_bucket` | 某个 stage 的 p99 连续 5 分钟上升 | NATS 快速写链路在 TDengine、Redis 或 NATS ack 某一阶段变慢TDengine 阶段可小步调整 `FAST_WRITER_TDENGINE_MAX_OPEN_CONNS`Redis 阶段是整批 pipeline 写入耗时,调整后必须观察是否出现选库错误和 ack-pending 增长。 |
| `vehicle_realtime_store_update_duration_ms_histogram_bucket{store="redis"}` | p99 连续 5 分钟上升 | Redis 实时投影变慢,会直接影响 realtime consumer 追平能力。 |
| `vehicle_realtime_store_update_duration_ms_histogram_bucket{store="mysql"}` | p99 连续 5 分钟上升 | MySQL 当前态/位置投影变慢,需结合 async queue depth 和 dropped 计数判断。 |
| `vehicle_stat_write_duration_ms_histogram_bucket` | p99 连续 5 分钟上升 | MySQL 每日里程统计写入变慢,可能导致 stat Kafka lag 增长。 |
| Kafka lag | 连续 5 分钟增长或 `> 10000` | 下游 consumer 或存储存在瓶颈。 |
| Writer 成功计数 | 入口增长但 writer 不增长 | bridge、Kafka、consumer 或存储链路断开。 |
## 事故处理路径
### 没有新数据
1. 先确认监听端口。
```bash
ss -lntp | grep -E ':(808|32960|20200|20211|20212|20213|20214) '
```
2. 检查 gateway readiness 和日志。
```bash
curl -fsS http://127.0.0.1:20211/readyz
journalctl -u lingniu-go-gateway.service --since '10 minutes ago' --no-pager
```
3. 看 gateway 帧计数是否增长。
4. 如果 gateway 有增长但 bridge 没增长,查 NATS publish 和 bridge 日志。
5. 如果 bridge 写入增长但 writer 不增长,查 Kafka lag 和 consumer 日志。
### Kafka lag 持续增长
1. 先从 metrics 定位是哪个服务、哪个 topic、哪个 partition。
2. 检查对应服务 `/readyz`
3. 检查存储依赖history 看 TDenginestat 看 MySQLrealtime 看 Redis/MySQL/TDengine。
4. 先看日志,再决定是否重启。
```bash
journalctl -u lingniu-go-history-writer.service --since '10 minutes ago' --no-pager
journalctl -u lingniu-go-stat-writer.service --since '10 minutes ago' --no-pager
journalctl -u lingniu-go-realtime-api.service --since '10 minutes ago' --no-pager
```
Kafka 是可回放日志。只要 Kafka 还保留消息,恢复 consumer 后 lag 应该能自动追平。不要在 lag 未归零前手工补数。
### NATS pending 持续增长
1. 检查 bridge `/readyz` 和日志。
2. 对比 `vehicle_bridge_kafka_writes_total``vehicle_bridge_nats_acks_total`
3. 如果 Kafka 写入失败,检查 ECS 到 Kafka broker 的网络。
4. 如果 `ack_pending > 0`,优先检查 Kafka 是否监听 `9092`、Kafka 盘是否打满,再看 bridge 日志。
5. 如果 `ack_pending = 0``consumer_pending` 下降,说明 bridge 正在追历史积压;不要反复重启,持续观察下降速度即可。
6. 如果 `consumer_pending` 不下降,才考虑扩容 bridge 或降低 batch/fetch 等配置。
```bash
journalctl -u lingniu-go-nats-kafka-bridge.service --since '10 minutes ago' --no-pager
systemctl restart lingniu-go-nats-kafka-bridge.service
```
生产 stream 必须设置字节上限,避免 NATS JetStream 在 Kafka 或下游故障时吃满根盘:
```bash
grep NATS_STREAM_MAX_BYTES /opt/lingniu-go-native/env/nats-fast-writer.env
grep NATS_STREAM_MAX_BYTES /opt/lingniu-go-native/env/nats-kafka-bridge.env
grep NATS_STREAM_ENSURE_TIMEOUT_SECONDS /opt/lingniu-go-native/env/nats-fast-writer.env
grep NATS_STREAM_ENSURE_TIMEOUT_SECONDS /opt/lingniu-go-native/env/nats-kafka-bridge.env
du -sh /opt/lingniu-nats/data
df -h /
```
当前建议值:`NATS_STREAM_MAX_BYTES=21474836480`,即 `20GiB``NATS_STREAM_ENSURE_TIMEOUT_SECONDS=60`,避免大 stream 元数据更新时被 NATS 客户端默认 5s 超时误杀。Kafka topic 只作为短期缓冲,当前建议保留 `6h`,不要把 Kafka 或 NATS 当长期历史存储;长期历史和 RAW 查询以 TDengine/MySQL 投影为准。
fast-writer 的 `FAST_WRITER_OPERATION_TIMEOUT_MS` 建议为 `1000`。实时链路仍以 100ms 级为目标,但 TDengine 批量写存在尾延迟,过小的超时会造成 NATS 消息反复重投和重复写压力。
### Raw 有数据但实时查不到
1. 查 realtime Kafka lag。
2. 查 realtime update 计数。
3. 查 realtime API readiness。
4. 查最新 snapshot/location API。
```bash
curl -fsS http://127.0.0.1:20200/readyz
curl -fsS 'http://127.0.0.1:20200/api/realtime/locations?limit=1'
curl -fsS 'http://127.0.0.1:20200/api/realtime/snapshots?limit=1'
```
## 重启顺序
优先重启最小故障层:
1. 只有一个 consumer lag 异常时,先重启对应 writer。
2. 实时投影或 API 异常时,重启 realtime API。
3. bridge pending 或 ack-pending 卡住时,重启 NATS Kafka bridge。
4. 只有入口监听、解析循环或上游连接处理异常时,才重启 gateway。
```bash
systemctl restart lingniu-go-history-writer.service
systemctl restart lingniu-go-stat-writer.service
systemctl restart lingniu-go-realtime-api.service
systemctl restart lingniu-go-nats-kafka-bridge.service
systemctl restart lingniu-go-gateway.service
```
排查前不要直接全量重启。全量重启会抹掉时间线证据,也会掩盖问题到底发生在入口、队列、存储还是查询层。
## 存储原则
运行指标保留在 `/metrics`,不要为了服务健康计数新增 MySQL 或 TDengine 业务表。
业务存储保持最小化:
- raw frames 保存完整 parsed JSON用于回放和审计。
- realtime snapshot/location 只保存 API 需要的当前业务字段。
- history 表保存可查询的时间序列核心字段。
- 派生统计应能从 Kafka 或 raw history 重新计算。
只有当产品查询明确需要、并且指标定义稳定时,才新增持久化聚合。