Files
lingniu-vehicle-ingest/docs/ops/go-vehicle-ingest-memory.md
2026-07-03 18:58:37 +08:00

463 lines
15 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.
# Go 车辆接入上下文记忆
更新时间2026-07-03
这份文档用于在 Codex 上下文重载后快速恢复项目状态。更完整的运行排查步骤见:
- `docs/ops/vehicle-ingest-runbook.md`
- `docs/architecture/production-data-plane-inventory.md`
## 当前目标
Go 版本车辆数据接入链路已经作为生产主链路运行在 ECS `115.29.187.205`
核心目标:
1. 高性能可靠接收 GB32960、JT808、宇通 MQTT。
2. 协议解析后先写 NATS再桥接 Kafka。
3. Kafka raw 供 TDengine 历史、Redis 实时、MySQL 当前态/统计消费。
4. 尽量避免 Java 老链路、Xinda 相关链路和重复存储。
5. 解析字段只投影一次,避免 TDengine/Redis/MySQL 各自重复 flatten。
6. 新长期目标:朝 10W 车辆生产稳定运行优化,容量基线见 `docs/ops/100k-capacity-baseline.md`
## 服务和端口
ECS`115.29.187.205`
| 服务 | systemd | 端口 | 作用 |
| --- | --- | --- | --- |
| Gateway | `lingniu-go-gateway.service` | `808``32960``20211` | JT808、GB32960、宇通 MQTT 接入和解析 |
| NATS fast writer | `lingniu-go-nats-fast-writer.service` | 健康端口随 env | NATS 快速写 TDengine/Redis |
| NATS Kafka bridge | `lingniu-go-nats-kafka-bridge.service` | `20214` | NATS 到 Kafka |
| History writer | `lingniu-go-history-writer.service` | `20212` | Kafka raw 写 TDengine |
| Stat writer | `lingniu-go-stat-writer.service` | `20213` | Kafka raw 写每日里程 |
| Realtime API | `lingniu-go-realtime-api.service` | `20200` | Redis/MySQL 投影和 HTTP API |
业务端口:
- JT808`0.0.0.0:808`
- GB32960`0.0.0.0:32960`
- Swagger/API`http://115.29.187.205:20200/swagger-ui/index.html`
## 部署路径
Go 服务使用 systemd 裸机部署,不使用 Docker。
```bash
/opt/lingniu-go-native/current
/opt/lingniu-go-native/releases/*
/opt/lingniu-go-native/env/*.env
```
最近 release
```text
/opt/lingniu-go-native/releases/100k-hardening-20260703175730
/opt/lingniu-go-native/releases/parsed-fields-once-20260703163821
```
服务重启:
```bash
systemctl restart lingniu-go-gateway.service
systemctl restart lingniu-go-nats-fast-writer.service
systemctl restart lingniu-go-nats-kafka-bridge.service
systemctl restart lingniu-go-history-writer.service
systemctl restart lingniu-go-stat-writer.service
systemctl restart lingniu-go-realtime-api.service
```
健康检查:
```bash
for port in 20211 20212 20213 20214 20200; do
curl -fsS "http://127.0.0.1:${port}/readyz"
echo
done
```
## 中间件
Kafka / NATS ECS
- 公网:`114.55.58.251`
- 内网:`172.17.111.56`
- NATS`172.17.111.56:4222`
TDengine
- 内网:`172.17.111.57`
- WS`172.17.111.57:6041`
- 数据库:`lingniu_vehicle_ts`
MySQL RDS
- 内网:`rm-bp179zbv481rnw3e2.mysql.rds.aliyuncs.com:3306`
- 数据库:`lingniu_vehicle_data`
Redis
- 内网:`r-bp1u741kij7e51i481.redis.rds.aliyuncs.com:6379`
- DB`50`
不要在最终回答中明文输出生产密码。
## Topic
当前 Go raw topic
```text
vehicle.raw.go.gb32960.v1
vehicle.raw.go.jt808.v1
vehicle.raw.go.yutong-mqtt.v1
```
fields topic 已存在:
```text
vehicle.fields.go.gb32960.v1
vehicle.fields.go.jt808.v1
vehicle.fields.go.yutong-mqtt.v1
```
当前核心链路仍以 raw topic 作为事实输入raw envelope 里已经包含一次性预计算的 `parsed_fields`
## 代码结构
主要目录:
```text
go/vehicle-gateway/cmd/gateway
go/vehicle-gateway/cmd/history-writer
go/vehicle-gateway/cmd/realtime-api
go/vehicle-gateway/cmd/stat-writer
go/vehicle-gateway/cmd/nats-fast-writer
go/vehicle-gateway/cmd/nats-kafka-bridge
go/vehicle-gateway/internal/protocol/gb32960
go/vehicle-gateway/internal/protocol/jt808
go/vehicle-gateway/internal/protocol/yutongmqtt
go/vehicle-gateway/internal/realtime
go/vehicle-gateway/internal/history
go/vehicle-gateway/internal/identity
```
每次改代码后在本地跑:
```bash
cd go/vehicle-gateway
go test ./...
```
## 解析和存储原则
### 一次解析字段
2026-07-03 已完成优化:
- `FrameEnvelope` 增加:
- `parsed_fields`
- `parsed_field_types`
- Gateway 在发布 raw 前调用 `realtime.EnsureParsedFields(&env)`
- TDengine writer、Redis realtime KV、MySQL snapshot 优先使用 `env.ParsedFields`
- 旧消息没有 `parsed_fields` 时,保留 fallback`Parsed` 展开一次。
相关文件:
```text
go/vehicle-gateway/internal/envelope/envelope.go
go/vehicle-gateway/internal/realtime/kv.go
go/vehicle-gateway/internal/gateway/tcp_server.go
go/vehicle-gateway/internal/gateway/mqtt_client.go
go/vehicle-gateway/internal/history/writer.go
go/vehicle-gateway/internal/realtime/repository.go
go/vehicle-gateway/internal/realtime/snapshot_writer.go
```
### GB32960 多帧
GB32960 有两类实际模式:
1. 单条 `0x02` 同时包含标准 data unit 和 vendor data unit。
2. 标准帧加 vendor 栈分片帧。
例子:
- `LB9A32A24R0LS1426`:单帧完整混合上报,单帧约 `774 bytes``gd_fc_stack.cell_count=108`
- `LNXNEGRR7SR318212`:多帧上报,`gd_fc_stack.cell_count=432`,按 `200 + 200 + 32` 分片。
TDengine raw_frames 必须保持单帧事实,不跨帧合并。
Redis/MySQL 实时视图可以按 `protocol + vin` 增量合并,但不能让后来的 stack-only 帧覆盖标准车辆字段。
### JT808
JT808 主入口端口是 `808`,不再使用 `8089`
解析重点:
- 包头手机号按 BCD 去前导 0。
- `0x0100` 注册帧写 `jt808_registration`
- `0x0102` 鉴权帧尝试按 phone 查 registration/binding 关联 VIN。
- `0x0200` 位置帧即使没有注册/鉴权,也会按 phone 降级查 `vehicle_identity_binding`,建立 session。
- 位置附加信息 `0x01` 是 GPS 总里程,按差值用于每日里程。
## MySQL 身份表
`vehicle_identity_binding` 是外部维护事实表,服务只读,不允许运行时写入。
核心字段:
```text
vin
plate
phone
oem
updated_at
```
用途:
- JT808 用 phone 或 plate 找 VIN。
- Realtime snapshot/location 用 VIN 反查 plate。
- 后续导入 G7s、广安车联等来源时更新 phone/oem。
`jt808_registration` 是 808 注册鉴权运行状态表,以 phone 为主键记录注册、鉴权、source_endpoint、关联 VIN 状态。
## 2026-07-03 数据维护记录
### G7s binding
源文件:
```text
/Users/lingniu/Library/Mobile Documents/com~apple~CloudDocs/rsync/2026_07_02_17_11_37转发配置列表-1(2).xlsx
```
处理规则:
1. 删除原有 G7/G7s 的 `phone/oem`
2. 使用文件中的 `手机号` 列更新。
3. 冲突的不更新,其他更新。
执行结果:
- 清空旧 G7/G7s`666` 行。
- 更新安全 G7s`561` 行。
- 跳过:`72` 行。
- 缺 plate 或 VIN`14`
- 与非 G7/G7s 绑定冲突:`58`
跳过清单:
```text
outputs/g7s_binding_skipped_apply.csv
```
### JT808 unknown VIN 导出
导出条件:
- `jt808_registration.vin` 为空、`unknown``unknow`
-`phone` 不存在于 `vehicle_identity_binding.phone`
结果:
- `30` 个 phone
导出文件:
```text
outputs/jt808_unknown_vin_phone_not_in_binding.csv
```
## 常用查询
历史 raw frame
```bash
curl -s 'http://115.29.187.205:20200/api/history/raw-frames?protocol=GB32960&vin=LB9A32A24R0LS1426&limit=1&includeFields=true'
```
只取指定 parsed fields
```bash
curl -s 'http://115.29.187.205:20200/api/history/raw-frames?protocol=GB32960&vin=LB9A32A24R0LS1426&limit=1&includeFields=true&fields=gb32960.vehicle.speed_kmh&fields=gb32960.gd_fc_stack.stack_water_outlet_temp_c'
```
808 源 IP
```bash
journalctl -u lingniu-go-gateway.service --since '10 minutes ago' --no-pager
ss -tn sport = :808
```
最近已观察到 808 源 IP
```text
115.159.85.149
222.66.200.68
122.152.221.156
115.231.168.135
```
## 部署步骤
本地构建:
```bash
cd go/vehicle-gateway
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-deploy/gateway ./cmd/gateway
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-deploy/history-writer ./cmd/history-writer
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-deploy/realtime-api ./cmd/realtime-api
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-deploy/stat-writer ./cmd/stat-writer
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-deploy/nats-fast-writer ./cmd/nats-fast-writer
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-deploy/nats-kafka-bridge ./cmd/nats-kafka-bridge
```
上传部署:
```bash
release="name-$(date +%Y%m%d%H%M%S)"
ssh root@115.29.187.205 "mkdir -p /opt/lingniu-go-native/releases/$release"
scp /tmp/lingniu-go-deploy/* root@115.29.187.205:/opt/lingniu-go-native/releases/$release/
ssh root@115.29.187.205 "chmod 755 /opt/lingniu-go-native/releases/$release/* && ln -sfn /opt/lingniu-go-native/releases/$release /opt/lingniu-go-native/current"
```
部署后验证:
```bash
ssh root@115.29.187.205 '
systemctl is-active lingniu-go-gateway.service lingniu-go-nats-fast-writer.service lingniu-go-nats-kafka-bridge.service lingniu-go-history-writer.service lingniu-go-stat-writer.service lingniu-go-realtime-api.service
for port in 20211 20212 20213 20214 20200; do curl -fsS "http://127.0.0.1:${port}/readyz"; echo; done
journalctl -u lingniu-go-gateway.service --since "2 minutes ago" --no-pager | grep -Ei "error|failed|panic|fatal" || true
'
```
## 2026-07-03 10W 接入优化部署记录
Release
```text
/opt/lingniu-go-native/releases/100k-hardening-20260703175730
```
包含:
- Gateway 默认 `TCP_MAX_CONNECTIONS=120000`
- 新增 `vehicle_gateway_connection_rejections_total`
- 新增 `/opt/lingniu-go-native/current/load-sim` 运维压测工具。
- Redis-first realtime projector 增加 MySQL async queue dropped 回归测试。
ECS 已落地内核参数:
```text
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_tw_reuse = 1
```
注意:远端 `/etc/sysctl.conf` 原本有 `net.ipv4.tcp_max_syn_backlog = 1024`,已改为 `65535`,同时保留 `/etc/sysctl.d/zz-lingniu-vehicle-ingest.conf`
部署后验证:
- `current` 指向 `100k-hardening-20260703175730`
- `20211/20212/20213/20214/20200` readyz 均为 ok。
- `808``32960``20200``20211-20214` 监听 backlog 均显示 `65535`
- Gateway 重启后持续接收 GB32960、JT808、Yutong MQTT 帧。
- 重启完成后窗口未发现 gateway/realtime-api 持续 error/fatal 日志。
### 10W 阶段压测进展
2026-07-03 已完成 ECS 本机 JT808 hold-only 阶段压测,不发送业务帧:
| 阶段 | 结果 |
| --- | --- |
| 100 连接 / 60s | opened `100`failed `0`frames `0`write_errors `0` |
| 1,000 连接 / 60s | opened `1000`failed `0`frames `0`write_errors `0` |
| 10,000 连接 / 120s | opened `10000`failed `0`frames `0`write_errors `0` |
| 50,000 连接 / 180s | opened `50000`failed `0`frames `0`write_errors `0` |
| 100,000 总连接 / 180s | JT808 opened `50000`、GB32960 opened `50000`failed `0`frames `0`write_errors `0` |
1W 阶段峰值约 `10240` JT808 active connectionsgateway FD 约 `10256`NATS ack pending 为 `0`,压测后连接回落到生产基线约 `240`readyz 全部 OK。
5W 阶段峰值约 `50238` JT808 active connectionsgateway FD 约 `50254`available memory 约 `5590 MB`。NATS ack pending 中间短时到 `29``18:16:49 CST` 回到 `0`history/stat/realtime Kafka lag 总和均为 `0`。压测后 JT808 active 回落到生产基线约 `238-240`readyz 全部 OK。
10W 总连接阶段使用双端口 loopbackJT808 `50000` + GB32960 `50000`,均 `-send=false`。峰值约 `100238` active connectionsgateway FD 约 `100252`available memory 约 `4511 MB`。NATS ack pending 中间短时到 `10``18:23:02 CST` 回到 `0`history/stat/realtime Kafka lag 总和均为 `0`。压测后 JT808 回落到约 `236`GB32960 回落到约 `2`readyz 全部 OK。
### Gateway async publish 指标
2026-07-03 已部署 gateway async sink 指标到 ECS 当前 release
```text
vehicle_async_sink_enqueue_total{sink,kind,status}
vehicle_async_sink_publish_total{sink,kind,status}
vehicle_async_sink_publish_duration_ms{sink,kind,status}
vehicle_async_sink_queue_depth{sink}
```
部署后验证:
- `vehicle_async_sink_queue_depth{sink="nats"} 0`
- `vehicle_async_sink_enqueue_total{kind="raw",sink="nats",status="queued"}` 持续增长
- `vehicle_async_sink_publish_total{kind="raw",sink="nats",status="ok"}` 持续增长
- NATS bridge ack pending 为 `0`
- Gateway 启动后窗口无 `error|failed|panic|fatal` 日志
这些指标是进入带帧率压测前的关键保护栏:如果 `queue_depth` 持续增长或 `enqueue_total{status="timeout"}` 增长,说明入口 publish 队列已成为瓶颈。
### NATS Kafka bridge batch 指标
2026-07-03 已为 NATS -> Kafka bridge 增加 batch pending 和 duration histogram
```text
vehicle_bridge_batch_pending_messages
vehicle_bridge_batch_pending_kafka_messages
vehicle_bridge_batch_duration_ms_histogram_bucket{status,le}
vehicle_bridge_batch_duration_ms_histogram_count{status}
vehicle_bridge_batch_duration_ms_histogram_sum{status}
```
该耗时覆盖一批 NATS 消息路由到 Kafka、Kafka 同步写入、NATS ack 的完整过程。带帧率压测时,如果 pending 持续非 0 或 duration p99 持续上升,应优先排查 Kafka broker、bridge batch size、NATS ack-pending 和网络。
### Gateway frame duration histogram
2026-07-03 已为 Gateway 帧处理耗时增加 histogram
```text
vehicle_gateway_frame_duration_ms_histogram_bucket{protocol,status,le}
vehicle_gateway_frame_duration_ms_histogram_count{protocol,status}
vehicle_gateway_frame_duration_ms_histogram_sum{protocol,status}
```
该耗时覆盖单帧从解析、identity resolve、parsed fields 生成、publish enqueue 到协议响应写入的整体入口处理路径。带帧率压测时用它按协议观察 p95/p99不能只看 `vehicle_gateway_frame_duration_ms` 最后一条 gauge。
### History writer TDengine 批写
2026-07-03 已实现 history-writer 第一阶段批写:
- `history.Writer.AppendAllBatch` 按 TDengine child table 聚合 raw/location 多行 `INSERT`
- `cmd/history-writer` 默认 `HISTORY_BATCH_SIZE=200``HISTORY_BATCH_WAIT_MS=100`
- TDengine batch 成功后才批量提交 Kafka messages失败不提交 offset。
- 保留单条 `AppendAll` 路径作为回退。
新增指标:
```text
vehicle_history_batch_flush_total{status}
vehicle_history_batch_rows_total{status}
vehicle_history_batch_pending_messages
vehicle_history_batch_pending_rows
vehicle_history_batch_flush_duration_ms{status}
```
进入带帧率压测时,必须同时观察 `vehicle_history_kafka_lag``vehicle_history_batch_pending_*``vehicle_history_batch_*`。如果 pending 持续非 0 但 Kafka lag 尚未放大,说明 history-writer 已经在本地批写或提交阶段形成早期积压。
## 下次恢复上下文时先做
1. 读本文件。
2.`docs/ops/vehicle-ingest-runbook.md`
3.`docs/architecture/production-data-plane-inventory.md`
4. 执行 `git status --short`
5. 如果涉及生产,先查 ECS 服务健康和最近日志。