Skip to content
bitzorcas
中EN

Concept

Auditing 队列、批写与交付语义

说明有界 Channel 的背压与同步拒绝、定时聚合、稳定 AuditId、持久化重试、优雅排空和仍然存在的进程故障窗口。

Last updated

通用审计管道现在遵循“不能静默丢弃”的原则:异步入口在队列满时等待容量,同步入口无法等待时明确失败。这个合同比过去的 DropOldest 强得多,但它仍然是进程内队列,不应与事务 Outbox 或磁盘消息队列混为一谈。

1. Channel 合同

ChannelAuditQueue 是单例有界队列:

  • ChannelCapacity 默认 10,000;
  • BoundedChannelFullMode.Wait;
  • 单 Reader、多 Writer;
  • EnqueueAsync 在饱和时背压,并接受调用方取消;
  • TryEnqueue 在饱和或关闭时返回 false;Dispatcher 随即抛出 InvalidOperationException;
  • 每条记录首次入队前获得稳定 AuditId,同时执行规范化、脱敏与长度校验。
async/sync enqueuereaches capacityreader frees capacityasync writer backpressuresync writer fails closedfirst item readsize or interval reachedstore failurebounded backoffsuccess

Ready

Buffered

Saturated

Waiting

Rejected

Batching

Persisting

Retrying

RejectedCount(兼容属性名 DroppedCount)只统计同步入口的明确拒绝,不表示后台发生了静默丢失。首条拒绝和之后每 1,000 条会采样告警;告警单飞投递,避免告警本身再次触发队列而形成递归风暴。

2. 批次何时刷新

Writer 先等待第一条记录,再继续聚合,满足任一条件即返回:

  • 达到 BatchSize,默认 100;
  • 第一条到达后经过 BatchIntervalSeconds,默认 5 秒;
  • Channel 完成,返回已经读取的部分批次。

因此低流量时单条记录的聚合等待上限约为 5 秒,高流量时会更早以满批写入。三个参数都会在启动时校验:Capacity 1–1,000,000,BatchSize 1–10,000 且不大于 Capacity,Interval 1–3,600 秒。

3. 失败重试与幂等

AuditBatchWriter 持有当前批次,只有 Store 成功才读取下一批。失败时:

  • 保留同一批次无限重试,直到成功或宿主强制取消;
  • 普通异常按 1–30 秒有界退避;
  • 识别连接池压力后使用 35–90 秒的较长退避,给登录和 HTTP 请求释放连接预算;
  • 第一次和每第十次失败触发 BatchWriteFailure 告警;
  • OOM 不被吞掉。

稳定 AuditId 让整批重试具备可验证的幂等性:

  • SqlSugar 在一个数据库事务中写六组表;提交结果未知时,重试会按目标分表查重,并验证已存在行与预期证据完全一致;
  • Mongo 按类别集合执行 $setOnInsert upsert,随后回读并核对完整证据。跨集合不是一个事务,但部分成功后重试会补齐缺失集合;
  • 批内同 ID、同内容折叠为一条;同 ID、不同内容直接失败,避免用后写覆盖审计事实。

4. 排除与抑制发生在哪里

Dispatcher 在入队前检查两类规则:

  • Audit:Excludes:按 Category 与 Pattern 排除已知高频、低价值记录;最多 1,000 条规则,启动时校验;
  • AuditEmissionScope:Schema、Seed 等明确基础设施流程临时抑制运行时审计。

被明确配置排除的记录不计入拒绝,也不触发告警。这是采集策略,不是容量溢出。运维报表必须区分“策略未采集”“同步拒绝”“宿主故障窗口”三种情况。

5. 停机与崩溃语义

AuditBatchWriter.StopAsync 先调用 ChannelAuditQueue.Complete(),停止新写入,再等待 Reader 抽完剩余记录和当前重试。正常停机可做到:

  1. 写端关闭;
  2. 部分批次仍被返回;
  3. 每批持久化成功后退出;
  4. Host 停机预算耗尽时才取消重试。

这不等于耐久队列。以下场景仍可能丢失尚在内存中的通用审计:

  • SIGKILL、OOM、节点断电或容器被强制回收;
  • 停机宽限期不足且 Store 尚未恢复;
  • 进程在记录形成后、入队前崩溃;
  • 生产者主动取消等待中的 EnqueueAsync,且上层选择吞掉取消。

多实例各有自己的队列,没有跨实例顺序。需要与业务提交同生共死的证据应走事务实体审计 Outbox 或业务专用 Outbox;需要 WORM、签名或时间戳的监管证据应使用专门账本。

6. 交付等级选择

场景建议交付等级
普通 HTTP/查询运行留痕当前 Channel + 告警与停机预算
一般授权、外呼与后台任务诊断Channel + 明确的拒绝/积压 SLO
实体变更且启用 CAP 工作单元内置事务实体审计 Outbox
权限授予、租户模拟、密钥轮换业务事务 Outbox + 不可变审计投影
财务、合同、监管删除专用 Ledger/WORM + 签名/可信时间戳
高频遥测OTel/日志平台;不要把每个指标塞进审计表

同一系统可以并存多种等级。关键是用例先声明所需保证,再选择 Sink;接口名字里有 Audit 并不自动等于“监管级不可抵赖”。

7. 配置

审计队列关键配置
Audit:
# 每个宿主进程的有界容量。
ChannelCapacity: 10000
# 单批最大条数。
BatchSize: 100
# 第一条到达后的最长聚合秒数。
BatchIntervalSeconds: 5

容量估算可从 capacity >= max(0, P - W) × T 起步:P 为峰值生产速率,W 为 Store 可持续速率,T 为希望吸收的短暂故障秒数。还要计入单条最大正文、GC 暂停、数据库连接池、跨类别往返和停机排空时间。

8. 生产观测

至少建立以下信号:

  • 同步拒绝累计值与采样告警;
  • Store 写入耗时、批大小、失败次数和连续重试轮次;
  • 连接池压力分类及恢复时间;
  • 停机排空耗时与宽限期耗尽;
  • 宿主重启次数,以及重启前最后一条持久化 AuditId/OccurredAt;
  • SIEM 投递积压与审计存储积压分开统计。

源码目前没有暴露完整 Queue Depth/Oldest Age 指标;若 SLO 依赖它们,应先补观测端口,不能用日志条数反推精确队列状态。

9. 故障注入合同

  • Capacity 1/2/N 下的异步等待、同步拒绝、顺序和取消;
  • BatchSize/Interval 的边界与 Channel 完成后的部分批次;
  • SqlSugar 各组插入失败、提交结果未知、回滚失败和稳定 ID 冲突;
  • Mongo 单集合/跨集合部分成功、回读不完整和证据冲突;
  • 普通数据库故障、连接池耗尽与 OOM 的不同处理;
  • 正常停机、宽限期耗尽、SIGTERM 与 SIGKILL;
  • 告警 Sink 失败、递归防护和每 1,000 条采样;
  • 1x/5x/10x 峰值下的请求延迟、内存与数据库恢复时间。

上一篇:捕获点与规范化 · 下一篇:存储、分表与查询

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%