- 新增普通通道字段上限配置,默认2048个UTF-8字节,截断尾部保留标记 - 规则计算后执行截断,告警不应用新增2KB限制,原有256KB策略不变 - 非标原文、头部及详细原因应用新增限制,大日志通道和InfluxDB策略不变 - Retry/Replay按原始Topic识别通道,启动日志输出生效上限 - 补充达梦实际写入内容大小、最大行及最大字段诊断 - 慢日志默认阈值统一调整为1000毫秒,异常和失败仍记录 - 更新v4配置说明及回归测试 验证:JDK 8构建成功,250项测试通过 升级:替换JAR并重启,无需数据库或Kafka脚本
22 KiB
吞吐优化:统计退出事实写库路径、普通业务批次自适应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,也不打印构建时间。
代码标识证明包含本次功能修订,不代表唯一构建或未来永远是最新版本;后续功能修订需同步更新标识。
只检查当前容器这次启动后的日志,避免把上次启动的记录当成本次证据:
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 已分配分区,仍需检查以下运行指标。
在各消费节点执行(宿主机,单行命令):
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 自动沿用1000ms(1秒)默认阈值,无须打开所有事务INFO日志:
connectionBeginMs包括取连接/事务初始化,lateAlarmBucketMs仅为分钟桶小键准备/注册提交回调,
jdbcExecuteMs是JDBC批执行,statsProjectionStageMs在DIRECT下是统计事件构造/注册回调阶段,
不含后台Kafka/统计写库;commitMs是事实事务提交阶段。状态中的统计成功指标表示聚合/注册完成,不保证投影落库。
SLOW_DM_QUEUE 单独说明批写任务排队超过阈值。
达梦写入内容大小诊断
SLOW_DM_TRANSACTION 的普通表/告警表成功慢事务、失败事务日志增加以下字段,无需新配置、数据库或Kafka脚本:
| 字段 | 含义 |
|---|---|
| sizeEstimateStatus | COMPLETE:支持的参数内容已测量;PARTIAL:有未测量类型;FAILED:估算异常;NOT_MEASURED:尚未执行到测量阶段 |
| estimatedPayloadBytes | 本次小事务实际INSERT列的非空参数内容估算总字节数 |
| maxRowEstimatedBytes | 单行内容估算最大字节数 |
| maxFieldEstimatedBytes / maxFieldName | 单个字段值最大估算字节数及列名,不打印内容;同大小取首个,均为零时名称为none |
| unmeasuredValues | 不支持测量的非空参数个数,如LOB/流/自定义对象;不会为了测量读取它们 |
| sizeEstimateMs | 大小估算自身耗时,包含在事务总耗时中,不计入jdbcExecuteMs或commitMs |
测量在已有字段截断和字段准备之后、按表元数据确定INSERT列之后、绑定和执行之前进行。 String按UTF-8计数(含中文、emoji,不创建完整编码字节数组),byte[]按原始长度计数, 常用数值/布尔/日期按文本表示估算,NULL计零。不包含SQL文本、驱动协议、索引、数据库页及REDO开销, 也不等于数据库字符集编码后的物理长度。COMPLETE表示参数内容估算完整,不代表精确磁盘字节数。 失败事务表示本次尝试量,不是成功落库量;失败/二分/重试的多条日志不能累加为净新增量。 NOT_MEASURED或FAILED中的零/部分值不能理解为本次写入内容为空。 仅增加观测,不修改字段值、截断阈值、保存条数或Kafka确认策略;不覆盖现有非标异步写库路径。 若需要快慢批次对照,已有stats.direct.transaction-log-enabled=true可打印DIRECT成功事务信息(日志量会增加)。
客户仍在宿主机执行:
docker logs --since 10m syslog-consumer-rule-dm 2>&1 | grep 'SLOW_DM_TRANSACTION' | tail -30
将estimatedPayloadBytes、maxRowEstimatedBytes与jdbcExecuteMs、commitMs一起比较, 通过batchId关联SLOW_SYSLOG_BATCH;不能将polledMessageBytes视为达梦写入大小。
在有Kafka脚本的现场节点执行(已确认bootstrap地址):
./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,但后台仍共享数据库和连接池资源。
精细慢批次日志与后台状态
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耗时之和,二者不要混淆。
慢日志统一使用 syslog.slow-batch.threshold-ms,默认1000ms,适用于 SLOW_SYSLOG_BATCH、
SLOW_DM_TRANSACTION、SLOW_DM_QUEUE 和 SLOW_NON_NORMAL_WRITE。
如容器已有 SYSLOG_SLOW_BATCH_THRESHOLD_MS=500,需删除该覆盖或改为1000;仅替换JAR不会覆盖环境变量。
启动日志 SYSLOG_BUILD_READY 中的 slowBatchThresholdMs 可确认生效值。无需执行数据库或Kafka脚本。
业务异常和提交异常即使小于1000ms也输出诊断。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 本路径不再增加。
普通通道字段截断(默认2KB)
配置文件:syslog-consumer-rule/src/main/resources/application.properties(prod也继承)。
syslog.raw-listener.field-max-utf8-bytes=${SYSLOG_RAW_LISTENER_FIELD_MAX_UTF8_BYTES:2048}
- 默认2048个UTF-8字节,不是2048个汉字;改为4KB时填4096。0仅关闭本层限制,启用时至少64。
- 对原始Topic等于
spring.kafka.consumer.topic的消息生效。Retry/Replay优先按信封的originalTopic判断; 缺失originalTopic时只按当前Topic判断,不猜测大日志来源。 - 在规则计算及采样决策后、达梦写入前处理:
syslog_normal_data的文本内容字段 (包括自定义映射字段),以及非标表的syslog_message、header_message、reason_detail。 - 不截断数值、日期、二进制对象;标准化结果的ID、syslog_uuid、Topic、规则版本、设备/租户/采集ID、
分区时间和event_level等技术字段保持不变。
syslog_normal_alarm跳过新增2KB限制, 但仍保留原有dm.normal.large-field-max-utf8-bytes(默认256KB,配置字段白名单)限制,本次不改旧策略。 - 超限内容尾部追加
...[HB_XDR_TRUNCATED original_utf8_bytes=原字节数],标记包含在2048字节内, 不切断中文或emoji。不会修改规则输入或InfluxDB原文限制;大日志通道不应用2KB限制,仍沿用旧策略。 - 旧的
dm.normal.large-field-max-utf8-bytes和dm.non-normal.large-field-max-utf8-bytes继续生效; 同时命中时最终遵循较小上限。此配置不修改历史记录,已截掉的字段内容不能靠回滚JAR恢复。
替换JAR并重启即可,无需数据库或Kafka脚本。若容器已有 SYSLOG_RAW_LISTENER_FIELD_MAX_UTF8_BYTES,
环境变量优先于JAR内默认值。启动日志 SYSLOG_BUILD_READY 的 rawFieldMaxUtf8Bytes=2048 可确认实际值。
回滚
回滚旧JAR会恢复主消费同步入口统计、事实计数Outbox、同步审计及分钟桶保护,新独立listener随进程退出停止。 新版本退出时尚未发送的内存统计可能丢失;回滚不自动恢复这些统计,后续需要另行校准。 保留新增统计组offset、现有Outbox和审计表;不删除Topic、不重置主消费offset。