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

15 KiB
Raw Blame History

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

服务和端口

ECS115.29.187.205

服务 systemd 端口 作用
Gateway lingniu-go-gateway.service 8083296020211 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

业务端口:

  • JT8080.0.0.0:808
  • GB329600.0.0.0:32960
  • Swagger/APIhttp://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
  • NATS172.17.111.56:4222

TDengine

  • 内网:172.17.111.57
  • WS172.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
  • DB50

不要在最终回答中明文输出生产密码。

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_fields
    • parsed_field_types
  • Gateway 在发布 raw 前调用 realtime.EnsureParsedFields(&env)
  • TDengine writer、Redis realtime KV、MySQL snapshot 优先使用 env.ParsedFields
  • 旧消息没有 parsed_fields 时,保留 fallbackParsed 展开一次。

相关文件:

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 bytesgd_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

处理规则:

  1. 删除原有 G7/G7s 的 phone/oem
  2. 使用文件中的 手机号 列更新。
  3. 冲突的不更新,其他更新。

执行结果:

  • 清空旧 G7/G7s666 行。
  • 更新安全 G7s561 行。
  • 跳过:72 行。
    • 缺 plate 或 VIN14
    • 与非 G7/G7s 绑定冲突:58

跳过清单:

outputs/g7s_binding_skipped_apply.csv

JT808 unknown VIN 导出

导出条件:

  • jt808_registration.vin 为空、unknownunknow
  • 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/20200 readyz 均为 ok。
  • 808329602020020211-20214 监听 backlog 均显示 65535
  • Gateway 重启后持续接收 GB32960、JT808、Yutong MQTT 帧。
  • 重启完成后窗口未发现 gateway/realtime-api 持续 error/fatal 日志。

10W 阶段压测进展

2026-07-03 已完成 ECS 本机 JT808 hold-only 阶段压测,不发送业务帧:

阶段 结果
100 连接 / 60s opened 100failed 0frames 0write_errors 0
1,000 连接 / 60s opened 1000failed 0frames 0write_errors 0
10,000 连接 / 120s opened 10000failed 0frames 0write_errors 0
50,000 连接 / 180s opened 50000failed 0frames 0write_errors 0
100,000 总连接 / 180s JT808 opened 50000、GB32960 opened 50000failed 0frames 0write_errors 0

1W 阶段峰值约 10240 JT808 active connectionsgateway FD 约 10256NATS ack pending 为 0,压测后连接回落到生产基线约 240readyz 全部 OK。

5W 阶段峰值约 50238 JT808 active connectionsgateway FD 约 50254available memory 约 5590 MB。NATS ack pending 中间短时到 2918:16:49 CST 回到 0history/stat/realtime Kafka lag 总和均为 0。压测后 JT808 active 回落到生产基线约 238-240readyz 全部 OK。

10W 总连接阶段使用双端口 loopbackJT808 50000 + GB32960 50000,均 -send=false。峰值约 100238 active connectionsgateway FD 约 100252available memory 约 4511 MB。NATS ack pending 中间短时到 1018:23:02 CST 回到 0history/stat/realtime Kafka lag 总和均为 0。压测后 JT808 回落到约 236GB32960 回落到约 2readyz 全部 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_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

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

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=200HISTORY_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_lagvehicle_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 服务健康和最近日志。