通用审计管道现在遵循“不能静默丢弃”的原则:异步入口在队列满时等待容量,同步入口无法等待时明确失败。这个合同比过去的 DropOldest 强得多,但它仍然是进程内队列,不应与事务 Outbox 或磁盘消息队列混为一谈。
1. Channel 合同
ChannelAuditQueue 是单例有界队列:
ChannelCapacity默认 10,000;BoundedChannelFullMode.Wait;- 单 Reader、多 Writer;
EnqueueAsync在饱和时背压,并接受调用方取消;TryEnqueue在饱和或关闭时返回false;Dispatcher 随即抛出InvalidOperationException;- 每条记录首次入队前获得稳定
AuditId,同时执行规范化、脱敏与长度校验。
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 按类别集合执行
$setOnInsertupsert,随后回读并核对完整证据。跨集合不是一个事务,但部分成功后重试会补齐缺失集合; - 批内同 ID、同内容折叠为一条;同 ID、不同内容直接失败,避免用后写覆盖审计事实。
4. 排除与抑制发生在哪里
Dispatcher 在入队前检查两类规则:
Audit:Excludes:按 Category 与 Pattern 排除已知高频、低价值记录;最多 1,000 条规则,启动时校验;AuditEmissionScope:Schema、Seed 等明确基础设施流程临时抑制运行时审计。
被明确配置排除的记录不计入拒绝,也不触发告警。这是采集策略,不是容量溢出。运维报表必须区分“策略未采集”“同步拒绝”“宿主故障窗口”三种情况。
5. 停机与崩溃语义
AuditBatchWriter.StopAsync 先调用 ChannelAuditQueue.Complete(),停止新写入,再等待 Reader 抽完剩余记录和当前重试。正常停机可做到:
- 写端关闭;
- 部分批次仍被返回;
- 每批持久化成功后退出;
- 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 峰值下的请求延迟、内存与数据库恢复时间。