服务治理规则优先级如何设计?
简化版
规则优先级不能只比较一个全局整数。系统应先区分同一规则的修订版本、不可覆盖的安全护栏、可继承的默认规则和临时业务例外:同一 ruleId 只采用有效的最新修订;安全护栏始终参与决策;覆盖型配置先比作用域具体度,再比同层业务优先级;限额等累积型规则则同时生效。版本号只表示新旧,不能替代业务优先级;同层同优先级却动作不同的规则必须拒绝发布。
详细版
先为每种规则声明 ruleType、ruleId、revision、scope、businessPriority、overridePolicy 和有效期。解析时先淘汰过期、未生效和旧 revision,再加载平台强制护栏;之后才让地域、租户、接口等更具体作用域覆盖默认值。不同作用域是否可覆盖必须由字段声明,不能默认“越具体权限越大”。
例如全局并发上限 500 是不可覆盖护栏,租户 A 可在 /search 上把自身上限收紧到 80,却不能放宽到 800;超时默认值可从服务级 300ms 被接口级 120ms 覆盖;应急规则可以临时高于普通业务规则,但必须限制作用域、审批、TTL 和回滚版本。
解析结果应编译为不可变快照,并提供 explain 信息:采用了哪个 revision、继承了哪些默认值、哪些规则因护栏被截断。这样优先级才是可测试的模型,而不是依赖加载顺序的偶然结果。
完整版教学
一、先把四种“优先”分开
优先级设计最容易犯的错误,是把所有关系压成一个整数。实际上至少有四种不同问题:同一规则哪次修订最新,哪些约束永远不能绕过,配置如何沿作用域继承,以及同一作用域内哪个业务例外优先。这四类关系若混在一起,高版本规则可能意外绕过安全上限,租户例外也可能覆盖平台底线。
| 关系 | 解决的问题 | 正确依据 |
|---|---|---|
| 修订选择 | 同一 ruleId 用哪版 | revision、有效时间 |
| 安全护栏 | 哪些边界不可放宽 | guardrail 标记与策略 |
| 作用域继承 | 默认值从哪里来 | 固定 scope 层级 |
| 业务裁决 | 同层多个例外选哪个 | 显式 businessPriority |
记忆钩子:版本管新旧,作用域管继承,priority 管同层业务意图,护栏管谁都不能越过的底线。
二、规则模型必须表达语义
一条规则至少包含稳定 ruleId、单调 revision、类型、作用域、动作、业务优先级、生效区间和覆盖策略。revision=12 只能替代同一个 ruleId 的 revision 11,不能因为数字更大就压过另一个安全规则。
ruleId: search-timeout-tenant-a
revision: 12
ruleType: timeout
scope: {service: search, endpoint: /query, tenant: A}
businessPriority: 30
overridePolicy: replace
effectiveFrom: 2027-01-01T10:00:00+08:00
expiresAt: 2027-01-01T12:00:00+08:00
规则 ID、修订号和业务优先级必须各自独立。把发布时间或 revision 当成 priority,会让一次普通修订悄悄改变业务裁决关系。
三、按固定作用域树继承
作用域层级应由平台预先定义,例如 平台 -> 环境 -> 服务 -> 接口 -> 租户+接口。覆盖型字段从宽到窄继承,最具体且合法的配置成为结果。条件数量多不等于作用域更具体,否则随意增加一个无关标签就能抢占优先级。
假设超时配置为:平台默认 500ms、搜索服务 300ms、/query 接口 180ms、租户 A 的 /query 为 120ms。租户 A 命中 120ms,其他租户命中 180ms;未配置接口继续继承服务级 300ms。平台应能展示这条继承链,而不是只返回最终数字。
同一作用域内若存在两个普通业务规则,再比较 businessPriority。两条规则作用域和优先级相同、条件又可同时命中,却给出不同动作时,不应靠 ruleId 字典序选一个,而应在发布阶段拒绝歧义。
四、安全护栏不能被业务例外放宽
有些规则不是默认值,而是系统底线,例如实例最大并发、跨地域合规限制、管理接口强认证和支付写入的拒绝策略。它们应标记为不可覆盖约束,业务规则只能进一步收紧,不能因为作用域更具体就放宽。
platformMaxConcurrency = 500
tenantRequestedLimit = 800
effectiveLimit = min(500, 800) = 500
但“全部取最小”也不是通用算法:超时、并发上限可定义上界,路由和 fallback 则有各自语义。每个 ruleType 必须声明是 replace、cap、floor、accumulate 还是其他受控合并方式。
五、累积规则不能被选赢家逻辑吞掉
全局配额、租户配额和接口配额通常需要全部通过;选出一个“最高优先级限流规则”会绕过其他容量边界。相反,超时默认值通常采用最具体合法覆盖。优先级引擎必须先看规则类型的合并策略,再决定是否需要选一个结果。
| 规则类型 | 常见策略 | 示例 |
|---|---|---|
| 超时默认值 | replace/cap | 接口值覆盖服务默认,但不得超过平台上限 |
| 分层配额 | accumulate | 全局、租户、接口三个桶都通过 |
| 并发上限 | cap | 业务值不能突破实例安全上限 |
| 开关 | replace | 最具体合法配置生效 |
这也是为什么全局 priority 数字不可取:不同类型甚至不执行同一种运算。
六、应急优先级必须有生命周期
事故期间可能需要临时关闭非核心功能或收紧流量。应急规则可以在同一作用域内高于普通业务规则,但必须使用预定义动作,并携带原因、审批人、开始时间、TTL 和上一版本。它不能绕过不可覆盖护栏,也不能永久占据最高优先级。
TTL 到期时按声明策略撤销,并验证实例版本、规则命中和业务积压。若事故尚未结束,续期需要重新审批。把 priority=999999 留在配置里而没有所有者和有效期,会把一次止血动作变成长期隐患。
七、解析、发布和多实例一致性
控制面先选择每个 ruleId 的有效 revision,再按规则类型编译继承和护栏,生成带总版本号的不可变快照。实例只有在完整下载、Schema 校验和编译成功后才原子切换;不能逐条更新,否则短时间内可能出现默认值已变、护栏尚未加载的危险组合。
测试至少覆盖典型租户、默认继承、边界值、过期规则和应急撤销。影子解析比较新旧快照的最终值与来源链;看板按快照版本展示覆盖率。explain 输出命中的 ruleId/revision、继承路径、护栏截断和最终结果,方便确认优先级是否符合设计。
八、常见误区与追问
- 误区:revision 越大,业务优先级越高。 revision 只替换同一 ruleId 的旧修订。
- 误区:作用域越具体就能覆盖任何规则。 不可覆盖护栏不允许业务例外放宽。
- 误区:所有规则都选最高 priority。 分层配额等规则需要累积执行。
- 误区:ruleId 排序可以解决同级矛盾。 它只能制造确定结果,不能证明结果符合业务语义。
- 追问:全局安全上限能否临时突破? 不应通过普通优先级突破;需要独立、受审计的容量变更流程。
- 追问:同层同优先级怎么办? 条件可能重叠且动作不同时拒绝发布。
- 追问:怎样解释最终值? 返回修订选择、作用域继承、业务裁决和护栏处理的完整来源链。
九、加强记忆
规则优先级按“选修订、套护栏、走继承、比同层、按类型合并”理解:同一规则先取有效新版本,平台安全边界始终保留,覆盖型配置沿固定作用域树取最具体值,同层例外才比较业务优先级,累积型规则则全部执行。这样既能支持业务定制,也不会让版本号、加载顺序或巨大 priority 绕过系统底线。