Redis 业务隔离方案拆解:没有表的世界里,两个 app 怎么共用 Redis 业务隔离方案拆解没有表的世界里两个 app 怎么共用给 K3s 上的 Redis 做 GitOps 规范化的时候我把redis-deployment仓库推上去Redis 稳稳跑在 free-arm-vm 上Kong 的 6379 Stream 也通了。然后问题来了这套 Redis 后面是要给多个业务共用的。第一个 app 用得很爽第二个 app 接进来的时候隔离怎么做我先问了自己一个很基础的问题Redis 到底有没有表这一问牵出一整条问题链——没有表的话那业务 A 的数据和业务 B 的数据靠什么分开这篇文章就是这条问题链的完整记录从数据模型聊到四种隔离方案从 requirepass 聊到 ACL最后把我自己踩的一个认知坑跨 db 原子性也摊开来讲。一、 先回答最基础的问题Redis 没有表那它有什么没有。Redis 是最典型的 NoSQL 键值库没有 schema、没有表、没有行/列、没有 SQL。数据模型就一句话key - valuekey 永远是字符串value 有类型——string、hash、list、set、zset、stream还有 bitmap、HyperLogLog 这些边角料。关系型数据库和 Redis 的差别本质是你用什么去找数据Redisuser:1 - hashkey 就是查询入口GET user:1按 key 直接取关系型数据库users 表id | name | ageschema 强约束SELECT * FROM users WHERE id 1按列查询关系型是表 主键 SQL 查询的心智模型Redis 是key 命名 数据结构的心智模型。没有表不是 Redis 的缺陷是它的设计——把查询路径砍到最短换来 O(1) 级别的读写。但没有表直接带出一个问题表都没了业务之间的数据怎么划地盘这就引出了第二个问题。二、 两个 app 共用 Redis业界就这四种玩法把能想到的方案全列出来就四种按隔离强度从弱到强方案隔离手段本质1. key 前缀命名空间app1:xxx/app2:xxx纯约定2. 逻辑 databaseSELECT 0~15键空间分开3. ACL 用户隔离Redis 6 用户 key pattern权限强制4. 独立实例各自进程/端口/持久化物理隔离各自实例同一个 Redis 实例app1:user:1app2:user:1db 0db 1user: app1~app1:*user: app2~app2:*Redis 实例 ARedis 实例 BApp 1App 2下面逐个拆优缺点都摆出来。方案一key 前缀命名空间最简单零成本app1:user:123、app2:user:123靠 key 名前缀做逻辑分区。SCAN app1:*就能扫出某个业务的所有 key。优点实现零成本不需要任何配置Cluster 模式下唯一的选择后面会解释为什么缺点纯靠约定没有强制力。任何人写 key 忘了带前缀数据就串了。代码 review 拦不住所有手滑文档约定挡不住新同学。方案二逻辑 databaseSELECT 0~15一个实例默认 16 个 dbdatabases 16配置每个 db 是独立键空间FLUSHDB只清当前库TTL 过期互不干扰。看起来表的意思有了——把每个 db 当成一张表业务 A 用 db 1业务 B 用 db 2。优点配置一行搞定天然把键空间分开缺点一堆而且都很致命见第五章专门拆方案三ACL 用户隔离Redis 6Redis 6 引入真正的 ACL。给每个业务建一个用户带自己的密码和 key 权限ACL SETUSER app1 on pass1 ~app1:* all -dangerous ACL SETUSER app2 on pass2 ~app2:* all -dangerousapp1 只能碰app1:*前缀的 key碰app2:*直接NOPERM。这是权限层面的强制隔离不是约定。优点真正摸不到的隔离还能顺带限制危险命令-dangerous禁掉 FLUSHALL/KEYS 之类缺点Redis 6 以下没有配置比前缀麻烦一点方案四独立实例各起各的进程、端口、持久化物理隔离。优点彻底干净故障域、容量、安全边界全部分开缺点多一套资源、多一份运维。但这是大公司的最终答案见第六章三、 requirepass 的真相一个密码 一个身份在聊方案三之前先澄清一个常见误解。很多人还在用老式认证# redis.conf requirepass my-secret-password这玩意儿没有用户概念——全局一个密码所有客户端AUTH上来都是同一个身份default 用户权限一模一样没有粒度可言。技术上它其实是 ACL 的兼容简写等价于ACL SETUSER default on my-secret-password ~* all也就是说requirepass “给 default 用户设了个密码且给了全部权限”。所以 requirepass 模式下谈业务隔离只有前缀约定一条路因为大家都是一个身份、全权限。要真正的多用户隔离只能上 ACL。四、 多 db 为什么是坑五个硬伤 一个认知反转方案二看起来最像表但它其实是四种方案里最不该用的。逐条拆每条都有实测或源码证据。4.1 它不是安全边界db index 没有权限概念。只要客户端能连上过了认证SELECT 3随便切没有任何机制能限制app1 只能碰 db 1。ACL 也救不了。ACL 的 key pattern 是跨 db 生效的——我实测了一把。建一个用户只给~app1:*redis-cli ACL SETUSER app1 on pass1 ~app1:* all # db0 里 redis-cli --user app1 --pass pass1 -n 0 SET app1:x v # (OK) redis-cli --user app1 --pass pass1 -n 0 SET app2:x v # (NOPERM this user has no permissions to access one of the keys used as arguments) # 切到 db1 再试 redis-cli --user app1 --pass pass1 -n 1 SET app1:y v # (OK) redis-cli --user app1 --pass pass1 -n 1 SET app2:y v # (NOPERM ...)看到了吗db1 里同样被~app1:*约束。ACL 的 key pattern 不区分 db所以db1 给 app1、db2 给 app2这种 per-db 授权根本做不出来。ACL 里顶多禁掉select命令但那是谁都不能切不是每个人只能切到自己的 db。结论多租户共享时db 隔离纯属心理安慰。4.2 Cluster 模式直接把这条路堵死Redis Cluster只有 db 0SELECT直接报错。这意味着上了多 db将来想从单实例平滑扩容到 Cluster就得先把所有 key 迁回 db 0、改造应用、加前缀——等于推倒重来。db 方案把将来的架构路线堵死了这是它最大的原罪。官方在 Cluster 里直接砍掉 db 0 以外的东西等于官方表态这玩意儿就是历史遗留。4.3 资源完全不隔离键空间隔离是假象键空间分开了但底层全是共享的。最隐蔽的坑是内存maxmemory 是实例级的。db 1 写爆触发 eviction 时LRU 淘汰是全局的——可能把 db 2 里最近没访问的 key 干掉。db 2 的数据被 db 1 的流量间接淘汰出事故的时候根本想不到是这个原因AOF rewrite、RDB 快照、大 key 删除、慢命令——全是实例级的动静一个 db 的折腾所有 db 一起扛阻塞操作有人对 db 3 跑KEYS *整个实例所有 db 全部卡住4.4 连接池串库经典事故SELECT是连接级状态。很多客户端库尤其懒加载/异步连接池复用时不会重置 db index请求 A 选了 db 1连接归还池子请求 B 复用同一连接、以为是 db 0结果读写到 db 1。这种 bug 随机复现极难排查。4.5 运维粒度太粗监控INFO 按实例粒度看不出哪个 db 内存爆了、哪个 db 有热点备份恢复RDB/AOF 是整实例的没法只备份 db 2恢复也是整库覆盖复制slave 复制全部 dbdb 0 的数据量拖慢所有 db 的同步误操作FLUSHALL一条命令把 16 个 db 全清了db 隔离在这种事故面前等于零4.6 认知反转跨 db 原子操作其实可以做写到这里必须承认一个我自己的错误。我一开始笃定地跟人说“SELECT不能进MULTI/EXEC所以跨 db 的更新做不了原子操作。”然后我去翻了 Redis 7.2 的源码src/commands/select.jsoncommand_flags:[LOADING,STALE,FAST]没有NO_MULTI标志。也就是说 SELECT 允许进事务。为了确认我直接在本机装了个 Redis 7.0.15 实测127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SELECT 1 QUEUED 127.0.0.1:6379 SET mykey 1 QUEUED 127.0.0.1:6379 EXEC 1) OK 2) OK能跑。跨 db 的原子操作在 MULTI 里是可行的。再测一脚Lua 脚本里 SELECT 居然也允许而且真的切换了 dbredis-cli EVAL redis.call(SELECT, 1); redis.call(SET, scriptkey, v); return done 0 # done redis-cli -n 0 EXISTS scriptkey # (integer) 0 redis-cli -n 1 EXISTS scriptkey # (integer) 1这跟我记忆里的SELECT is not allowed in script完全相反——至少在 Redis 7.x 上脚本里 SELECT 是合法的执行后连接还停在切过去的 db。所以跨 db 做不了原子操作这条不成立我之前的说法是错的。多 db 的坑很多但这条不算。顺带再补一个查证网上有传言说 Redis 8 引入了类似表/字典的命名空间特性我特意翻了 redis/redis 仓库 8.0 分支的00-RELEASENOTES——8.0 的新特性是搜索索引、vector sets、I/O 多线程这些没有任何表/字典命名空间的东西。没有表这个心智模型在最新版里依然成立。五、 大公司生产环境里到底怎么选答案其实很干脆独立实例按业务拆分实例/集群是大公司的主流共享场景用前缀 ACL多 db 基本被禁用。大公司要的是这四样只有独立实例能全部满足故障域隔离一个业务的 Redis OOM、主从切换、大 key不能把别的业务一起拖死容量隔离每个业务独立规划内存、独立扩容互不抢资源安全合规支付数据和缓存不是一个安全等级一个实例一个安全边界才好审计运维自治各自升级、各自持久化策略、各自监控告警云上的形态就是每业务一个云 Redis 实例/集群阿里云、AWS ElastiCache、GCP Memorystore 都是这个玩法大厂内部往往还做 Redis 平台按业务自助申请开实例。共享实例的场景小团队、成本敏感期里主流组合是key 前缀 ACL前缀做逻辑组织ACL 做强约束双保险。完整的选择逻辑是生产/多团队/合规否共享省钱是否多个 app 要共用 Redis?业务间要强隔离?独立实例每业务一套Redis ≥ 6?前缀 ACL 双保险前缀约定唯一选择db 方案忘掉它一句话总结业务间隔离靠实例实例内多租户靠前缀 ACLdb 基本不存在。六、 复盘没有表是 Redis 设计的一部分不是缺陷。关系型按列查Redis 按 key 取业务隔离的手段也完全不同关系型靠 schema/表Redis 靠命名空间。四种隔离方案按强度排前缀 db ACL 独立实例但强度不等于正确性——db 看似隔离键空间实则既不安全也不隔离资源还是唯一堵死 Cluster 扩容路的方案。requirepass 没有用户概念本质是给 default 用户设密码 全权限。要真隔离Redis 6 上 ACL 是必须的。能实测就不要靠记忆。我信誓旦旦说SELECT 不能进事务源码 实测双重打脸。Redis 这种可以本地秒起的软件一个redis-server加几条redis-cli就能把结论钉死比翻文档靠谱。选型跟着规模走一个实例自用前缀够了多业务共享前缀 ACL多团队/合规独立实例。别一上来就上重方案也别在 db index 上抠。