20 KiB
Go 车辆接入上下文记忆
更新时间:2026-07-03
这份文档用于在 Codex 上下文重载后快速恢复项目状态。更完整的运行排查步骤见:
docs/ops/vehicle-ingest-runbook.mddocs/architecture/production-data-plane-inventory.md
当前目标
Go 版本车辆数据接入链路已经作为生产主链路运行在 ECS 115.29.187.205。
核心目标:
- 高性能可靠接收 GB32960、JT808、宇通 MQTT。
- 协议解析后先写 NATS,再桥接 Kafka。
- Kafka raw 供 TDengine 历史、Redis 实时、MySQL 当前态/统计消费。
- 尽量避免 Java 老链路、Xinda 相关链路和重复存储。
- 解析字段只投影一次,避免 TDengine/Redis/MySQL 各自重复 flatten。
- 新长期目标:朝 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。
/opt/lingniu-go-native/current
/opt/lingniu-go-native/releases/*
/opt/lingniu-go-native/env/*.env
最近 release:
/opt/lingniu-go-native/releases/100k-hardening-20260703175730
/opt/lingniu-go-native/releases/parsed-fields-once-20260703163821
服务重启:
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
健康检查:
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:
vehicle.raw.go.gb32960.v1
vehicle.raw.go.jt808.v1
vehicle.raw.go.yutong-mqtt.v1
fields topic 已存在:
vehicle.fields.go.gb32960.v1
vehicle.fields.go.jt808.v1
vehicle.fields.go.yutong-mqtt.v1
当前核心链路仍以 raw topic 作为事实输入;raw envelope 里已经包含一次性预计算的 parsed_fields。
代码结构
主要目录:
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
每次改代码后在本地跑:
cd go/vehicle-gateway
go test ./...
解析和存储原则
一次解析字段
2026-07-03 已完成优化:
FrameEnvelope增加:parsed_fieldsparsed_field_types
- Gateway 在发布 raw 前调用
realtime.EnsureParsedFields(&env)。 - TDengine writer、Redis realtime KV、MySQL snapshot 优先使用
env.ParsedFields。 - 旧消息没有
parsed_fields时,保留 fallback,从Parsed展开一次。
相关文件:
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 有两类实际模式:
- 单条
0x02同时包含标准 data unit 和 vendor data unit。 - 标准帧加 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 是外部维护事实表,服务只读,不允许运行时写入。
核心字段:
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
源文件:
/Users/lingniu/Library/Mobile Documents/com~apple~CloudDocs/rsync/2026_07_02_17_11_37转发配置列表-1(2).xlsx
处理规则:
- 删除原有 G7/G7s 的
phone/oem。 - 使用文件中的
手机号列更新。 - 冲突的不更新,其他更新。
执行结果:
- 清空旧 G7/G7s:
666行。 - 更新安全 G7s:
561行。 - 跳过:
72行。- 缺 plate 或 VIN:
14 - 与非 G7/G7s 绑定冲突:
58
- 缺 plate 或 VIN:
跳过清单:
outputs/g7s_binding_skipped_apply.csv
JT808 unknown VIN 导出
导出条件:
jt808_registration.vin为空、unknown或unknow- 且
phone不存在于vehicle_identity_binding.phone
结果:
30个 phone
导出文件:
outputs/jt808_unknown_vin_phone_not_in_binding.csv
常用查询
历史 raw frame:
curl -s 'http://115.29.187.205:20200/api/history/raw-frames?protocol=GB32960&vin=LB9A32A24R0LS1426&limit=1&includeFields=true'
只取指定 parsed fields:
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:
journalctl -u lingniu-go-gateway.service --since '10 minutes ago' --no-pager
ss -tn sport = :808
最近已观察到 808 源 IP:
115.159.85.149
222.66.200.68
122.152.221.156
115.231.168.135
部署步骤
本地构建:
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
上传部署:
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"
部署后验证:
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:
/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 已落地内核参数:
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/20200readyz 均为 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 connections,gateway FD 约 10256,NATS ack pending 为 0,压测后连接回落到生产基线约 240,readyz 全部 OK。
5W 阶段峰值约 50238 JT808 active connections,gateway 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 总连接阶段使用双端口 loopback:JT808 50000 + GB32960 50000,均 -send=false。峰值约 100238 active connections,gateway 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:
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_publish_duration_ms_histogram_bucket{sink,kind,status,le}
vehicle_async_sink_publish_duration_ms_histogram_count{sink,kind,status}
vehicle_async_sink_publish_duration_ms_histogram_sum{sink,kind,status}
vehicle_async_sink_queue_depth{sink}
部署后验证:
vehicle_async_sink_queue_depth{sink="nats"} 0vehicle_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"} 增长,或 vehicle_async_sink_publish_duration_ms_histogram_bucket 的 p99 持续上升,说明入口 publish 队列或下游 NATS/Kafka 写入已成为瓶颈。
Gateway connection close reason
2026-07-03 已为 TCP gateway 增加连接关闭原因计数:
vehicle_gateway_connection_closes_total{protocol,reason}
当前 reason 包括 eof、read_timeout、read_error、extract_error、context_cancelled、max_connections。10W 长连接和带帧率压测时,应重点关注 read_error、extract_error、max_connections 是否异常增长;read_timeout 需要结合协议上报周期和在线数判断。
NATS fast writer stage histogram
2026-07-03 已为 NATS fast writer 增加阶段耗时 histogram:
vehicle_fast_writer_stage_duration_ms_histogram_bucket{subject,stage,status,le}
vehicle_fast_writer_stage_duration_ms_histogram_count{subject,stage,status}
vehicle_fast_writer_stage_duration_ms_histogram_sum{subject,stage,status}
stage 取值包括 tdengine、redis、ack,分别对应快速写 TDengine raw/location、Redis realtime 投影、NATS ack。带帧率压测时如果某个 stage 的 p99 持续上升,可以直接定位 fast path 是存储瓶颈还是 ack/网络瓶颈。
2026-07-03 后续优化:nats-fast-writer 已从“Fetch batch 后逐条写 TDengine”改为“Fetch batch 后先调用 history.Writer.AppendAllBatch 批量写 TDengine,再逐条更新 Redis 和 ack”。这样可以减少 TDengine round trip;Redis/ack 仍按单条执行,避免某条实时投影失败时误 ack。
2026-07-03 后续优化:nats-fast-writer 的 TDengine SQL 连接池已从固定 1 改为可配置:
FAST_WRITER_TDENGINE_MAX_OPEN_CONNS
FAST_WRITER_TDENGINE_MAX_IDLE_CONNS
默认 max_open=1、max_idle=1。原因是当前 TDengine 写入依赖启动时执行的 USE <database>;database/sql 连接池新建连接不会继承其他连接上的 USE 状态。实测默认跟随 worker 放大到 8 会导致 [0x200] db is not specified / [0x2616] Database not specified。后续只有在 TDengine DSN 或 writer SQL 改为每条连接都明确选库后,才能提高该连接池。
同时新增 batch pending gauge:
vehicle_fast_writer_nats_consumer_pending{stream,consumer}
vehicle_fast_writer_nats_consumer_ack_pending{stream,consumer}
vehicle_fast_writer_nats_consumer_waiting{stream,consumer}
vehicle_fast_writer_batch_pending_messages
vehicle_fast_writer_batch_pending_envelopes
vehicle_fast_writer_nats_consumer_* 表示 JetStream durable consumer 层面的 backlog/ack-pending;vehicle_fast_writer_batch_pending_* 表示 fast-writer 已拉取但尚未完成 TDengine/Redis/ack 的批内压力。带帧率压测时它们应在稳态接近 0,burst 后能够回落;如果持续非 0,需要结合 vehicle_fast_writer_stage_duration_ms_histogram 判断卡在 NATS 拉取、TDengine、Redis 还是 ack。
NATS Kafka bridge batch 指标
2026-07-03 已为 NATS -> Kafka bridge 增加 batch pending 和 duration histogram:
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 和网络。
Realtime store update histogram
2026-07-03 已为 realtime-api Redis/MySQL store update 增加 duration histogram:
vehicle_realtime_store_update_duration_ms_histogram_bucket{store,protocol,status,le}
vehicle_realtime_store_update_duration_ms_histogram_count{store,protocol,status}
vehicle_realtime_store_update_duration_ms_histogram_sum{store,protocol,status}
该耗时覆盖 Redis 快速实时投影和 MySQL 异步当前态/位置投影的实际写入耗时。带帧率压测时,Redis p99 上升会直接影响 realtime consumer 追平;MySQL p99 上升需要结合 vehicle_realtime_async_queue_depth 和 vehicle_realtime_async_queue_total{status="dropped"} 判断是否开始丢弃低优先级当前态写入。
Stat writer write histogram
2026-07-03 已为 stat-writer MySQL append 增加 duration histogram:
vehicle_stat_write_duration_ms_histogram_bucket{topic,status,le}
vehicle_stat_write_duration_ms_histogram_count{topic,status}
vehicle_stat_write_duration_ms_histogram_sum{topic,status}
该耗时覆盖单条 Kafka 消息解析后写入 vehicle_daily_mileage 的 MySQL append 路径。带帧率压测时,如果该 p99 持续上升并伴随 vehicle_stat_kafka_lag 增长,优先排查 MySQL 写入、每日里程表索引和 stat-writer 消费能力。
Gateway frame duration histogram
2026-07-03 已为 Gateway 帧处理耗时增加 histogram:
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路径作为回退。
新增指标:
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 已经在本地批写或提交阶段形成早期积压。
下次恢复上下文时先做
- 读本文件。
- 读
docs/ops/vehicle-ingest-runbook.md。 - 读
docs/architecture/production-data-plane-inventory.md。 - 执行
git status --short。 - 如果涉及生产,先查 ECS 服务健康和最近日志。