Files
lingniu-vehicle-ingest/docs/go-version-data-flow.html

752 lines
27 KiB
HTML
Raw Permalink 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.
<!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></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 topic。<code>vehicle.event.go.unified.v1</code> 仅作为显式兼容开关保留。</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。</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>消费三个 RAW 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 subjectunified 只在兼容开关打开时写。</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、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>Redis</strong></td>
<td><code>vehicle:latest:{vin}</code></td>
<td>realtime-api Kafka consumer</td>
<td>跨协议合并后的实时字段快照</td>
<td>查 VIN 是否在线、查 VIN 实时数据</td>
</tr>
<tr>
<td><strong>Redis</strong></td>
<td><code>vehicle:latest:{vin}:{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:{protocol}:{vin}</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:{vin}</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_mileage</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 latest snapshot</td><td>跨协议合并核心 fields完整协议字段通过 realtime-raw 查询。</td></tr>
<tr><td>单协议实时 RAW</td><td>Redis realtime-raw</td><td>查看某 VIN 在某协议下最新 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>高频位置分页查询,包含 total_mileage_km避免重复里程点接口。</td></tr>
<tr><td>每日里程</td><td>MySQL vehicle_daily_mileage</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 latest snapshot 只保留统一实时核心字段;完整协议 parsed 只放 realtime-raw避免重复缓存大 JSON。
</div>
<div class="ok">
<strong>身份解析边界</strong><br>
实时缓存只写 VIN 非空车辆;无 VIN 帧保留在 RAW 和注册/绑定排查链路,补齐绑定后通过新帧或回放进入实时态。
</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>