docs: add go data flow diagram

This commit is contained in:
lingniu
2026-07-02 15:48:14 +08:00
parent c27150cc15
commit f3b4cbbbb2

View File

@@ -0,0 +1,759 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lingniu Vehicle Ingest Go Version Data Flow</title>
<style>
:root {
--bg: #f6f7f9;
--ink: #17202a;
--muted: #5f6b7a;
--line: #ccd3dd;
--panel: #ffffff;
--blue: #1f6feb;
--green: #16803c;
--amber: #a15c00;
--red: #c23131;
--purple: #7c3aed;
--teal: #087f8c;
--shadow: 0 10px 30px rgba(25, 33, 45, .08);
}
* { box-sizing: border-box; }
body {
margin: 0;
color: var(--ink);
background: var(--bg);
font: 14px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
}
header {
padding: 28px 34px 22px;
background: #111827;
color: white;
}
h1 {
margin: 0 0 8px;
font-size: 28px;
line-height: 1.2;
letter-spacing: 0;
}
.subtitle {
max-width: 1180px;
color: #d1d5db;
font-size: 15px;
}
main {
width: min(1480px, calc(100vw - 36px));
margin: 22px auto 48px;
}
h2 {
margin: 0 0 14px;
font-size: 20px;
line-height: 1.25;
}
h3 {
margin: 0 0 10px;
font-size: 15px;
}
.section {
margin: 18px 0;
padding: 20px;
background: var(--panel);
border: 1px solid #e4e8ef;
border-radius: 8px;
box-shadow: var(--shadow);
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 16px;
}
.tag {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 4px 8px;
border: 1px solid #d8dee8;
border-radius: 6px;
background: #fff;
color: var(--muted);
font-size: 12px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--blue);
}
.dot.source { background: var(--amber); }
.dot.gateway { background: var(--blue); }
.dot.bus { background: var(--purple); }
.dot.store { background: var(--green); }
.dot.api { background: var(--teal); }
.dot.warning { background: var(--red); }
.flow {
display: grid;
grid-template-columns: 1.15fr 1.35fr 1.15fr 1.2fr 1.4fr 1.1fr;
gap: 14px;
align-items: stretch;
overflow-x: auto;
padding-bottom: 8px;
}
.lane {
min-width: 190px;
display: flex;
flex-direction: column;
gap: 12px;
}
.lane-title {
padding: 8px 10px;
border-radius: 6px;
color: white;
font-weight: 700;
text-align: center;
background: #374151;
}
.node {
position: relative;
min-height: 88px;
padding: 13px 13px 12px;
border: 1px solid #d9e0ea;
border-left: 5px solid var(--blue);
border-radius: 8px;
background: #fff;
}
.node.source { border-left-color: var(--amber); }
.node.gateway { border-left-color: var(--blue); }
.node.bus { border-left-color: var(--purple); }
.node.store { border-left-color: var(--green); }
.node.api { border-left-color: var(--teal); }
.node.warning { border-left-color: var(--red); }
.node strong {
display: block;
margin-bottom: 5px;
font-size: 14px;
}
.node p {
margin: 0;
color: var(--muted);
font-size: 12px;
}
.arrow {
display: flex;
align-items: center;
justify-content: center;
height: 28px;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}
.arrow:before, .arrow:after {
content: "";
height: 1px;
background: var(--line);
flex: 1;
margin: 0 8px;
}
.arrow:after {
max-width: 16px;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 8px solid var(--line);
background: transparent;
margin-left: 0;
}
.grid-2 {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
table {
width: 100%;
border-collapse: collapse;
overflow: hidden;
border: 1px solid #e1e6ef;
border-radius: 8px;
background: white;
}
th, td {
padding: 10px 11px;
border-bottom: 1px solid #edf1f6;
text-align: left;
vertical-align: top;
font-size: 13px;
}
th {
background: #f0f4f8;
color: #263241;
font-weight: 700;
}
tr:last-child td { border-bottom: 0; }
code {
padding: 1px 5px;
border-radius: 4px;
background: #eef2f7;
color: #1f2937;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
}
.pill {
display: inline-block;
margin: 2px 4px 2px 0;
padding: 2px 7px;
border-radius: 999px;
background: #eef2ff;
color: #3730a3;
font-size: 12px;
white-space: nowrap;
}
.note {
padding: 11px 12px;
border-left: 4px solid var(--amber);
background: #fff7ed;
color: #61410b;
border-radius: 6px;
}
.ok {
padding: 11px 12px;
border-left: 4px solid var(--green);
background: #effaf1;
color: #164b25;
border-radius: 6px;
}
.mini-flow {
display: grid;
grid-template-columns: repeat(7, minmax(128px, 1fr));
gap: 8px;
overflow-x: auto;
padding-bottom: 8px;
}
.mini-step {
min-height: 82px;
padding: 10px;
border: 1px solid #dfe6f1;
border-radius: 8px;
background: #fff;
}
.mini-step b {
display: block;
margin-bottom: 4px;
color: #111827;
font-size: 13px;
}
.mini-step span {
color: var(--muted);
font-size: 12px;
}
.footer {
color: var(--muted);
font-size: 12px;
text-align: center;
padding: 20px;
}
@media (max-width: 980px) {
.grid-2, .grid-3 { grid-template-columns: 1fr; }
header { padding: 22px 18px; }
main { width: calc(100vw - 20px); }
.section { padding: 14px; }
}
</style>
</head>
<body>
<header>
<h1>Go 版本车辆数据处理与流转全图</h1>
<div class="subtitle">
当前 Go 架构以 gateway 做高性能协议接入NATS JetStream 做入口缓冲和解耦nats-kafka-bridge 保持现有 Kafka 下游兼容,最终进入 TDengine、Redis、MySQL并由 realtime-api 对外提供查询能力。
</div>
<div class="legend">
<span class="tag"><span class="dot source"></span>外部数据源</span>
<span class="tag"><span class="dot gateway"></span>Go 接入与解析</span>
<span class="tag"><span class="dot bus"></span>NATS / Kafka 解耦</span>
<span class="tag"><span class="dot store"></span>存储</span>
<span class="tag"><span class="dot api"></span>查询 API</span>
<span class="tag"><span class="dot warning"></span>可靠性边界</span>
</div>
</header>
<main>
<section class="section">
<h2>1. 总览图:从车辆平台到 API</h2>
<div class="flow">
<div class="lane">
<div class="lane-title">外部输入</div>
<div class="node source">
<strong>GB/T 32960 平台</strong>
<p>TCP 连接到 ECS <code>:32960</code>,包含登录、登出、实时信息、补发等帧。</p>
</div>
<div class="node source">
<strong>JT/T 808 平台</strong>
<p>TCP 连接到 ECS <code>:808</code>,包含注册、鉴权、位置上报 <code>0x0200</code> 等。</p>
</div>
<div class="node source">
<strong>宇通 MQTT</strong>
<p>gateway 作为 MQTT client 订阅 <code>/ytforward/shln/+</code></p>
</div>
</div>
<div class="lane">
<div class="lane-title">gateway 接入层</div>
<div class="node gateway">
<strong>TCPServer / MQTTClient</strong>
<p>读取连接、拆包、处理半包/粘包、按协议路由到解析器。</p>
</div>
<div class="node gateway">
<strong>协议解析器</strong>
<p><code>gb32960</code><code>jt808</code><code>yutongmqtt</code> 解析 RAW抽取核心 fields。</p>
</div>
<div class="node gateway">
<strong>身份解析</strong>
<p>通过 MySQL <code>vehicle_identity_binding</code> 将 phone/device/plate 尽量映射到 VIN。</p>
</div>
<div class="node gateway">
<strong>协议响应</strong>
<p>32960/808 需要应答时,在成功发布后写 ACK。</p>
</div>
</div>
<div class="lane">
<div class="lane-title">统一消息</div>
<div class="node gateway">
<strong>FrameEnvelope</strong>
<p>统一封装protocol、message_id、vin、phone、device_id、source_endpoint、raw、parsed、fields。</p>
</div>
<div class="node warning">
<strong>StableEventID</strong>
<p>基于协议、消息、车辆键、序号、时间、raw 生成稳定 ID用于去重和追踪。</p>
</div>
</div>
<div class="lane">
<div class="lane-title">入口解耦</div>
<div class="node bus">
<strong>NATS JetStream</strong>
<p>gateway 发布到 <code>VEHICLE_INGEST</code> stream文件存储24 小时保留。</p>
</div>
<div class="node bus">
<strong>Subjects</strong>
<p><code>vehicle.raw.go.gb32960.v1</code><br><code>vehicle.raw.go.jt808.v1</code><br><code>vehicle.raw.go.yutong-mqtt.v1</code><br><code>vehicle.event.go.unified.v1</code></p>
</div>
<div class="node warning">
<strong>Async + Retry</strong>
<p>gateway 内部异步队列发布 NATS降低协议连接被 Kafka 慢写拖住的风险。</p>
</div>
</div>
<div class="lane">
<div class="lane-title">Kafka 兼容层</div>
<div class="node bus">
<strong>nats-kafka-bridge</strong>
<p>durable pull consumer 批量拉 NATS写 Kafka 成功后才 ACK NATS。</p>
</div>
<div class="node bus">
<strong>Kafka Topics</strong>
<p>与 NATS subject 同名raw 三个协议 + unified 一个实时主题。</p>
</div>
<div class="node warning">
<strong>失败语义</strong>
<p>Kafka 写失败不 ACKNATS 保留消息等待重试;未知 subject 不 ACK防止静默丢数据。</p>
</div>
</div>
<div class="lane">
<div class="lane-title">落库与查询</div>
<div class="node store">
<strong>history-writer</strong>
<p>消费 RAW topic写 TDengineraw_frames、payload_chunks、locations、mileage_points。</p>
</div>
<div class="node store">
<strong>stat-writer</strong>
<p>消费 RAW topic根据总里程差值写 MySQL 每日指标。</p>
</div>
<div class="node store">
<strong>realtime-api 消费器</strong>
<p>消费 unified topic写 Redis 在线状态、实时合并快照、各协议 realtime-raw。</p>
</div>
<div class="node api">
<strong>HTTP API</strong>
<p>realtime-api 暴露实时、历史、统计查询接口,端口 <code>:20200</code></p>
</div>
</div>
</div>
</section>
<section class="section">
<h2>2. 单条报文的完整生命周期</h2>
<div class="mini-flow">
<div class="mini-step"><b>1. 到达</b><span>32960/808 TCP 报文或 MQTT 消息到 gateway。</span></div>
<div class="mini-step"><b>2. 拆包</b><span>TCP 按协议提取完整帧MQTT 直接按消息处理。</span></div>
<div class="mini-step"><b>3. 解析</b><span>生成 parsed 全量结构化数据和 fields 核心字段。</span></div>
<div class="mini-step"><b>4. 绑定 VIN</b><span>根据 VIN/phone/device_id/plate 生成 vehicle_key。</span></div>
<div class="mini-step"><b>5. 发 NATS</b><span>写 RAW subject同时写 unified subject。</span></div>
<div class="mini-step"><b>6. 桥接 Kafka</b><span>bridge 写 Kafka 成功后 ACK NATS。</span></div>
<div class="mini-step"><b>7. 多路消费</b><span>历史、实时、统计各自消费 Kafka互不阻塞。</span></div>
</div>
</section>
<section class="section">
<h2>3. FrameEnvelopeGo 版本内部统一数据格式</h2>
<div class="grid-2">
<div>
<table>
<thead>
<tr><th>字段</th><th>含义</th><th>来源</th></tr>
</thead>
<tbody>
<tr><td><code>event_id</code></td><td>稳定事件 ID</td><td>已有则保留,否则按协议/消息/车辆/时间/raw 计算</td></tr>
<tr><td><code>protocol</code></td><td>协议</td><td><code>GB32960</code><code>JT808</code><code>YUTONG_MQTT</code></td></tr>
<tr><td><code>message_id</code></td><td>协议消息类型</td><td>如 808 <code>0x0200</code>32960 命令标识等</td></tr>
<tr><td><code>vin</code></td><td>VIN</td><td>协议原始字段或 MySQL binding 反查</td></tr>
<tr><td><code>phone</code></td><td>808 终端手机号</td><td>808 消息头 BCD 解析后去前导 0</td></tr>
<tr><td><code>device_id</code></td><td>设备标识</td><td>协议内设备号、MQTT 设备字段等</td></tr>
<tr><td><code>plate</code></td><td>车牌</td><td>注册帧或 binding 表</td></tr>
<tr><td><code>source_endpoint</code></td><td>来源地址</td><td>TCP remote ip:port 或 MQTT endpoint/topic</td></tr>
</tbody>
</table>
</div>
<div>
<table>
<thead>
<tr><th>字段</th><th>含义</th><th>去向</th></tr>
</thead>
<tbody>
<tr><td><code>raw_hex</code> / <code>raw_text</code></td><td>原始报文</td><td>TDengine <code>raw_frames</code>,超长进入 chunks</td></tr>
<tr><td><code>parsed</code></td><td>全量解析 JSON</td><td>TDengine <code>parsed_json</code>Redis <code>realtime-raw</code></td></tr>
<tr><td><code>fields</code></td><td>最小核心字段</td><td>TDengine 位置/里程点Redis 合并快照;统计计算</td></tr>
<tr><td><code>event_time_ms</code></td><td>车端事件时间</td><td>TDengine 主时间Redis field time</td></tr>
<tr><td><code>received_at_ms</code></td><td>平台接收时间</td><td>延迟分析、在线状态 last_seen</td></tr>
<tr><td><code>parse_status</code></td><td>解析状态</td><td><code>OK</code><code>PARTIAL</code><code>BAD_FRAME</code></td></tr>
<tr><td><code>parse_error</code></td><td>解析错误</td><td>RAW 查询排查使用</td></tr>
<tr><td><code>vehicle_key</code></td><td>车辆主键</td><td>优先 VIN否则 <code>PROTOCOL:phone/device</code></td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="section">
<h2>4. 三个协议的处理差异</h2>
<table>
<thead>
<tr>
<th>协议</th>
<th>接入方式</th>
<th>身份主线</th>
<th>RAW 数据</th>
<th>核心 fields</th>
<th>特殊动作</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>GB32960</strong></td>
<td>gateway TCP <code>:32960</code></td>
<td>报文本身通常有 VIN平台账号用于登录鉴权</td>
<td>完整 parsed JSON 写 <code>raw_frames.parsed_json</code></td>
<td>速度、经纬度、SOC、累计里程等按帧类型抽取</td>
<td>登录/数据帧需要平台应答,成功发布后 ACK</td>
</tr>
<tr>
<td><strong>JT808</strong></td>
<td>gateway TCP <code>:808</code></td>
<td>消息头 phone 为主;注册/鉴权/位置帧结合 binding 找 VIN</td>
<td>完整 parsed JSON 写 <code>raw_frames.parsed_json</code></td>
<td>位置、速度、方向、状态、报警、GPS 总里程</td>
<td>注册/鉴权/通用应答;每日里程按总里程差值统计</td>
</tr>
<tr>
<td><strong>YUTONG_MQTT</strong></td>
<td>gateway MQTT client 订阅</td>
<td>MQTT payload 中 VIN/设备字段source endpoint 为 MQTT endpoint/topic</td>
<td>完整 parsed JSON 写 <code>raw_frames.parsed_json</code></td>
<td>速度、经纬度、总里程、SOC 等</td>
<td>不需要 TCP ACK实时多帧在 Redis 按协议合并</td>
</tr>
</tbody>
</table>
</section>
<section class="section">
<h2>5. NATS + Kafka 的职责分工</h2>
<div class="grid-3">
<div class="node bus">
<strong>NATS JetStream入口缓冲层</strong>
<p>部署在 Kafka ECSDocker Compose 管理;监听内网 <code>172.17.111.56:4222</code>,监控 <code>172.17.111.56:8222</code>。gateway 只需要把事件快速、可靠地写入 NATS。</p>
</div>
<div class="node bus">
<strong>nats-kafka-bridge可靠桥接层</strong>
<p>使用 durable pull consumer批量写 Kafka只有 Kafka 写入成功才 ACK NATS。Kafka 短暂异常时NATS 消息保留并重试。</p>
</div>
<div class="node bus">
<strong>Kafka业务消费层</strong>
<p>保持现有 topic 和消费者模型history/realtime/stat 不直接依赖 gateway也不直接依赖 NATS API。</p>
</div>
</div>
<div class="note" style="margin-top:14px;">
当前阶段没有让下游直接消费 NATS这是为了最小化切换风险入口换成 NATS 后,下游仍沿用 Kafka。后续如果要进一步实时化可以让实时缓存或控制命令直接使用 NATS subject。
</div>
</section>
<section class="section">
<h2>6. 存储模型:哪些数据进入哪里</h2>
<table>
<thead>
<tr>
<th>存储</th>
<th>表/Key</th>
<th>写入方</th>
<th>内容</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>TDengine</strong></td>
<td><code>raw_frames</code></td>
<td>history-writer</td>
<td>原始报文、完整 parsed JSON、fields JSON、parse status、source endpoint</td>
<td>RAW 查询、追溯、排查解析问题</td>
</tr>
<tr>
<td><strong>TDengine</strong></td>
<td><code>raw_frame_payload_chunks</code></td>
<td>history-writer</td>
<td>超长 raw/parsed payload 分片</td>
<td>避免 BINARY 长度限制导致完整 JSON 丢失</td>
</tr>
<tr>
<td><strong>TDengine</strong></td>
<td><code>vehicle_locations</code></td>
<td>history-writer</td>
<td>经纬度、速度、方向、状态、报警、总里程等核心字段</td>
<td>高频历史位置分页查询</td>
</tr>
<tr>
<td><strong>TDengine</strong></td>
<td><code>vehicle_mileage_points</code></td>
<td>history-writer</td>
<td>累计里程点、速度、经纬度</td>
<td>里程曲线、区间差值分析</td>
</tr>
<tr>
<td><strong>Redis</strong></td>
<td><code>vehicle:realtime:{vehicle_key}</code></td>
<td>realtime-api Kafka consumer</td>
<td>跨协议合并后的实时字段快照</td>
<td>查 VIN 是否在线、查 VIN 实时数据</td>
</tr>
<tr>
<td><strong>Redis</strong></td>
<td><code>vehicle:realtime:{vehicle_key}:{protocol}</code></td>
<td>realtime-api Kafka consumer</td>
<td>单协议实时快照</td>
<td>区分 32960/808/MQTT 的实时状态</td>
</tr>
<tr>
<td><strong>Redis</strong></td>
<td><code>vehicle:realtime-raw:{vehicle_key}:{protocol}</code></td>
<td>realtime-api Kafka consumer</td>
<td>单协议最新 parsed 全量字段</td>
<td>实时 RAW 字段查看,不走 TDengine 历史扫描</td>
</tr>
<tr>
<td><strong>Redis</strong></td>
<td><code>vehicle:online:{vehicle_key}</code><code>vehicle:last_seen</code></td>
<td>realtime-api Kafka consumer</td>
<td>在线状态、最后接收时间、协议列表</td>
<td>在线判断和近期车辆列表</td>
</tr>
<tr>
<td><strong>MySQL</strong></td>
<td><code>vehicle_identity_binding</code></td>
<td>人工/导入维护gateway 读取</td>
<td>phone/device/plate 到 VIN 的映射</td>
<td>808 等缺 VIN 协议反查正确 VIN</td>
</tr>
<tr>
<td><strong>MySQL</strong></td>
<td><code>jt808_registration</code></td>
<td>gateway</td>
<td>808 注册、鉴权、首次/最新上报、phone、device、plate、vin 解析结果</td>
<td>定位哪些手机号没有映射 VIN排查注册鉴权</td>
</tr>
<tr>
<td><strong>MySQL</strong></td>
<td><code>vehicle_daily_metric</code></td>
<td>stat-writer</td>
<td>每日里程等指标,按总里程差值计算</td>
<td>统计查询</td>
</tr>
</tbody>
</table>
</section>
<section class="section">
<h2>7. 查询入口</h2>
<div class="grid-2">
<div>
<h3>实时查询</h3>
<table>
<thead><tr><th>接口类型</th><th>数据源</th><th>说明</th></tr></thead>
<tbody>
<tr><td>VIN 是否在线</td><td>Redis online key</td><td>TTL 内有数据即在线,默认 TTL 600 秒。</td></tr>
<tr><td>VIN 实时数据</td><td>Redis merged snapshot</td><td>跨协议合并 fields并保留各协议 ProtocolData。</td></tr>
<tr><td>单协议实时 RAW</td><td>Redis realtime-raw</td><td>查看某 VIN/phone 在某协议下最新 parsed 全量字段。</td></tr>
</tbody>
</table>
</div>
<div>
<h3>历史查询</h3>
<table>
<thead><tr><th>接口类型</th><th>数据源</th><th>说明</th></tr></thead>
<tbody>
<tr><td>RAW 帧查询</td><td>TDengine raw_frames + chunks</td><td>按协议、VIN/phone、时间、消息类型分页。</td></tr>
<tr><td>位置历史</td><td>TDengine vehicle_locations</td><td>高频位置分页查询,避免每次扫完整 JSON。</td></tr>
<tr><td>里程点</td><td>TDengine vehicle_mileage_points</td><td>区间里程、曲线、异常总里程排查。</td></tr>
<tr><td>每日指标</td><td>MySQL vehicle_daily_metric</td><td>按日期、协议、指标查询统计结果。</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="section">
<h2>8. 生产部署视图</h2>
<table>
<thead>
<tr><th>节点</th><th>组件</th><th>地址/端口</th><th>说明</th></tr>
</thead>
<tbody>
<tr>
<td>Gateway ECS</td>
<td><code>lingniu-go-gateway</code></td>
<td>公网 <code>115.29.187.205</code>;生产端口 <code>32960</code><code>808</code></td>
<td>承接外部平台真实连接,发布到 NATS。</td>
</tr>
<tr>
<td>Gateway ECS</td>
<td><code>lingniu-go-nats-kafka-bridge</code></td>
<td>连接 NATS <code>172.17.111.56:4222</code>Kafka <code>172.17.111.56:9092</code></td>
<td>当前部署在 gateway ECS负责 NATS 到 Kafka 桥接。</td>
</tr>
<tr>
<td>Gateway ECS</td>
<td><code>history-writer</code><code>stat-writer</code><code>realtime-api</code></td>
<td>API <code>115.29.187.205:20200</code></td>
<td>消费 Kafka写 TDengine/Redis/MySQL并提供查询。</td>
</tr>
<tr>
<td>Kafka ECS</td>
<td>Kafka + NATS</td>
<td>公网 <code>114.55.58.251</code>;内网 <code>172.17.111.56</code></td>
<td>NATS 使用 Docker Compose端口只绑定内网 <code>4222</code>/<code>8222</code></td>
</tr>
<tr>
<td>TDengine ECS</td>
<td>TDengine</td>
<td>内网 <code>172.17.111.57:6041</code></td>
<td>历史 RAW、位置、里程点时序存储。</td>
</tr>
<tr>
<td>云服务</td>
<td>MySQL / Redis</td>
<td>RDS 内网、Redis 内网</td>
<td>身份绑定、808 注册、每日指标、实时缓存。</td>
</tr>
</tbody>
</table>
</section>
<section class="section">
<h2>9. 关键可靠性设计</h2>
<div class="grid-3">
<div class="ok">
<strong>接入不直接等待 Kafka</strong><br>
gateway 先写 NATSKafka 慢写由 bridge 消化,降低 32960/808 连接积压风险。
</div>
<div class="ok">
<strong>NATS ACK 在 Kafka 成功之后</strong><br>
bridge 写 Kafka 失败时不 ACK消息仍在 JetStream恢复后继续拉取。
</div>
<div class="ok">
<strong>RAW 完整 JSON 保留</strong><br>
<code>parsed</code> 全量写 RAW核心表只存查询常用字段避免位置查询每次扫完整 JSON。
</div>
<div class="ok">
<strong>实时多协议合并</strong><br>
Redis 同时保留 merged snapshot、protocol snapshot、realtime-raw既能看统一实时也能追单协议原始字段。
</div>
<div class="ok">
<strong>身份解析降级</strong><br>
VIN 优先,其次 phone、vehicle_key_hint、device_id保证无 VIN 时仍可临时归档和查询。
</div>
<div class="ok">
<strong>数据分层查询</strong><br>
高频查询走 TDengine 核心字段表和 Redis完整追溯走 RAW JSON性能和完整性分开处理。
</div>
</div>
</section>
<section class="section">
<h2>10. 当前运行链路一句话</h2>
<p>
外部 32960/808/MQTT 数据进入 Go gateway 后,被解析成 <code>FrameEnvelope</code>,先写入 NATS JetStream
<code>nats-kafka-bridge</code> 把 NATS 消息可靠转写到 KafkaKafka 再分发给 <code>history-writer</code> 写 TDengine、
<code>stat-writer</code> 写 MySQL 每日指标、<code>realtime-api</code> 写 Redis 实时缓存并提供 HTTP 查询。
</p>
</section>
</main>
<div class="footer">
Generated for lingniu-vehicle-ingest Go architecture. Update this file when gateway subjects, storage tables, or deployment topology changes.
</div>
</body>
</html>