From 17024651c8338d7dd46ac4a62ca678f12aff3003 Mon Sep 17 00:00:00 2001 From: lingniu Date: Wed, 1 Jul 2026 21:55:27 +0800 Subject: [PATCH] chore: add go gateway deployment stack --- deploy/portainer/docker-compose-go.yml | 101 ++++++++++++++ .../go-vehicle-gateway-verification.md | 127 ++++++++++++++++++ go/vehicle-gateway/Dockerfile | 29 ++++ 3 files changed, 257 insertions(+) create mode 100644 deploy/portainer/docker-compose-go.yml create mode 100644 docs/operations/go-vehicle-gateway-verification.md create mode 100644 go/vehicle-gateway/Dockerfile diff --git a/deploy/portainer/docker-compose-go.yml b/deploy/portainer/docker-compose-go.yml new file mode 100644 index 00000000..78fb0d26 --- /dev/null +++ b/deploy/portainer/docker-compose-go.yml @@ -0,0 +1,101 @@ +version: "3.8" + +# Go sidecar stack for the vehicle ingest redesign. +# It is intentionally separate from deploy/portainer/docker-compose.yml so the +# current Java production stack can keep running until Go evidence is captured. + +x-common-env: &common-env + TZ: Asia/Shanghai + KAFKA_BROKERS: ${KAFKA_BROKERS:-172.17.111.56:9092} + KAFKA_TOPIC_GB32960_RAW: ${KAFKA_TOPIC_GB32960_RAW:-vehicle.raw.gb32960.v1} + KAFKA_TOPIC_JT808_RAW: ${KAFKA_TOPIC_JT808_RAW:-vehicle.raw.jt808.v1} + KAFKA_TOPIC_YUTONG_MQTT_RAW: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:-vehicle.raw.yutong-mqtt.v1} + KAFKA_TOPIC_UNIFIED: ${KAFKA_TOPIC_UNIFIED:-vehicle.event.unified.v1} + +x-restart: &restart-policy + restart: unless-stopped + networks: + - vehicle-ingest + logging: + driver: json-file + options: + max-size: "100m" + max-file: "5" + +services: + go-vehicle-gateway: + <<: *restart-policy + image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} + container_name: go-vehicle-gateway + command: ["/app/gateway"] + mem_limit: ${GO_GATEWAY_MEM_LIMIT:-512m} + environment: + <<: *common-env + GB32960_TCP_ADDR: ":32960" + JT808_TCP_ADDR: ":808" + TCP_READ_BUFFER_BYTES: ${TCP_READ_BUFFER_BYTES:-65536} + TCP_IDLE_TIMEOUT_SECONDS: ${TCP_IDLE_TIMEOUT_SECONDS:-180} + TCP_MAX_CONNECTIONS: ${TCP_MAX_CONNECTIONS:-20000} + YUTONG_MQTT_ENABLED: ${YUTONG_MQTT_ENABLED:-false} + YUTONG_MQTT_ENDPOINT: ${YUTONG_MQTT_ENDPOINT:-yutong} + YUTONG_MQTT_URI: ${YUTONG_MQTT_URI:-} + YUTONG_MQTT_TOPICS: ${YUTONG_MQTT_TOPICS:-/ytforward/shln/+} + YUTONG_MQTT_QOS: ${YUTONG_MQTT_QOS:-2} + YUTONG_MQTT_CLIENT_ID: ${YUTONG_MQTT_CLIENT_ID:-lingniu-go-yutong-mqtt} + YUTONG_MQTT_USERNAME: ${YUTONG_MQTT_USERNAME:-} + YUTONG_MQTT_PASSWORD: ${YUTONG_MQTT_PASSWORD:-} + ports: + - "${GO_GB32960_TCP_PORT:-32960}:32960" + - "${GO_JT808_TCP_PORT:-808}:808" + + go-history-writer: + <<: *restart-policy + image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} + container_name: go-history-writer + command: ["/app/history-writer"] + mem_limit: ${GO_HISTORY_WRITER_MEM_LIMIT:-768m} + environment: + <<: *common-env + KAFKA_GROUP: ${KAFKA_GROUP_GO_HISTORY:-go-history-writer} + KAFKA_TOPICS: ${KAFKA_TOPICS_GO_HISTORY:-vehicle.raw.gb32960.v1,vehicle.raw.jt808.v1,vehicle.raw.yutong-mqtt.v1} + TDENGINE_DRIVER: ${TDENGINE_DRIVER:-taosWS} + TDENGINE_DSN: ${TDENGINE_DSN:?set TDENGINE_DSN, example root:password@ws(172.17.111.57:6041)/lingniu_vehicle_ts} + TDENGINE_DATABASE: ${TDENGINE_DATABASE:-lingniu_vehicle_ts} + TDENGINE_ENSURE_SCHEMA: ${TDENGINE_ENSURE_SCHEMA:-true} + + go-stat-writer: + <<: *restart-policy + image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} + container_name: go-stat-writer + command: ["/app/stat-writer"] + mem_limit: ${GO_STAT_WRITER_MEM_LIMIT:-512m} + environment: + <<: *common-env + KAFKA_GROUP: ${KAFKA_GROUP_GO_STAT:-go-stat-writer} + KAFKA_TOPICS: ${KAFKA_TOPICS_GO_STAT:-vehicle.raw.gb32960.v1,vehicle.raw.jt808.v1} + MYSQL_DSN: ${MYSQL_DSN:?set MYSQL_DSN} + MYSQL_ENSURE_SCHEMA: ${MYSQL_ENSURE_SCHEMA:-true} + LOCAL_TZ: ${LOCAL_TZ:-Asia/Shanghai} + + go-realtime-api: + <<: *restart-policy + image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} + container_name: go-realtime-api + command: ["/app/realtime-api"] + mem_limit: ${GO_REALTIME_API_MEM_LIMIT:-512m} + environment: + <<: *common-env + HTTP_ADDR: ":20210" + KAFKA_GROUP: ${KAFKA_GROUP_GO_REALTIME:-go-realtime-api} + KAFKA_TOPICS: ${KAFKA_TOPICS_GO_REALTIME:-vehicle.event.unified.v1} + REDIS_ADDR: ${REDIS_ADDR:-r-bp1u741kij7e51i481.redis.rds.aliyuncs.com:6379} + REDIS_USERNAME: ${REDIS_USERNAME:-} + REDIS_PASSWORD: ${REDIS_PASSWORD:?set REDIS_PASSWORD} + REDIS_DB: ${REDIS_DB:-50} + ONLINE_TTL_SECONDS: ${ONLINE_TTL_SECONDS:-600} + ports: + - "${GO_REALTIME_HTTP_PORT:-20210}:20210" + +networks: + vehicle-ingest: + name: vehicle-ingest diff --git a/docs/operations/go-vehicle-gateway-verification.md b/docs/operations/go-vehicle-gateway-verification.md new file mode 100644 index 00000000..28370dc0 --- /dev/null +++ b/docs/operations/go-vehicle-gateway-verification.md @@ -0,0 +1,127 @@ +# Go Vehicle Gateway 验证手册 + +本文档用于验证 Go 重构运行面: + +- `go-vehicle-gateway`:GB32960 TCP、JT808 TCP、宇通 MQTT 接入。 +- `go-history-writer`:Kafka RAW 写 TDengine。 +- `go-stat-writer`:Kafka RAW 写 MySQL 每日指标。 +- `go-realtime-api`:Kafka unified event 写 Redis,并提供实时查询 API。 + +## 本地构建验证 + +```bash +cd go/vehicle-gateway +go test ./... +go build ./cmd/gateway +go build ./cmd/history-writer +go build ./cmd/stat-writer +go build ./cmd/realtime-api +``` + +## 镜像构建 + +```bash +cd go/vehicle-gateway +docker build -t vehicle-gateway-go:local . +``` + +同一个镜像包含四个二进制: + +- `/app/gateway` +- `/app/history-writer` +- `/app/stat-writer` +- `/app/realtime-api` + +## Portainer 部署 + +Go 旁路 stack 文件: + +```bash +deploy/portainer/docker-compose-go.yml +``` + +必填变量: + +```bash +LINGNIU_GO_IMAGE_VERSION= +KAFKA_BROKERS=172.17.111.56:9092 +TDENGINE_DSN=root:@ws(172.17.111.57:6041)/lingniu_vehicle_ts +MYSQL_DSN=lingniu_vehicle:@tcp(rm-bp179zbv481rnw3e2.mysql.rds.aliyuncs.com:3306)/lingniu_vehicle_data?parseTime=true&charset=utf8mb4,utf8&loc=Asia%2FShanghai +REDIS_ADDR=r-bp1u741kij7e51i481.redis.rds.aliyuncs.com:6379 +REDIS_PASSWORD= +REDIS_DB=50 +``` + +宇通 MQTT 开启时再配置: + +```bash +YUTONG_MQTT_ENABLED=true +YUTONG_MQTT_URI= +YUTONG_MQTT_TOPICS=/ytforward/shln/+ +YUTONG_MQTT_CLIENT_ID=lingniu-go-yutong-mqtt +YUTONG_MQTT_USERNAME= +YUTONG_MQTT_PASSWORD= +``` + +## ECS 进程检查 + +```bash +docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' \ + | egrep 'go-vehicle-gateway|go-history-writer|go-stat-writer|go-realtime-api|NAMES' + +ss -lntp | egrep ':(808|32960|20210)\b' +``` + +## Kafka 验证 + +```bash +kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ + --topic vehicle.raw.jt808.v1 --max-messages 1 --timeout-ms 10000 + +kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ + --topic vehicle.raw.gb32960.v1 --max-messages 1 --timeout-ms 10000 + +kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ + --topic vehicle.event.unified.v1 --max-messages 1 --timeout-ms 10000 +``` + +## TDengine 验证 + +```sql +USE lingniu_vehicle_ts; +SHOW STABLES; +SELECT COUNT(*) FROM raw_frames; +SELECT COUNT(*) FROM vehicle_locations; +SELECT COUNT(*) FROM vehicle_mileage_points; +``` + +## MySQL 验证 + +```sql +SELECT protocol, metric_key, COUNT(*) +FROM vehicle_daily_metric +GROUP BY protocol, metric_key; + +SELECT * +FROM vehicle_daily_metric +WHERE metric_key IN ('daily_mileage_km', 'daily_total_mileage_km') +ORDER BY updated_at DESC +LIMIT 20; +``` + +## Redis 实时查询 + +```bash +curl -sS 'http://115.29.187.205:20210/api/realtime/vehicles/' +curl -sS 'http://115.29.187.205:20210/api/realtime/vehicles//online' +curl -sS 'http://115.29.187.205:20210/api/realtime/vehicles//protocols/JT808' +``` + +## 验收证据 + +正式切换前至少记录: + +- 一个真实 GB32960 VIN 的 RAW、location、mileage point、Redis snapshot。 +- 一个真实 JT808 VIN/phone 的 RAW、location、mileage point、daily metric、Redis snapshot。 +- 一个真实宇通 MQTT VIN 或 device_id 的 RAW、Redis snapshot。 +- `go-history-writer` 和 `go-stat-writer` 重启后 Kafka offset 能继续推进。 diff --git a/go/vehicle-gateway/Dockerfile b/go/vehicle-gateway/Dockerfile new file mode 100644 index 00000000..3e923865 --- /dev/null +++ b/go/vehicle-gateway/Dockerfile @@ -0,0 +1,29 @@ +FROM golang:1.26-bookworm AS build + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/gateway ./cmd/gateway \ + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/history-writer ./cmd/history-writer \ + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/stat-writer ./cmd/stat-writer \ + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/realtime-api ./cmd/realtime-api + +FROM debian:bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY --from=build /out/gateway /app/gateway +COPY --from=build /out/history-writer /app/history-writer +COPY --from=build /out/stat-writer /app/stat-writer +COPY --from=build /out/realtime-api /app/realtime-api + +ENV TZ=Asia/Shanghai + +EXPOSE 808 32960 20210 + +CMD ["/app/gateway"]