8173c2ff3f
- 入口统计改用独立 Kafka 消费组,移除主消费同步统计写库 - DIRECT 模式入库计数改为事务提交后异步投递,取消事实事务内统计 Outbox 写入 - 采样审计、告警分钟桶通知改为后台处理,异常不阻断主消费或回滚告警 - RAW 基础业务批量默认 500,按保留率动态扩大,上限 3000 - 限制业务块字节数及规则任务数量,保留告警全量处理和整次 poll 完成后提交 - 细化 SLOW_SYSLOG_BATCH,补充批次关联、线程排队、数据库执行及提交耗时 - 更新 v4 升级说明,明确异步统计损失风险及现场验证方法 验证:JDK 8 构建成功,232 项测试通过。 升级:复用现有表和 Kafka Topic,无新增建表或建 Topic 脚本要求。
200 lines
17 KiB
Markdown
200 lines
17 KiB
Markdown
# 吞吐优化:统计退出事实写库路径、普通业务批次自适应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。
|