主题
Redis 平台化治理:配置、查询与审计
一句话:Redis 平台化治理把「应用连哪个集群、谁能查什么、每次操作留下什么」从口头约定变成可发布、可对账、可回滚的元数据与流程;它换来自助和统一,代价是多了一跳查询代理、一套审批,以及一份必须持续维护的清单。
适用版本:Redis 6.0+(ACL 自 6.0 提供);命令差异单独标注,如 ZRANGE 的 BYSCORE 需 6.2+。类名与字段名为示例命名。
为什么需要
集群少的时候,连接信息写在应用配置文件里就够了。集群到几十上百个之后,问题变成:应用在不同环境连错集群、密码靠聊天工具传递、排查时有人直接 redis-cli 上生产执行 KEYS *、事后说不清谁在什么时候看过哪个 key。这些问题的共同点是「没有登记、没有边界、没有记录」,不是 Redis 本身不会用。
平台化做三件事:把「应用—逻辑集群—物理集群」登记成元数据;把变更从「改完就生效」变成「有 diff、有审批、可回滚」;把人的临时查询收进只读白名单和审计。它不替代 Redis ACL,也不承载应用运行时的数据流量。
核心机制
治理对象。 平台管的第一层不是命令,而是「什么归谁」:
| 对象 | 登记内容 | 判据 |
|---|---|---|
| Key | 环境:业务域:用途,如 prd:order:cart:1001 | 写入校验 + 离线抽样 |
| 逻辑集群 | 逻辑名到物理分片、主备的映射 | 应用只引用逻辑名,不写 IP |
| 归属 | 应用、负责人、读写权限、环境 | 无负责人的配置不许发布 |
| TTL | 缓存 key 必须有 TTL,常驻需豁免理由 | 无 TTL 的 key 进治理报表 |
平台侧用 RedisKeyRegistry 维护前缀与 TTL 规则,用 LogicalCluster 表示逻辑名到物理拓扑的映射(示例命名)。这层间接是平台的价值:扩容、迁移、切换只改映射,应用不改配置;代价是映射成了新的真相来源,必须和集群实际拓扑对账。
配置模型。 配置放在配置中心(Apollo、Nacos 等),按 namespace/group 分环境、分用途(不同产品术语略有差异);平台做编辑、校验、发布和展示,配置值仍以配置中心为准。每次发布要有版本号、变更人、生效范围,发布记录可追溯。密码、token 以密文存储,详情页只返回掩码,解密在使用侧完成。配置中心若自带审批和灰度就优先用,避免平台和配置中心两套状态。
发布流程。 发布改的是「应用连哪个逻辑集群、用什么超时和连接池参数」,不是 redis.conf 的 maxmemory;服务端参数走另一条变更流程,查询入口禁止 CONFIG SET。顺序是:
「发布」和「生效」是分开的:发布只改配置中心里的意图,客户端刷新后才切换连接。回滚因此不是瞬时的,要等一个刷新周期才能确认全部恢复。
灰度按应用或实例比例放量,观察错误率、连接数和延迟;异常时回滚版本。注意回滚只恢复路由,不恢复数据:新集群如果是空库,切回去也找不回已经写丢的数据。
对账。 声明和现实会分叉:节点换了 IP、集群下线了但配置还在、应用发版后仍连着旧集群。规则只有三条:
- 意图以配置为准:应用该连哪里由登记决定,不能因为监控看到它连了别处就自动改配置。
- 事实以集群为准:分片、主从、节点地址以
CLUSTER NODES、INFO replication为准,平台不能凭配置去改集群。 - 冲突是事件:两边不一致时告警给负责人,人工确认后改配置或改拓扑;自动覆盖会把一次误配置固化成事实。
对账每天跑,比对三份数据:登记的「应用→逻辑集群」、逻辑集群到物理节点的映射、连接分析里实际连上来的来源(按地址反查应用)。常见漂移是迁移后旧连接未清理、节点替换后配置未更新、逻辑集群指向已下线实例;连续漂移超过一个发布周期就升级为告警。
权限。 平台账号是人的身份,应用身份是服务自己的凭证,两者不能互相冒用。授权粒度到「环境 + 集群 + 操作」,三级够用:
- 只读:看配置、看监控、执行只读查询;默认级别,按应用授权。
- 运维:编辑草稿、非生产发布、发起生产变更;生产发布需审批。
- 管理:授权、生产发布审批、紧急通道;双人复核,操作留痕。
生产变更默认双人:一人提交、一人审批。紧急 break-glass 允许单人临时提权,但要填原因、关联故障单、限定时间窗,24 小时内复核。自助和护栏不矛盾:只读查询做成自助,写操作和发布留给审批,默认最小权限,所有变更可回滚。
查询边界。 查询入口只接受只读命令,先按命令白名单校验,再校验参数个数(下面的片段只演示 MGET 上限);白名单随 Redis 版本维护(例如 ZRANGE 在 6.2 起支持 BYSCORE,参数形态变了)。允许 GET/MGET/HGET/LRANGE/ZRANGE/SCAN 等,拒绝 KEYS、FLUSHALL、FLUSHDB、CONFIG、DEBUG、EVAL、MONITOR(它会把每条命令复制给监控连接,生产尤其要禁)。同时限制返回条数、单 key 大小和执行时长,超限截断或拒绝。链路是「账号鉴权 → 命令白名单 → 限额 → 代理执行 → 审计」,任一环节拒绝都要记审计。
审计。 一条审计至少记录:请求 ID、时间、平台账号、来源应用、来源 IP、环境、逻辑集群、物理节点、命令与参数(脱敏)、返回行数/字节数、耗时、结果码、审批单号、是否 break-glass。值本身不落库——审计日志存了手机号或 token,它自己就成了泄露源。长参数截断,敏感前缀(*:phone:*、*:token:*)的 key 名做哈希或掩码。保留期按合规要求,常见 6–12 个月(经验值);日志只追加不修改。异常查询看四类:单账号高频、同一 key 高频、返回体过大、访问敏感前缀。
最小上手
只读白名单校验(Java 9+,示例命名,省略 import 与错误处理;argCount 不含命令名):
java
public final class ReadOnlyCommandGuard {
private static final Set<String> ALLOWED = Set.of(
"GET", "MGET", "STRLEN", "HGET", "HMGET", "HGETALL",
"LLEN", "LINDEX", "LRANGE", "SCARD", "SISMEMBER",
"ZCARD", "ZRANGE", "ZSCORE", "TYPE", "TTL", "EXISTS", "SCAN");
private static final Set<String> DENIED = Set.of(
"KEYS", "FLUSHALL", "FLUSHDB", "CONFIG", "DEBUG",
"EVAL", "EVALSHA", "MONITOR", "SHUTDOWN", "ACL");
public static String check(String command, int argCount) {
String cmd = command == null ? "" : command.trim().toUpperCase(Locale.ROOT);
if (DENIED.contains(cmd) || !ALLOWED.contains(cmd)) {
return "REJECT: not in read-only allowlist";
}
if ("MGET".equals(cmd) && argCount > 100) {
return "REJECT: too many keys";
}
return "OK";
}
}一次查询与一次拒绝:
http
POST /api/redis/query
{"appId":"order-service","logicalCluster":"order-cache","env":"prd","command":"GET","args":["prd:order:cart:1001"]}
200 {"data":"3","rows":1,"elapsedMs":3,"returnedBytes":1}
POST /api/redis/query
{"appId":"order-service","logicalCluster":"order-cache","env":"prd","command":"KEYS","args":["*"]}
403 {"error":"command not allowed: KEYS","requestId":"q-7f3c"}KEYS * 被拒后不要换账号重试;按前缀找 key 用 SCAN 0 MATCH prd:order:* COUNT 100,保留游标并由平台限制总条数,不能把 MATCH * 当 KEYS 用。
发布 diff 的示例格式:
diff
namespace: redis-config / env: prd / group: order
key: order-service.order-cache version: 42 -> 43
changedBy: alice approvedBy: bob
- cluster: order-cache-v1 nodes: 10.0.1.11:6379,10.0.1.12:6379
+ cluster: order-cache-v2 nodes: 10.0.2.11:6379,10.0.2.12:6379
+ ttlPolicy: cache-onlydiff 要同时露出「改了什么」和「谁改、谁批、从哪个版本到哪个版本」;只显示新值不算 diff。
生产动作
危险命令与限额。 白名单在代理层拒绝,Redis 侧再用 ACL 兜底(6.0+),例如查询账号只给 +@read -@write -@dangerous,该账号的网络策略只放行代理到集群;应用运行时用独立账号和网段。MGET 单次最多 100 个 key。下面是起点值,按基线校准:
| 限制项 | 起点值 | 超限动作 |
|---|---|---|
| 单次返回 | 1000 行 | 截断并提示用 SCAN 分页 |
| 单 key/value | 1 MB | 拒绝,改用 STRLEN/GETRANGE |
| 单次耗时 | 2 s | 取消并告警 |
| 单账号并发/频率 | 5 并发、10 次/秒 | 排队或拒绝 |
审计、告警、复核与定期治理。 值不落日志,参数截断到 256 字符,敏感前缀的 key 名哈希。告警起点:单账号 > 60 次/分钟、同一 key > 100 次/分钟、单次返回 > 1 MB 或 > 1000 行、被拒命令 > 5 次/分钟、非工作时间的生产管理操作。生产发布走「提交 → 审批 → 灰度 → 全量」,灰度观察 10–30 分钟(起点);回滚是重新发布上一版本,不是手改配置。每天跑配置漂移对账;每周抽样无 TTL 和命名不合规的 key,按负责人派单;每月出审计报表(查询量、拒绝量、break-glass、漂移项);权限每季度复核一次,离职/转岗即时回收,break-glass 24 小时内复核。排障按三条路径:
- 配置不生效 → 查 namespace/group 是否选对 → 是「保存草稿」还是「已发布」 → 客户端刷新日志和本地缓存;三者都正常再怀疑应用读了旧配置文件。
- 查询被拒 → 查白名单(命令拼写、版本)→ 权限(环境/集群/应用)→ 限额(条数/大小/耗时)→ 集群连通性;审计结果码能区分这四类。
- 配置与集群不一致 → 先跑对账,确认是配置旧、拓扑变,还是应用仍连旧地址;不要改配置让它「看起来一致」。
边界
- 平台账号不是 Redis ACL。 平台鉴权在代理层,直连或绕过代理的客户端不受约束;真正的兜底是 Redis ACL、网络策略和密码轮换。
- 查询代理多一跳,也是单点。 同机房延迟增量以实测为准,它还会成为容量瓶颈;要多实例、限流、熔断,并且只服务人的临时查询,不接应用运行时流量。配置中心同样不能是硬依赖:不可用时客户端应使用最后一次成功拉取的本地快照。
- 审计不能记值。 记了值可能泄露,记太少又查不清;折中是记 key、命令、条数、字节数、耗时和结果码,值只在当次请求的内存里存在。
- 回滚不等于恢复数据。 配置回滚只恢复路由;空集群、写丢失、数据不一致要靠备份和迁移流程。
- 不要用平台绕过变更管理。 break-glass 是带审计的例外,不是「审批太慢」的替代;频繁使用说明流程该优化。
- 元数据会腐烂。 负责人离职、TTL 豁免过期、逻辑集群改名,都会让平台给出错误答案;没有对账和复核,可信度随时间下降。
相关:08-集群模式、10-大Key问题、15-Redis监控指标与告警、99-生产检查清单。
讲清楚
平台化治理解决的不是 Redis 性能,而是集群多了以后配置、权限和操作记录散落在人和聊天里的问题。它把逻辑集群、Key 规范、TTL 和负责人登记成元数据,用配置中心的版本和发布流程管理变更,用只读白名单和限额约束人的查询,再用审计回答谁在什么时候动了什么。关键取舍在边界:平台账号不等于 Redis ACL,查询代理会多一跳且可能是单点,审计日志不能成为敏感数据的副本。它适合自助查询和配置治理,不适合承载应用运行时流量;意图以配置为准、拓扑以集群实际为准,冲突时告警而不是自动覆盖。