docs: add detailed 32960 pipeline comments

This commit is contained in:
kkfluous
2026-06-23 13:17:37 +08:00
parent ba68ffe061
commit a096e4ce0e
125 changed files with 493 additions and 14 deletions

View File

@@ -8,15 +8,23 @@ import java.lang.annotation.Target;
/**
* 批量异步处理:框架把多次同类调用聚合成 {@code List} 交给方法。
*
* <p>处理方法签名需 {@code void foo(List<T> list)} 或返回 {@code List<VehicleEvent>}
* <p>处理方法签名需接收 {@code List<T>}返回 {@code List<VehicleEvent>} 或单个
* {@code VehicleEvent}。Dispatcher 不直接调用目标方法,而是交给
* {@code AsyncBatchExecutor} 按 size/waitMs 聚合后调用。
*
* <p>注意:当调用方需要给每条事件补不同的 rawArchiveUri 等逐条元数据时,执行器会按单条 flush
* 以保证事件与原始帧一一对应。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AsyncBatch {
/** 达到该批量大小后立即 flush。 */
int size() default 1000;
/** 第一条消息入队后最多等待毫秒数,避免低流量车辆长期不出批。 */
long waitMs() default 500;
/** 每个 Handler 方法对应的批处理 worker 数。 */
int poolSize() default 2;
}

View File

@@ -9,6 +9,8 @@ import java.lang.annotation.Target;
* 声明幂等键,由 Dedup 拦截器使用。支持 SpEL 表达式,上下文根对象为 Handler 参数。
*
* <p>示例:{@code @IdempotentKey("#msg.vin + ':' + #msg.seq + ':' + #msg.eventTime")}
*
* <p>当前内置去重主要基于 RawFrame sourceMeta/raw bytes该注解保留给更细粒度 Handler 级去重。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)

View File

@@ -14,14 +14,20 @@ import java.lang.annotation.Target;
* <li>JT/T 808{@code command} = 消息 ID0x0100 / 0x0200 / ...{@code infoType} 留空
* <li>MQTT{@code command} 可为 topic hash 或忽略
* </ul>
*
* <p>同一个方法可以声明多个 command/infoType。Dispatcher 会按协议、command、infoType 精确路由;
* 32960 实时上报解析后通常由 0x02/0x03 主命令加信息体 ID 决定落到哪个 Handler。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MessageMapping {
/** 协议主命令或消息 ID空数组表示该维度不限制。 */
int[] command() default {};
/** 协议子类型GB32960 中对应信息体 ID空数组表示该维度不限制。 */
int[] infoType() default {};
/** 仅用于日志/Swagger/排障展示,不参与路由。 */
String desc() default "";
}

View File

@@ -6,7 +6,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 单 VIN 速率限制。超限消息直接进 DLQ 并打点
* 单 VIN 速率限制声明
*
* <p>当前内置限流器在 Pipeline before 阶段按 VIN 中止处理,超限帧不会进入 Handler 和下游存储。
* 如果需要把超限帧写入 DLQ应在拦截器或入口层显式增加对应 sink。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)

View File

@@ -33,6 +33,8 @@ public final class EnvelopeConsumerProcessor {
throw new IllegalArgumentException("record must not be null");
}
EnvelopeIngestResult result = ingestor.tryIngest(record.payload());
// 派生消费者不在这里抛出业务异常给 Kafka worker坏消息统一进入 DLQ
// worker 看到 process 正常返回后才能提交 offset避免同一坏消息无限阻塞消费组。
if (DEAD_LETTER_STATUSES.contains(result.status())) {
deadLetterSink.publish(toDeadLetter(record, result));
}

View File

@@ -9,6 +9,10 @@ import java.util.Map;
/**
* Shared raw-archive key conventions used by Dispatcher, archive sink, and query services.
*
* <p>32960 生产链路里RAW .bin 文件本体按 {@code 日期/协议/VIN/eventId.bin} 落在 archive 根目录,
* DuckDB 只保存 {@code archive://...} 引用和查询索引。这个类集中维护 key 规则,避免接收端、落盘端、
* snapshot 查询端对目录分区的理解不一致。</p>
*/
public final class RawArchiveKeys {
@@ -23,6 +27,7 @@ public final class RawArchiveKeys {
}
public static String key(Instant ingestTime, ProtocolId source, String vin, String eventId) {
// 分区日期使用 ingestTime 的东八区自然日和磁盘目录保持一致eventTime 只代表车端上报时间。
String dateKey = DATE_KEY.format(ingestTime == null ? Instant.EPOCH : ingestTime);
String safeVin = vin == null || vin.isBlank() ? "unknown-vin" : vin;
String safeEventId = eventId == null || eventId.isBlank()

View File

@@ -5,6 +5,10 @@ package com.lingniu.ingest.api.event;
*
* <p>Protocol mappers use stable internal field keys so Kafka, Parquet, Redis,
* and statistics do not depend on protocol-specific names.
*
* <p>{@code value} 统一用字符串承载,真实类型由 {@code valueType} 表达。这样 Kafka
* protobuf、CSV 导出、DuckDB JSON 字段和前端展示可以共用同一份字段结构;数值精度/格式化
* 由查询层或前端按 {@code valueType + unit} 决定。
*/
public record TelemetryFieldValue(
String key,
@@ -24,6 +28,7 @@ public record TelemetryFieldValue(
}
value = value == null ? "" : value;
unit = unit == null ? "" : unit;
// 默认 GOOD只有解析器明确知道异常/无效/缺失时才降级,避免调用方到处补质量字段。
quality = quality == null ? Quality.GOOD : quality;
sourcePath = sourcePath == null ? "" : sourcePath;
}

View File

@@ -50,6 +50,7 @@ public record TelemetrySnapshot(
rawArchiveUri = rawArchiveUri == null ? "" : rawArchiveUri;
metadata = Map.copyOf(metadata == null ? Map.of() : metadata);
fields = List.copyOf(fields == null ? List.of() : fields);
// snapshot/fields 查询按 key 投影和导出,重复 key 会导致前端列和 CSV 表头不稳定,构造期直接拒绝。
validateUniqueKeys(fields);
}
@@ -57,6 +58,7 @@ public record TelemetrySnapshot(
if (key == null || key.isBlank()) {
return Optional.empty();
}
// fields 保持插入顺序,单字段查询走线性查找;批量查询应使用 fieldsByKey 建索引。
for (TelemetryFieldValue field : fields) {
if (field.key().equals(key)) {
return Optional.of(field);
@@ -70,6 +72,7 @@ public record TelemetrySnapshot(
for (TelemetryFieldValue field : fields) {
index.put(field.key(), field);
}
// LinkedHashMap 保留原始字段顺序CSV 导出和 Swagger 示例能稳定复现解析器顺序。
return Map.copyOf(index);
}

View File

@@ -141,11 +141,11 @@ public sealed interface VehicleEvent
) implements VehicleEvent {}
/**
* 原始报文冷存事件:每条成功解码的入站帧由 Dispatcher 产出一条,携带原始字节
* {@code ArchiveEventSink} 写入 ArchiveStore。Kafka sink 默认不处理本类型
* (见 {@code KafkaEventSink.accepts})。
* 原始报文归档事件:每条成功解码的入站帧由 Dispatcher 产出一条,携带原始字节和归档索引信息。
* 32960 历史全字段查询依赖它对应的 {@code archive://...} 文件能够被回读。
*
* <p>key 组装建议:{@code yyyy/MM/dd/<source>/<vin>/<eventId>.bin},具体由 sink 实现决定。
* <p>当前 {@code KafkaEventSink} 默认不发送本类型,{@code EventFileStoreSink} 只保存索引不保存
* bytes因此启用新 raw 落盘/转发实现时,必须同时保证 {@link RawArchiveKeys} 的 key 规则一致。
*
* @param command 协议主命令码(如 32960 0x02/0x03 等),冗余在事件里便于按命令分片归档
* @param infoType 协议子类型(可为 0同上

View File

@@ -5,6 +5,9 @@ import java.util.Map;
/**
* 一次消息处理的上下文,贯穿整个拦截链与 Handler。线程不共享不需要同步。
*
* <p>attributes 用于在入口、拦截器、Dispatcher 之间传递轻量元数据,例如 traceId、归档 URI、
* 鉴权结果等;不要放大对象或长期缓存,避免高频上报时放大内存占用。
*/
public final class IngestContext {
@@ -32,6 +35,7 @@ public final class IngestContext {
}
public void abort(String reason) {
// reason 会进入日志/诊断信息,保持短小、机器可读,方便定位是去重、限流还是鉴权失败。
this.aborted = true;
this.abortReason = reason;
}

View File

@@ -15,6 +15,9 @@ import java.util.Map;
* @param rawBytes 原始字节,用于冷存与排障(可能为 null按配置开关
* @param sourceMeta 来源元数据peer ip、端口、session id、topic 等
* @param receivedAt 服务器接收时刻
*
* <p>RawFrame 是所有协议进入统一 Pipeline 的边界对象。32960 TCP 入口会把 VIN、seq、
* rawArchiveUri 等关键索引放进 sourceMeta后续去重、快照和历史查询都依赖这些字段保持稳定。
*/
public record RawFrame(
ProtocolId protocolId,