# 吞吐优化:统计退出事实写库路径、普通业务批次自适应3000~5000条 只升级 `syslog-consumer-rule`,保持 JDK 8。无需新增表或 Topic;不能删除现有 `stats_projection_outbox`、`stats_projection_applied_event`:前者用于排空旧事件及兼容回滚,后者继续用于统计去重。 ## 本次行为 - 普通 RAW 底层每次poll上限默认5000,业务块以3000为基础按采样阶段动态扩大(默认最低保留率时为5000)。 无积压时也可能一次poll返回5000条,随后按最多3000条分块;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 | 3000 | | syslog.raw-listener.adaptive-max-records | SYSLOG_RAW_LISTENER_ADAPTIVE_MAX_RECORDS | 5000 | | dm.standardized-write.batch-size | DM_STANDARDIZED_WRITE_BATCH_SIZE | 2000 | | dm.non-normal-write.batch-size | DM_NON_NORMAL_WRITE_BATCH_SIZE | 2000 | | 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` 覆盖时须删除或改成3000, 已有 `SYSLOG_RAW_LISTENER_ADAPTIVE_MAX_RECORDS` 覆盖时须删除或改成5000。无需数据库或Kafka脚本。 `spring.kafka.consumer.max-poll-records=500` 留给通用消费者,不再决定普通RAW批次上限。 Kafka上限不保证每次拉满;业务块还受到16MiB限制。达梦普通表/告警表按目标表、日期分组后,每个小事务默认最多2000行;非标表每条多行INSERT默认最多2000行,同一次服务调用的多个INSERT仍在同一个事务中。 不足2000行直接写入,不跨业务块等待凑满。已有 `DM_STANDARDIZED_WRITE_BATCH_SIZE` 或 `DM_NON_NORMAL_WRITE_BATCH_SIZE` 环境变量时,删除覆盖或改成2000才会采用新值。 例如同表同日保留3000行会按2000+1000写入;采样后只保留500行就只写500行。更大批次不保证性能提升,需对比现场执行及提交耗时。 保留旧配置名 `syslog.raw-listener.max-poll-records`,但其含义现在是**业务基础条数**。 底层RAW的 `max.poll.records` 固定使用 `adaptive-max-records`,不重建consumer、不反射修改Kafka内部参数。 基础值必须大于0且不超过上限,非法配置拒绝启动。独立入口统计默认2000条(不做规则计算),不采用该策略。 业务块目标条数 = min(上限, ceil(基础条数 × 10000 / 较高保留万分比))。 普通最低1000、非标最低2000,取较高2000,3000×10000÷2000=15000,受5000上限限制; 较高保留率为8000时目标3750,为6000或更低时目标5000。 两者都为0时也只到上限,避免除0;告警仍全部保留。各阶段自动沿用现有采样决策,不另配倍率。 实现采用**上一业务块的阶段反馈决定下一块大小**。每个RAW消费线程独立,首次或超过1分钟无有效反馈时从3000探测。 一个块内存在多个延迟阶段时取最高保留率,准备/规则异常保守回到3000;恢复全量保存后的下一块回到3000。 例如首次poll5000条且均为默认最低档:先处理3000,确认保留率后处理余下2000;后续poll目标可到5000。 不会为了凑足目标条数等待新消息,也不会跳过已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=5000, baseProcessingRecords=3000, adaptiveMaxRecords=5000`, 启动日志应包含 `dmStandardizedBatchSize=2000, dmNonNormalBatchSize=2000`, 主批次 `ingressMs=0`,入口状态模式为 `INDEPENDENT_KAFKA_DIRECT_MERGE`。 `polledRecords`可大于3000;`adaptiveTargetHistogram`和`actualChunkHistogram`分别表示业务目标与实际块大小。 例如 `{3000=1, 2000=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成功事务信息(日志量会增加)。 客户仍在宿主机执行: ```bash 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地址): ```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耗时之和,二者不要混淆。 慢日志统一使用 `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也继承)。 ```properties 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。