多节点权限缓存从写入到共享版本再到主库回源的技术封面

权限传播的核心不是删掉每一份缓存,而是让所有节点认同同一个当前版本。

摘要| Microi吾码AI 分析当前平台源码后发现,多节点权限陈旧问题不能靠缩短 TTL 或重启容器解决。更可靠的边界是:权限事实先写入主库,随后递增按租户隔离的 Redis 授权版本;每个节点先读版本,再使用带版本的用户快照;Redis 不可用时回源主库,不继续相信旧缓存。

① 这不是浏览器缓存,而是授权事实没有换版本

管理员刚撤销角色,A 节点已经拒绝请求,B 节点却仍然放行。最直觉的处理是退出登录、清浏览器缓存、重启 B 节点,甚至清空 Redis。它们可能暂时让现象消失,却没有回答真正的问题:谁负责告诉所有节点,上一份授权快照已经失效?

当前平台把 L1 进程缓存和 Redis L2 都视为性能层,而不是权限事实源。每次外部授权判断先读取当前租户的共享版本;快照 Key 带上这个版本后,旧节点即使还保存着旧对象,也无法再通过新 Key 命中它。

关键判断| 真正要跨节点同步的是一个单调递增的授权版本,而不是逐台机器追杀所有旧缓存对象。

② 一次授权判断依次穿过四层

Redis授权版本、L1快照、L2快照和主库组成的四层读取链

版本先行:没有拿到可信当前版本时,缓存快照不能继续参与授权判断。

第一层是 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租户隔离和角色顺序稳定的本地聚焦测试结果

本轮聚焦测试验证 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 断网、滚动发布或真实多节点压测。

⑦ 上线前别再问“缓存多久”,先检查这五件事

多节点权限缓存生产检查清单

租户隔离、提交后失效、主库回源、契约版本和可观测性共同决定安全边界。

  1. 版本 Key 是否显式包含 OsClient,避免跨租户事实混用。
  2. 用户、角色、菜单和高级表权限的所有写入口是否都会在成功后递增版本。
  3. Redis 异常时是否绕过旧快照并读取主库,而不是静默放行。
  4. 快照结构或解释语义改变时是否提升独立契约版本。
  5. 日志能否区分版本读取失败、快照读取失败和主库冷加载。

TTL 仍然有价值,它限制不可达旧对象占用空间;但 TTL 不是权限撤销协议。把“清缓存、重启节点”写进日常操作手册,只能说明系统尚未建立可靠的版本边界。

AI 声明| 本文社交平台技术概念卡片底图由AI生成并经确定性程序叠加准确中文;长文架构图、源码分析与本地测试证据来自当前工作区,概念图不冒充运行证据。

Logo

宁波官方开源宣传和活动阵地,欢迎各位和我们共建开源生态体系!

更多推荐