Files
ai-security-xdr/haobang-security-dm/upgrade-doc/v4/config/02-throughput-upgrade.md
T
王绍宏 8173c2ff3f perf(syslog): 统计异步化并按采样保留率自适应调整业务批量
- 入口统计改用独立 Kafka 消费组,移除主消费同步统计写库
- DIRECT 模式入库计数改为事务提交后异步投递,取消事实事务内统计 Outbox 写入
- 采样审计、告警分钟桶通知改为后台处理,异常不阻断主消费或回滚告警
- RAW 基础业务批量默认 500,按保留率动态扩大,上限 3000
- 限制业务块字节数及规则任务数量,保留告警全量处理和整次 poll 完成后提交
- 细化 SLOW_SYSLOG_BATCH,补充批次关联、线程排队、数据库执行及提交耗时
- 更新 v4 升级说明,明确异步统计损失风险及现场验证方法

验证:JDK 8 构建成功,232 项测试通过。
升级:复用现有表和 Kafka Topic,无新增建表或建 Topic 脚本要求。
2026-09-13 06:11:12 +08:00

200 lines
17 KiB
Markdown
Raw 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.
# 吞吐优化:统计退出事实写库路径、普通业务批次自适应500~3000条
只升级 `syslog-consumer-rule`,保持 JDK 8。无需新增表或 Topic;不能删除现有
`stats_projection_outbox`、`stats_projection_applied_event`:前者用于排空旧事件及兼容回滚,后者继续用于统计去重。
## 本次行为
- 普通 RAW 底层每次poll上限固定3000,业务块以500为基础按采样阶段动态扩大(默认最低档2500,最多3000)。
无积压时也可能一次poll返回3000条,随后分成500条业务块;Retry仍使用通用配置,大日志仍默认5条。
- 普通业务处理按 Kafka value 序列化长度分成最多16MiB的块,单条不拆分;一个poll的全部块完成后ACK。
- 每个业务块最多8个规则worker,按原顺序汇总结果。不会为2000条消息同时排队2000个任务。
- 主处理方法不再调用入口统计写库,因此主批次日志 `ingressMs=0`。
- 独立统计消费组读取原始普通/大日志Topic,解密后只读取设备头,不做规则或采样。
批内聚合后直接更新设备/采集任务MIN/MAX时间并提交达梦,再提交自己的offset;不写入口Outbox。
- 独立统计数据库异常只重试统计组,不推进其offset。非法密文/头记录
`INGRESS_STATS_INVALID` 和错误尝试计数后跳过,不阻塞其它入口状态、不重复投递业务Retry。
密钥配置错误也会导致非法记录,发现该标记必须检查;原始记录只能在Kafka保留期内重放。
- 标准化告警优先提交;告警分钟桶改为提交后入队,后台独立事务按设备、分钟固定顺序写入。
通知失败只后台重试,不回滚已入库告警;队列满/进程退出可能丢通知,导致部分迟到数据漏重算。
- 采样审计也改为后台独立事务:失败不再取消采样,不回退全量入库。审计可能缺失或提交结果不确定,
对累加审计不盲目重试,避免重复计数;内存采样指标描述决策,不代表审计或业务数据已成功写库。
- 生产默认 `stats.write-mode=DIRECT` 下,告警、普通、非标事实计数不再写Outbox,也不在事实事务内更新日统计表。
只做批内聚合并注册提交回调,事务成功提交后使用非等待式 `offer` 入有界内存队列。
Kafka发送、等待确认和重试在专用后台线程执行,使用独立Producer缓冲区;事实回滚不会入队。
- 统计构造异常、队列满、关闭发布等情况记录异常/计数后放行,不回滚事实、不重试主消费。
Kafka发送失败暂存在有界队列内后台重试;饱和后丢弃新统计事件,不丢弃对应事实数据。
进程退出、崩溃及队列溢出可能造成统计缺失。这是本次明确选择的“消费连续性优先”,不保证统计精确。
- 新统计事件标记 `outboxBacked=false`,独立投影消费者仅写统计及去重标记,不访问Outbox。
老事件默认 `true`,继续按原流程排空和标记Outbox。混合升级期间旧消费者仍可能执行无命中的Outbox UPDATE,
应完成所有消费节点升级。旧入口Outbox的MIN/MAX重放安全。
## properties及变量
均位于 `syslog-consumer-rule/src/main/resources/application.properties`,prod继承这些配置。
| properties | 环境变量 | 默认值 |
| --- | --- | --- |
| syslog.raw-listener.max-poll-records | SYSLOG_RAW_LISTENER_MAX_POLL_RECORDS | 500 |
| syslog.raw-listener.adaptive-max-records | SYSLOG_RAW_LISTENER_ADAPTIVE_MAX_RECORDS | 3000 |
| syslog.raw-listener.processing-batch-max-bytes | SYSLOG_RAW_PROCESSING_BATCH_MAX_BYTES | 16777216 |
| syslog.normalization.workers-per-batch | SYSLOG_NORMALIZATION_WORKERS_PER_BATCH | 8 |
| stats.ingress-metadata.group-id | STATS_INGRESS_GROUP_ID | 主消费组名加 `-ingress-v1` |
| stats.ingress-metadata.large-group-id | STATS_INGRESS_LARGE_GROUP_ID | 入口统计组名加 `-large` |
| stats.ingress-metadata.max-poll-records | STATS_INGRESS_MAX_POLL_RECORDS | 2000 |
| stats.ingress-metadata.auto-offset-reset | STATS_INGRESS_AUTO_OFFSET_RESET | earliest |
| stats.projection-async.memory-queue-capacity | STATS_PROJECTION_MEMORY_QUEUE_CAPACITY | 1000 |
| stats.projection-async.memory-max-pending-groups | STATS_PROJECTION_MEMORY_MAX_PENDING_GROUPS | 50000 |
| analysis.late-replay.notify-timeout-seconds | ANALYSIS_LATE_REPLAY_NOTIFY_TIMEOUT_SECONDS | 5 |
只替换JAR也会使用上述默认值;已有 `SYSLOG_RAW_LISTENER_MAX_POLL_RECORDS=2000` 时须删除或改成500。
`spring.kafka.consumer.max-poll-records=500` 留给通用消费者,不再决定普通RAW批次上限。
Kafka上限不保证每次拉满;业务块还受到16MiB限制。达梦每个小事务仍最多500行。
保留旧配置名 `syslog.raw-listener.max-poll-records`,但其含义现在是**业务基础条数**。
底层RAW的 `max.poll.records` 固定使用 `adaptive-max-records`,不重建consumer、不反射修改Kafka内部参数。
基础值必须大于0且不超过上限,非法配置拒绝启动。独立入口统计默认2000条(不做规则计算),不采用该策略。
业务块目标条数 = min(上限, ceil(基础条数 × 10000 / 较高保留万分比))。
普通最低1000、非标最低2000,取较高2000,500×10000÷2000=2500;两者都为1000时命中3000上限。
两者都为0时也只到上限,避免除0;告警仍全部保留。各阶段自动沿用现有采样决策,不另配倍率。
实现采用**上一业务块的阶段反馈决定下一块大小**。每个RAW消费线程独立,首次或超过1分钟无有效反馈时从500探测。
一个块内存在多个延迟阶段时取最高保留率,准备/规则异常保守回到500;恢复全量保存后的下一块回到500。
例如首次poll3000条且均为默认最低档:先处理500,确认保留率后处理余下2500;后续poll可按2500+500处理。
不会为了凑足目标条数等待新消息,也不会跳过已poll的尾部记录;每个poll所有块处理完才提交offset。
启用连续性放行时失败块仍沿用既有DLT/记录策略,之后以基础批量继续,未增加新的业务丢弃规则。
所有消息仍要解密、跑规则才能确认告警,扩大批量会增加整体poll处理耗时、内存和GC压力。
整次poll的处理必须在max.poll.interval.ms内完成;16MiB仅限制单个业务块,不限制整个Kafka缓存。
内存队列容量按批次聚合事件计,不是原始日志条数;另有最多1个发送中事件。
聚合分组上限包含排队和发送中事件,不储存原文。默认每200ms调度发送,失败按既有
`stats.projection-async.retry-backoff-ms`(默认1000ms)后台重试,保留同一eventId以便下游去重。
`stats.projection-async.enabled=false` 不会回退到同步统计SQL,而是跳过统计、记录损失;正常保持true。
历史 `DIRECT_SHADOW` / `LEGACY_CHANGE_LOG` 模式保留旧语义,本次消除统计同步写库针对默认DIRECT。
保留 `stats.ingress-metadata.enabled=true`。此前建议用 `STATS_INGRESS_METADATA_ENABLED=false`
应急关闭不适用:当正式统计回写启用时,现有启动校验会拒绝此组合。
## 新消费组首次启动
三个节点需使用相同的入口统计组名;它们不能与业务组相同,程序会拒绝这种错误配置。
新组需要读取对应原始Topic和提交offset的Kafka权限。
默认 `earliest` 从Kafka当前仍保留的最早记录开始,能够补齐保留期内入口时间;已有组offset时续跑。
现场有大量历史数据时,新统计组也会回放这些记录,增加Kafka网络、解密CPU和数据库负载。
只做MIN/MAX更新,不会根据原始消息再次增加成功入库条数。
若现场明确只需接管上线后的接收时间,可在首次启动前设置 `STATS_INGRESS_AUTO_OFFSET_RESET=latest`;
该选择不会回放启动前尚未更新的入口时间,且不影响已有offset。不能仅为了让LAG显示为零而选latest。
Kafka保留期必须覆盖统计积压;过期记录无法由这个消费组恢复。
不需要重置业务组offset。混合版本滚动期间,旧版仍可能写入口Outbox,新版直接更新MIN/MAX,
这些时间字段允许重复;三个节点完成升级后主消费才全部移除入口统计等待。
## 现场验证
应用就绪时输出一条 `[SYSLOG_BUILD_READY]`,本次代码标识为
`codeRevision=ASYNC_STATS_ADAPTIVE_RAW_V1`。同时打印应用名以及生效的基础条数、上限、并发和统计开关。
标识固定在 Java 代码中,不依赖构建插件,无需修改 pom.xml,也不打印构建时间。
代码标识证明包含本次功能修订,不代表唯一构建或未来永远是最新版本;后续功能修订需同步更新标识。
只检查当前容器这次启动后的日志,避免把上次启动的记录当成本次证据:
```bash
docker logs --since "$(docker inspect -f '{{.State.StartedAt}}' syslog-consumer-rule-dm)" syslog-consumer-rule-dm 2>&1 | grep 'SYSLOG_BUILD_READY' | tail -1
```
该日志表示应用已就绪,不证明数据库写入正常或 Kafka 已分配分区,仍需检查以下运行指标。
在各消费节点执行(宿主机,单行命令):
```bash
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep -E 'Raw Kafka batch configuration|Ingress stats consumer ready' | tail -10
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep -E 'SLOW_SYSLOG_BATCH|SLOW_DM_TRANSACTION|SLOW_DM_QUEUE' | tail -80
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep -E 'INGRESS_STATS_BATCH|INGRESS_STATS_INVALID|INGRESS_STATS_WRITE_FAILED' | tail -30
docker exec syslog-consumer-rule-dm sh -c 'curl -fsS http://127.0.0.1:8289/xdrrule/api/syslog/stats/ingress/status'
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep -E 'STATS_ASYNC_LOSS|STATS_ASYNC_PUBLISH_FAILED|STATS_ASYNC_SHUTDOWN_PENDING' | tail -30
docker exec syslog-consumer-rule-dm sh -c 'curl -fsS http://127.0.0.1:8289/xdrrule/api/syslog/stats/direct/status'
```
确认普通配置输出 `maxPollRecords=3000, baseProcessingRecords=500, adaptiveMaxRecords=3000`,
主批次 `ingressMs=0`,入口状态模式为 `INDEPENDENT_KAFKA_DIRECT_MERGE`。
`polledRecords`可大于500;`adaptiveTargetHistogram`和`actualChunkHistogram`分别表示业务目标与实际块大小。
例如 `{500=1, 2500=1}` 表示对应大小各1块,不代表保留率。实际大小受剩余条数及字节上限限制。
直接统计状态中 `daily_projection_write_mode=BEST_EFFORT_AFTER_COMMIT_KAFKA`,
`async_publisher` 下有排队事件/分组数、已发布、失败重试、丢弃计数。
`dropped_fact_rows_total` 指受统计缺失影响的事实行数/尝试行数,并不表示事实数据被删除。
这些内存指标会在重启后清零,不能覆盖崩溃时丢失的统计。损失日志最多每10秒一次,计数逐次累加。
`published_total` 仅表示Kafka确认,不代表统计已落达梦;统计最终入库还需检查投影消费组积压。
`SLOW_DM_TRANSACTION` 自动沿用500ms阈值,无须打开所有事务INFO日志:
`connectionBeginMs`包括取连接/事务初始化,`lateAlarmBucketMs`仅为分钟桶小键准备/注册提交回调,
`jdbcExecuteMs`是JDBC批执行,`statsProjectionStageMs`在DIRECT下是统计事件构造/注册回调阶段,
不含后台Kafka/统计写库;`commitMs`是事实事务提交阶段。状态中的统计成功指标表示聚合/注册完成,不保证投影落库。
`SLOW_DM_QUEUE` 单独说明批写任务排队超过阈值。
在有Kafka脚本的现场节点执行(已确认bootstrap地址):
```bash
./kafka-consumer-groups.sh --bootstrap-server 10.141.121.35:9092 --group agent-01-syslog-group --describe
./kafka-consumer-groups.sh --bootstrap-server 10.141.121.35:9092 --group agent-01-syslog-group-ingress-v1 --describe
./kafka-consumer-groups.sh --bootstrap-server 10.141.121.35:9092 --group agent-01-syslog-group-ingress-v1-large --describe
```
如现场组名不同,使用启动日志中的真实组名。间隔60秒再查,比较所有分区CURRENT-OFFSET增量之和与
LOG-END-OFFSET增量之和;前者持续大于后者才说明追赶积压。不要只比较筛选出的慢批次或单批毫秒数。
入口统计已不再阻塞主线程,但仍共享Kafka、JVM和数据库资源,不能保证固定倍数提速。
本地测试不替代现场达梦压测。主消费仍可能受事实表执行、COMMIT、规则计算、InfluxDB及异常路由等待限制。
采样审计和告警分钟桶不再在主消费/事实事务执行SQL,但后台仍共享数据库和连接池资源。
## 精细慢批次日志与后台状态
```bash
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep -E 'SLOW_SYSLOG_BATCH|SLOW_DM_TRANSACTION|SLOW_DM_QUEUE|SLOW_NON_NORMAL_WRITE' | tail -80
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep -E 'SYSLOG_AUX_FAILURE|SYSLOG_AUX_LOSS|SYSLOG_AUX_SHUTDOWN_PENDING|SLOW_SYSLOG_AUX' | tail -40
docker exec syslog-consumer-rule-dm sh -c 'curl -fsS http://127.0.0.1:8289/xdrrule/api/syslog/backlog-sampling/status'
```
拿到慢批次的batchId后,用 `docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep '实际batchId'`
关联该批次的达梦事务/队列、非标异步写入日志。日志不打印原文。
| 字段 | 如何判断 |
| --- | --- |
| batchId / offsetRanges | 本次监听调用的关联ID、各分区最小/最大offset(范围不代表中间全部连续) |
| adaptiveRaw / baseProcessingRecords / adaptiveMaxRecords | 是否RAW自适应通道、基础条数、硬上限;Retry/大日志adaptiveRaw=false |
| adaptiveTargetHistogram / actualChunkHistogram | 各业务目标条数/实际条数对应的块数,按整次poll累计 |
| observedRetainBps / nextTargetRecords | 最近完成块观测的较高保留万分比及下一块目标;依赖上一块反馈,可能滞后一块 |
| stageMs.ADAPTIVE_CHUNK_SELECTION | 计算RAW目标条数及按字节/条数选取业务块耗时 |
| listenerGapMs | 同一监听线程上次调用结束到本次调用的间隔;首次为-1,不能据此直接判定Kafka空闲或提交慢 |
| stageMs.PREPARE | 解密、元信息解析、原文点构造及准备失败路由 |
| stageMs.RULE_SNAPSHOT | 规则快照及批次上下文准备 |
| stageMs.RULE_EXECUTION_WAIT | 规则任务提交、排队和执行的整体墙钟时间 |
| ruleQueueMaxMs / ruleWorkerBusySumMs | 规则worker最大排队时间/worker执行累计时间;累计值可大于墙钟时间,不可与stageMs相加 |
| stageMs.SAMPLING_DECISION / AUDIT_ENQUEUE | 采样决策/审计聚合入队;没有审计SQL等待 |
| stageMs.ALARM_DM / NORMAL_DM | 告警/普通表批写等待;用相同batchId的SLOW_DM_QUEUE和SLOW_DM_TRANSACTION继续分解 |
| stageMs.DM_FAILURE_ROUTE / CONTINUITY_DLT | 业务坏行或整批异常的Retry/DLT处理耗时 |
| stageMs.NON_NORMAL_ENQUEUE | 只表示非标异步提交耗时,不表示非标落库;看SLOW_NON_NORMAL_WRITE的queueMs/writeMs |
| stageMs.INFLUX_AND_FAILURE_ROUTE | 仍然同步的原文写入及其失败处理耗时(此次未改成异步) |
| alarmRequested/alarmSaved/normalRequested/normalSaved | 计划提交行数及成功返回的写入报告行数;异常中断时不是数据库最终事实数 |
| rawSampledOut/normalSampledOut/nonNormalSampledOut | 已完成业务块的采样丢弃数,不包括异常丢弃 |
| auditAcceptedChunks/auditLostChunks | 采样审计入队成功/失败块数;入队成功不代表后台已入库 |
| failedChunks/failureStage/businessError | 中断的业务块数、最后失败阶段、异常类型;正常无失败时failureStage可为null |
| kafkaCommitStatus | SUCCESS、FAILED、NOT_ATTEMPTED;业务中断不得误判为Kafka提交成功 |
`stageMs`各阶段为互斥墙钟时间,跨业务块累加;旧有normalizeMs等字段包含部分子阶段,不能再与stageMs相加。
兼容字段ruleComputeWorkerMs实际是整批规则计算墙钟耗时;新增ruleWorkerBusySumMs才是worker耗时之和,二者不要混淆。
业务异常和提交异常即使小于500ms也输出诊断。`processedRecords/prepared`等旧计数仅覆盖完成的业务块;
出现failedChunks时不能把它们理解为整次poll已成功入库。无限等待中的操作尚未结束,不会提前输出最终慢批次。
采样审计接口新增 `audit_async`,直接统计接口的 `dm_transaction.late_alarm_bucket_async` 查看通知队列。
两个队列各固定最多256个等待任务、最多50000个小分组(含发送中),各有独立单线程;满时记录后放行。
审计失败后按既有audit-retry-backoff-ms暂停后台执行并跳过新审计,不取消采样;分钟桶每1秒后台重试失败任务。
辅助异常日志最多每10秒一次,损失计数逐次累加,重启清零。进程退出可能丢失队列任务;未实现补偿扫描。
新审计指标 `audit_lost_or_unconfirmed_messages_total` 表示未记录或提交结果未知的消息数,
旧 `audit_fallback_messages_total` 本路径不再增加。
## 回滚
回滚旧JAR会恢复主消费同步入口统计、事实计数Outbox、同步审计及分钟桶保护,新独立listener随进程退出停止。
新版本退出时尚未发送的内存统计可能丢失;回滚不自动恢复这些统计,后续需要另行校准。
保留新增统计组offset、现有Outbox和审计表;不删除Topic、不重置主消费offset。