权限改了,为什么另一台节点还在放行?

权限传播的核心不是删掉每一份缓存,而是让所有节点认同同一个当前版本。
摘要| Microi吾码AI 分析当前平台源码后发现,多节点权限陈旧问题不能靠缩短 TTL 或重启容器解决。更可靠的边界是:权限事实先写入主库,随后递增按租户隔离的 Redis 授权版本;每个节点先读版本,再使用带版本的用户快照;Redis 不可用时回源主库,不继续相信旧缓存。
✦ ① 这不是浏览器缓存,而是授权事实没有换版本
管理员刚撤销角色,A 节点已经拒绝请求,B 节点却仍然放行。最直觉的处理是退出登录、清浏览器缓存、重启 B 节点,甚至清空 Redis。它们可能暂时让现象消失,却没有回答真正的问题:谁负责告诉所有节点,上一份授权快照已经失效?
当前平台把 L1 进程缓存和 Redis L2 都视为性能层,而不是权限事实源。每次外部授权判断先读取当前租户的共享版本;快照 Key 带上这个版本后,旧节点即使还保存着旧对象,也无法再通过新 Key 命中它。
关键判断| 真正要跨节点同步的是一个单调递增的授权版本,而不是逐台机器追杀所有旧缓存对象。
✦ ② 一次授权判断依次穿过四层

版本先行:没有拿到可信当前版本时,缓存快照不能继续参与授权判断。
第一层是 Redis 中按 OsClient 隔离的版本;第二层是当前 API 进程的短期 L1;第三层是跨节点共享的 Redis L2;第四层才是主库冷加载。正常路径追求快,但故障路径必须优先正确。
- L1 只服务当前进程,滚动发布或节点重启都可以丢。
- L2 保存跨节点用户快照,但 Key 必须包含当前版本。
- 主库保存用户状态、角色、菜单、操作权限与数据范围的权威事实。
- 无 `_SysMenuId` 的历史调用也只能从同一快照推断,不能绕开行级范围。
✦ ③ Epoch 不删除旧快照,只改变它是否还能被到达
共享版本最精妙的一点,是不要求权限保存时同步扫描并删除所有用户快照。假设版本从 41 增到 42,新请求构造的是 v42 的 Key;v41 对象仍可能在 Redis 中等待 TTL 回收,但已经不在新请求的可达路径里。
versionKey = Microi:{OsClient}:FormEngineAuthz:Version
snapshotKey = Microi:{OsClient}:FormEngineAuthz:Snapshot:v2:{epoch}:{userHash}:{roleSetHash}
为什么还带契约版本 v2| Redis 会跨进程重启和滚动发布保留数据。快照结构新增安全字段时,契约版本必须一起提升,避免缺失字段被反序列化成错误默认值。
✦ ④ 权限写入与版本递增的顺序不能颠倒

先有新事实,再切换所有节点读取的新版本。
如果先递增版本、再提交数据库,另一个节点可能立刻用新版本冷加载,却读到旧事实;如果写入成功后忘记递增版本,所有节点仍会继续命中旧快照。正确边界是:控制面写入完成,关联的用户级别、角色、菜单或高级表权限同步完成,然后再递增共享版本。
var count = dbSession.Update(model);
if (count > 0) {
SyncUserLevelsForRole(dbSession, model.Id);
await FormEngineAuthorizationCache.InvalidateAsync(osClient);
}
这里的重点不是这几行代码本身,而是提交顺序表达的安全语义:新事实必须先存在,版本开关才能让其它节点来读取它。
✦ ⑤ Redis 出问题时,授权宁可慢也不能旧
读取共享版本发生异常时,最危险的降级是继续复用进程内最后一份快照。那会把缓存服务故障放大成权限撤销失效。当前实现选择返回空版本,让调用方绕过缓存并从主库读取;授权可能变慢,但不会把旧允许结果当成真。

本轮聚焦测试验证 Key 的租户隔离、角色集合顺序稳定与快照契约版本。
Redis version read failed
→ do not use stale snapshot
→ query primary database
→ evaluate menu, action, table and row scope
✦ ⑥ 租户、用户与角色集合为什么都要进入 Key
授权版本按租户隔离,只解决了不同 OsClient 不共享同一轨道;快照还要区分用户和有效角色集合。角色顺序不应影响结果,因此先去重、归一化并排序后再哈希;用户标识也哈希,避免把敏感 ID 直接暴露在 Redis Key 中。
- 同角色集合不同顺序生成同一 Key,减少无意义重复缓存。
- 不同租户即使版本数字相同,Key 也完全不同。
- 用户状态和级别进入快照,禁用账号不能只靠前端退出。
- 菜单 SqlWhere、SqlJoin 与 JoinTables 仍要进入最终 SQL,不能只缓存允许/拒绝。
测试边界| 本轮 Release 聚焦测试 1/1 通过,证明 Key 稳定与租户隔离;它不等同于生产 Redis 断网、滚动发布或真实多节点压测。
✦ ⑦ 上线前别再问“缓存多久”,先检查这五件事

租户隔离、提交后失效、主库回源、契约版本和可观测性共同决定安全边界。
- 版本 Key 是否显式包含 OsClient,避免跨租户事实混用。
- 用户、角色、菜单和高级表权限的所有写入口是否都会在成功后递增版本。
- Redis 异常时是否绕过旧快照并读取主库,而不是静默放行。
- 快照结构或解释语义改变时是否提升独立契约版本。
- 日志能否区分版本读取失败、快照读取失败和主库冷加载。
TTL 仍然有价值,它限制不可达旧对象占用空间;但 TTL 不是权限撤销协议。把“清缓存、重启节点”写进日常操作手册,只能说明系统尚未建立可靠的版本边界。
AI 声明| 本文社交平台技术概念卡片底图由AI生成并经确定性程序叠加准确中文;长文架构图、源码分析与本地测试证据来自当前工作区,概念图不冒充运行证据。
更多推荐




所有评论(0)