《合规护栏》把所有 PII 落点接到同一套脱敏引擎后,这个引擎就成了全系统的统一依赖------它的形态、加载方式、启动依赖、故障行为直接决定"防护层会不会成为新的单点故障"。
核心论点
引擎的可用性是一组结构性选择:规则即数据 + 薄引擎 + 三级规则来源 + 双层级降级------让中央规则源退化为纯"新鲜度提供者",任何时刻挂掉都不影响可用性,只影响规则更新速度。
引擎形态:规则即数据 + 薄引擎
库是语言绑定的 :Python 库只能在 Python 进程 import,跨语言(Go/Java/Node)要么重写规则(→ 规则漂移,正是要消灭的),要么嵌运行时(丑)。所以"库"不是跨语言的单一真相源。
正确形态:规则即数据 (YAML/JSON/Rego 表达正则/字典/字段规格)+ 各语言薄引擎只做"加载同一份规则文件并匹配"。规则不重写,只重写简单的匹配逻辑(OPA/Rego 思路)。
| 部署形态 | 适用 | 特点 |
|---|---|---|
进程内薄引擎(import) |
单语言(本项目全 Python/FastAPI) | 零额外跳,热路径不吃延迟 |
| OTel Collector redaction/transform processor | 跨语言 / 遥测管道(OTel Collector 是 Go、Langfuse exporter 非 Python 可控) | 现成 sidecar 模式,调同一份规则文件 |
命门是"规则文件唯一来源",不是"库还是 sidecar":两类引擎都加载同一份中央规则数据,否则各服务 pin 不同版本 → 规则漂移,等于换皮的"处处补丁"。
最强形态:采集即 token 化------边缘把 PII 换 token,下游只存 token,原始 PII 只在最边缘存在(与《LLM流量网关设计》"靠强制不靠约定"一脉相承)。
规则加载:本地缓存 + 异步刷新
| 选项 | 热路径联网 | 源挂了 |
|---|---|---|
| 每请求实时拉规则 | ✅ 塞了网络依赖 | 请求失败(明明有合法本地规则) |
| 本地缓存 + 异步刷新 | ❌ 零联网 | 照常跑(用 last-known-good) |
把两个目标拆开:
- 新鲜度(规则改了多快生效)靠 watch/短刷新 + 源 HA
- 可用性(源挂了还能跑)靠本地缓存
数据源高可用只保"新鲜度可靠性",不保热路径可用性------可用性来自本地缓存。每请求同步拉规则 = 在热路径塞网络依赖,把防护层变成新的同步单点。
启动依赖:三级规则来源,启动永不阻塞
若启动时同步拉源、拉不到就起不来:重启风暴时(批量重启/扩容)所有实例同时打源,服务恢复能力被规则源绑架;且与"运行中源挂了照跑"不自洽------进程重启不该改变依赖关系。
正确顺序:
① 本地磁盘快照(运行期每次刷新成功后落盘的 last-known-good,重启首选)
↓ 不可得
② 构建期内置基线(规则快照打进镜像/包,冷启动兜底)
↓ 启动后立即触发
③ 中央源异步刷新(只做新鲜度补偿,不阻塞启动)
绝不允许"零规则启动"------静默 fail-open 比起不来更危险。基线随构建产物存在,结构上排除了这种可能。
基线可能滞后:暴露"当前规则版本 + 距源滞后度"指标 + 超阈值告警补偿(接《系统健康监控巡检》告警节),而非用同步依赖消灭滞后。
故障隔离与降级:绝不二选一"放行/拦截"
脱敏引擎在每请求热路径里,若 fail-closed(默认拦截)且引擎故障,会连锁拖垮所有走它的服务;若把中央策略源做成每请求网络调用,规则服务才是真 SPOF(§2/§3 已从结构上排除)。
双层级检测,降级先丢贵的层:
| 层 | 实现 | 成本 | 降级行为 |
|---|---|---|---|
| 确定性层(正则/字典,身份证/手机号/邮箱) | 纯本地 | 极低、不会挂 | 永远在线 |
| 语义层(模型判定模糊 PII) | 依赖外部 | 贵 | 引擎降级时先丢这一层,仍能挡 90% 硬 PII |
降级阶梯(按引擎健康度):
| 等级 | 状态 | 行为 |
|---|---|---|
| 健康 | 两层全开 | 完整防护 |
| 降级 | 可达但慢/部分失败 | 熔断语义层、只跑本地正则、硬超时(如 5ms)跳过语义 |
| 全挂 | 引擎不可用 | 按路由敏感度分级决策 |
全挂时的分级决策:
| 路由敏感度 | fail-mode | 处置 |
|---|---|---|
| 高敏(身份/金额) | fail-closed | 拦截 + 告警 |
| 低敏 | fail-open | 放行 + 强告警 + 人工回溯 |
出口平面落点(日志/trace/缓存)的 redaction 必须在异步导出管道 做(OTel processor 本就异步),引擎挂了绝不阻塞用户请求,最坏只是遥测未脱净、告警让人处理。
降级阶梯健康阈值(约定常量):
| 常量 | 默认值 | 含义 |
|---|---|---|
RULE_REFRESH_TIMEOUT_SEC |
2.0 | 后台刷新超时上限,超此值视为源不可达 |
RULE_REFRESH_FAILURE_BUDGET |
5 | 连续刷新失败达此次数,由 degraded 降为 down |
降级判定由 governance hook 读取实现,引擎不自发计数熔断。
规则变更安全:版本化 + last-known-good + 灰度
"坏规则推送"本身是故障模式:一条写崩的正则能让所有引擎同时崩溃或全量误拦------比引擎故障更致命,因为它同时命中所有实例。
| 处置 | 作用 |
|---|---|
| 规则版本化 | 每次推送带版本号(哈希标识),可追溯 |
| last-known-good 回滚 | 加载失败/校验不过自动回退上一版 |
| 灰度推送 | 先小流量实例验证再全网 |
决策:源 HA 挡不住"内容错误"------高可用地推送一条坏规则只会挂得更快。变更安全靠版本化 + 灰度,不靠 HA。
核心要点
- 防护层自身不能成为新单点,靠五条结构性选择:
- 规则即数据 + 薄引擎:跨语言不漂移
- 本地缓存 + 异步刷新:热路径零联网
- 三级规则来源:启动永不阻塞、绝不零规则启动
- 双层级降级:确定性层永远在线,全挂按敏感度分级 fail-open/closed
- 版本化 + 灰度:坏规则不全网生效
- 中央规则源被降格为纯"新鲜度提供者":任何时刻挂掉都不影响可用性,只影响规则更新速度------这才配得上"不引入 SPOF"。