这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 当 Redis 从“某个服务的缓存”变成平台级组件后,问题就不再只是命令和性能,而是边界:哪些系统可以共用一套 Redis,哪些数据必须隔离,哪些场景适合 Redis,哪些场景应该交给 MySQL、Kafka、Elasticsearch 或对象存储。
本章讨论 Redis 在大型系统中的架构位置、隔离策略、冷热分层、数据生态和替代方案比较。
27.1 先定义 Redis 的角色
同一套 Redis,在不同系统里可能承担完全不同的角色:
| 角色 | 典型数据 | 可靠性要求 | 失败影响 |
|---|---|---|---|
| 缓存 | 商品详情、用户资料 | 可丢、可重建 | 数据库压力升高 |
| 会话存储 | 登录态、Token | 中等 | 用户重新登录 |
| 计数器 | 浏览量、点赞数 | 允许少量误差 | 数据略旧 |
| 排行榜 | 热榜、积分榜 | 允许重算 | 排名短时间不可用 |
| 分布式锁 | 任务互斥、防重 | 高 | 重复执行或业务阻塞 |
| 轻量队列 | 异步任务、事件流 | 与业务绑定 | 任务延迟或丢失 |
| 状态存储 | 规则、限流窗口 | 高 | 系统误放行或误拒绝 |
| 主数据存储 | 订单、账务 | 很高 | 通常不适合 |
架构评审时应明确每个实例或每个 DB 的角色。最常见的问题是:一个 Redis 实例里同时有低价值缓存、高价值锁和大 value 会话,某个慢查询或内存淘汰影响所有业务。
推荐隔离粒度:
按环境隔离:dev / test / staging / prod
按重要性隔离:核心交易 / 一般业务 / 离线分析
按延迟形态隔离:在线低延迟 / 批量任务 / 异步任务
按数据所有权隔离:商品、交易、用户、社区
27.2 多租户与资源隔离
27.2.1 常见隔离方案
| 方案 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| DB 编号隔离 | 每个业务使用不同 Redis DB | 管理简单 | CPU、内存、网络仍共享 |
| key 前缀隔离 | 同一 DB 内按前缀划分 | 灵活 | 只能逻辑隔离,容易互相影响 |
| 实例隔离 | 每个业务独立实例 | 故障隔离最好 | 成本和运维对象变多 |
| Cluster 分片隔离 | key 前缀路由到不同分片 | 局部隔离 | 仍共享节点和网络 |
| 代理多租户 | 云厂商或 Proxy 限流配额 | 可计量 | 依赖代理能力 |
Redis 的 DB 不是物理隔离。SELECT 0 和 SELECT 1 共享同一个进程的 CPU、内存淘汰策略、持久化和网络带宽。因此 DB 隔离只适合开发、测试或弱相关业务,不适合核心与非核心混部。
27.2.2 key 前缀与路由
多租户 key 设计:
tenant:{tenantId}:app:{appName}:object:{objectId}
示例:
tenant:1001:mall:product:base:20001
tenant:1002:mall:product:base:20001
如果使用 hash tag 控制 Cluster 槽位:
tenant:{1001}:lock:order:30001
tenant:{1001}:order:state:30001
同一个 hash tag 会落在同一个节点。适合需要事务、Lua 或管道原子操作的相关 key,但会牺牲分片均衡,只应小范围使用。
27.2.3 配额与限流
平台化 Redis 应该提供租户级配额:
| 控制项 | 建议 |
|---|---|
| key 数量 | 按前缀统计,限制增长 |
| 内存 | 通过监控和代理限制 |
| QPS | 应用客户端或代理限流 |
| value 大小 | SDK 拦截大 key |
| 命令类型 | 禁用 KEYS、FLUSHALL、全量 SMEMBERS |
| 连接数 | 每应用设置连接池上限 |
一个统一的 Cache SDK 可以在入口做治理:
public <T> T execute(String key, RedisCallback<T> action) {
tenantQuota.checkKeyPrefix(key);
if (key.getBytes(StandardCharsets.UTF_8).length > 512) {
throw new CacheKeyTooLongException(key);
}
return action.execute();
}
大 value 校验建议在序列化后执行:
byte[] payload = serializer.serialize(value);
if (payload.length > maxBytes) {
metrics.tooLarge(prefix);
throw new CacheValueTooLargeException(prefix, payload.length);
}
27.3 冷热分层与数据生命周期
Redis 的价值在于低延迟访问热数据。把冷数据长期留在 Redis,既浪费内存,也会增加 RDB、AOF 重写、备份和迁移成本。
27.3.1 数据分级
| 等级 | 特征 | 推荐存储 |
|---|---|---|
| 极热 | 秒级到分钟级大量访问 | 应用本地缓存、Redis |
| 热 | 常态高访问 | Redis |
| 温 | 偶尔访问 | MySQL、PostgreSQL |
| 冷 | 合规或审计访问 | 对象存储、归档库 |
| 历史分析 | 批处理扫描 | 数据仓库、湖仓 |
生命周期设计示例:
商品详情:
本地缓存 3 秒
Redis 1 小时
MySQL 永久或按业务归档
用户行为:
Redis 聚合 5 分钟
Kafka 保留 3 天
数仓分区保留 1 年
订单数据:
Redis 只缓存详情 10 分钟
MySQL 在线保留 2 年
对象存储归档 7 年
27.3.2 冷热迁移策略
常见迁移方式:
- TTL 自动清理;
- 定时任务扫描过期数据;
- 访问频率标记,低频数据降级;
- binlog 同步温库;
- 分析结果回写 Redis 热层。
访问频率可以用 ZSet 记录:
public void touch(Long productId) {
String key = "mall:product:access:" + LocalDate.now();
redis.opsForZSet().incrementScore(key, String.valueOf(productId), 1);
redis.expire(key, Duration.ofDays(3));
}
每日计算 Top N 写入热集合:
@Scheduled(cron = "0 20 1 * * ?")
public void refreshHotProducts() {
Set<String> hotIds = redis.opsForZSet()
.reverseRange("mall:product:access:" + LocalDate.now().minusDays(1), 0, 4999);
hotCacheService.replaceAll(hotIds);
}
冷热分层的核心不是自动越复杂越好,而是让每个数据都有明确生命周期和退出机制。
27.4 Redis 与数据库、消息和搜索的组合
27.4.1 Redis + MySQL
这是最常见的组合:
flowchart LR
App[应用] --> Redis[(Redis 缓存)]
Redis -- miss --> App
App --> MySQL[(MySQL 事实源)]
MySQL --> CDC[Canal / Debezium]
CDC --> MQ[Kafka]
MQ --> Invalidator[缓存失效服务]
Invalidator --> Redis
职责划分:
| 关注点 | Redis | MySQL |
|---|---|---|
| 数据完整性 | 不承诺完整 | 事实源 |
| 事务 | 有限事务,不回滚 | ACID 能力强 |
| 查询模型 | key 查找为主 | SQL、索引、关联 |
| 持久化 | 可配置,有丢失窗口 | 稳健持久化 |
| 容量成本 | 内存高 | 磁盘低 |
| 响应延迟 | 亚毫秒到毫秒 | 毫秒到几十毫秒 |
Redis 不应替代 MySQL 的唯一键、外键、事务和审计能力。尤其账务、订单、支付状态,必须有数据库约束兜底。
27.4.2 Redis + Kafka
Redis 和 Kafka 都能传递异步消息,但定位完全不同。
| 维度 | Redis Stream | Kafka |
|---|---|---|
| 定位 | 轻量事件流、任务队列 | 海量分布式日志 |
| 持久化 | 内存为主,受 maxmemory 限制 | 磁盘顺序写,可长期保留 |
| 吞吐 | 实例能力限制 | 分区水平扩展 |
| 回放 | 受保留策略和内存约束 | 可按 offset 长期回放 |
| 消费组 | 支持 | 成熟 |
| 生态 | 简单 | Connect、Streams、Schema Registry |
推荐边界:
Redis Stream:任务队列、短事件、小规模解耦、延迟低且数据量可控
Kafka:业务事件、CDC、日志、埋点、跨系统数据管道、大规模回放
常见组合:
1. Redis 做秒杀原子扣减,Kafka 承接订单创建事件
2. Redis 做实时计数,Kafka 记录原始行为
3. Redis 做应用侧缓冲,批量写入 Kafka
4. Kafka 事件触发 Redis 缓存失效或预热
27.4.3 Redis + Elasticsearch
| 关注点 | Redis Search / Elasticsearch |
|---|---|
| 数据量 | Redis 受内存约束,ES 可承载大规模索引 |
| 查询能力 | Redis Search 适合轻量全文和二次筛选,ES 查询语义更丰富 |
| 延迟 | Redis 低且稳定,ES 取决于索引和查询复杂度 |
| 运维 | Redis 简单,ES 集群和存储治理更复杂 |
| 生态 | Redis Stack 模块,ES 有成熟分析和查询生态 |
常见架构:
MySQL 存储事实 -> Elasticsearch 支持搜索 -> Redis 缓存搜索结果或热点文档
不要把 Redis 当通用搜索引擎。如果一个系统需要复杂布尔查询、聚合分析、分词、 synonyms、高亮和大规模索引,Elasticsearch 通常更合适。
27.4.4 Redis + 对象存储
对象存储适合图片、视频、压缩包、日志归档:
Redis 保存元数据、签名 URL、热点计数
对象存储保存文件内容
示例:
media:metadata:10001 -> Hash:大小、类型、宽度、存储路径
media:view:10001 -> String:播放计数
media:url:token -> 短 TTL:临时授权映射
不要把大文件 Base64 后放 Redis。Base64 会增加约三分之一体积,还会造成大 key 和网络阻塞。
27.4.5 Redis + 数仓
典型数据链路:
业务数据库 -> CDC -> Kafka -> 数仓
Redis 实时计数 -> 定时导出 -> Kafka/数仓
数仓计算结果 -> 回写 Redis 热点集合
职责划分:
| 层 | 职责 |
|---|---|
| Redis | 在线低延迟读写、短期聚合 |
| Kafka | 可靠传输和回放 |
| 数仓 | 离线建模、报表、归因 |
| 回写服务 | 把计算好的结果写回 Redis |
在线层不要做复杂分析。Redis 适合 O(log N) 或更低的点查、计数和排序,不适合扫描全量数据做多维分析。
27.5 Redis 的能力边界
27.5.1 缓存边界
适合:
- 热点读;
- 可重建数据;
- 允许短暂不一致;
- value 尺寸可控。
不适合:
- 必须强一致;
- 访问完全随机且命中率低;
- value 达到 MB 级;
- 数据增长没有上界。
27.5.2 队列边界
Redis Stream 可以做轻量队列,但要回答:
- 数据丢失是否可接受;
- 堆积是否可能超过内存;
- 是否需要长期回放;
- 消费组和 pending 恢复是否满足需求;
- 是否需要跨机房容灾。
如果答案是“海量、可回放、长期保留、多消费者生态”,Kafka 更合适。
27.5.3 锁边界
Redis 分布式锁适合效率锁:防止重复执行任务。对于正确性关键场景,要考虑:
- 网络分区下锁是否可能失效;
- 业务执行时间是否超过锁 TTL;
- 是否有数据库唯一键兜底;
- 是否需要 fencing token;
- Redlock 是否真的必要。
如果业务不能接受重复执行,必须设计业务幂等,而不能只相信锁。
27.5.4 主存储边界
Redis 作为主数据存储需要满足:
- 数据模型能全部用 key 表达;
- 内存成本可承受;
- 持久化策略满足 RPO;
- 备份和恢复演练完成;
- 集群扩缩容方案明确;
- 有完整审计和对账机制。
多数业务系统中,Redis 更适合作为热数据层,而不是唯一事实源。
27.6 Redis Stack 与增强模块
Redis 8 将部分 Redis Stack 能力并入主版本,不同版本的模块支持差异较大。使用前应确认所用发行版、许可证和运维能力。
27.6.1 RedisJSON
适合存储和修改 JSON 文档:
JSON.SET product:10001 $ '{"id":10001,"title":"Phone","price":1999,"tags":["digital"]}'
JSON.GET product:10001 $.title $.price
JSON.SET product:10001 $.price 1899
与 String JSON 相比,RedisJSON 支持路径级修改,不必每次反序列化整个对象。适合字段较多且需要局部更新的文档。
边界:
- 仍然要控制文档大小;
- 需要客户端支持;
- 复杂查询依赖 Search;
- 不等同于通用文档数据库的完整事务能力。
27.6.2 RediSearch
适合二级索引和轻量搜索:
FT.CREATE idx:product ON HASH PREFIX 1 mall:product: SCHEMA title TEXT price NUMERIC stock NUMERIC
FT.SEARCH idx:product "phone" LIMIT 0 10
FT.SEARCH idx:product "@price:[1000 3000]" SORTBY price ASC
适合:
- hash 或 JSON 的字段索引;
- 简单全文搜索;
- 范围过滤和排序;
- 小规模地理查询。
不适合超大规模复杂搜索、复杂聚合、深度分词和完整分析型查询。
27.6.3 RedisTimeSeries
适合时间序列:
TS.CREATE sensor:1001 RETENTION 86400000
TS.ADD sensor:1001 * 26.5
TS.RANGE sensor:1001 - + AGGREGATION avg 60000
适合设备指标、短周期监控、实时采样。如果是大规模可观测性平台,通常使用 Prometheus、VictoriaMetrics、InfluxDB 或 TimescaleDB 更合适。
27.6.4 RedisBloom 与 RedisAI
RedisBloom 提供布隆过滤器、Cuckoo Filter、Count-Min Sketch、Top-K:
BF.ADD valid:user 10001
BF.EXISTS valid:user 10001
适合防穿透、去重和近似计数。RedisAI 则偏向在线推理和特征服务,使用前要评估模型生命周期和部署复杂度。
27.7 与其他系统比较
27.7.1 Redis vs Memcached
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | 丰富 | 纯 key/value |
| 持久化 | RDB/AOF | 无 |
| 高可用 | 复制、哨兵、Cluster | 客户端分片为主 |
| 大 value | 支持但需治理 | 常见场景 |
| 功能 | Lua、Stream、Search 等 | 简单 |
| 简单缓存 | 可用 | 非常稳定 |
如果只需要多线程纯缓存,Memcached 依然是合理选择;如果需要数据结构、原子操作、复制和丰富生态,Redis 更合适。
27.7.2 Redis vs Valkey
Valkey 是从 Redis 开源版本分叉出来的项目,命令协议和运维习惯高度接近。选型时要关注:
- 所需版本和许可证;
- 客户端兼容性;
- 云厂商支持;
- 社区活跃度;
- 模块和商业功能需求;
- 长期维护承诺。
27.7.3 Redis vs etcd / Consul
etcd 和 Consul 更适合:
- 强一致元数据;
- 服务发现;
- leader 选举元数据;
- 配置变更的低容量高一致存储。
Redis 更适合:
- 高 QPS 读;
- 短 TTL;
- 数据结构计算;
- 可丢或可重建数据。
不要用 Redis 的最终一致视图去做分布式协调系统的唯一元数据源。
27.7.4 自建 vs 云托管
| 维度 | 自建 | 云托管 |
|---|---|---|
| 成本模型 | 机器和人力 | 实例规格和流量 |
| 可控性 | 高 | 依赖厂商 |
| 运维 | 自己负责备份、监控、扩容 | 平台托管 |
| 高可用 | 自建哨兵/Cluster | 厂商提供 |
| 安全 | 自己配置 ACL/TLS/网络 | 平台能力 |
| 排障 | 可登录节点 | 依赖日志和控制台 |
中小团队适合云托管降低运维成本;规模较大、合规要求特殊或成本敏感的团队可以自建,但必须投入监控、备份、演练和值班能力。
27.8 事件驱动与 CQRS 中的 Redis
27.8.1 事件驱动架构
典型链路:
命令写入 MySQL -> 发布领域事件到 Kafka -> 消费者更新投影 -> Redis 提供在线查询
职责:
| 组件 | 职责 |
|---|---|
| MySQL | 保存命令和事实 |
| Kafka | 可靠传输、回放、解耦 |
| Projection | 事件转查询模型 |
| Redis | 低延迟查询视图 |
例如订单事件更新 Redis 投影:
@KafkaListener(topics = "order.events")
public void onOrderEvent(OrderEvent event) {
if (event.type() == OrderEventType.CREATED) {
redis.opsForHash().putAll("order:view:" + event.orderId(), Map.of(
"status", "CREATED",
"version", String.valueOf(event.version())
));
}
}
消费必须幂等,且要按 orderId 分区,保证同订单事件顺序。
27.8.2 CQRS
CQRS 将写模型和读模型分离:
flowchart LR
Client[客户端] --> Command[命令服务]
Command --> MySQL[(写模型)]
MySQL --> CDC[CDC]
CDC --> Kafka[Kafka]
Kafka --> Projector[读模型构建]
Projector --> Redis[(读模型)]
Client --> Query[查询服务]
Query --> Redis
Redis 读模型可以采用:
| 查询 | Redis 结构 |
|---|---|
| 按 ID 查详情 | String / Hash |
| 用户订单列表 | ZSet |
| 状态分组 | Set |
| 聚合计数 | String / Hash |
| 搜索 | Search 索引 |
注意:CQRS 意味着读写模型存在同步延迟,接口设计要明确返回的是“最终一致读模型”。
27.9 平台化 Redis 架构
当一个组织内有几十个应用使用 Redis,可以抽象统一平台:
flowchart TD
Apps[业务应用] --> SDK[统一 Cache SDK]
SDK --> Proxy[Redis 代理]
Proxy --> Cluster[(Redis Cluster)]
Cluster --> Monitor[监控告警]
Cluster --> Backup[备份恢复]
Proxy --> Quota[租户配额]
Apps --> Console[自助控制台]
平台能力:
- 统一命名规范和 SDK;
- 命令白名单;
- 大 key 拦截;
- 慢查询和热点 key 监控;
- 租户 QPS 和内存配额;
- 缓存命中率和回源指标;
- 备份恢复演练;
- 容量预警和扩容流程;
- ACL 与 mTLS;
- 故障预案和值班 Runbook。
这不是为了增加层级,而是为了把散落在各团队的经验沉淀成默认安全边界。
27.10 架构检查清单
上线前逐项确认:
| 类别 | 检查项 |
|---|---|
| 数据 | 是否可丢、可重建、可审计 |
| 一致性 | 失效策略、补偿机制、版本冲突处理 |
| 容量 | key 数量、value 大小、增长率和峰值 |
| 性能 | P99 延迟、QPS、连接数、分片均衡 |
| 可用性 | 哨兵/Cluster、客户端重试、熔断降级 |
| 安全 | ACL、TLS、网络隔离、审计 |
| 运维 | 监控、备份、扩缩容、迁移、回滚 |
| 成本 | 内存、带宽、云服务和人力 |
| 边界 | 是否更适合 MySQL/Kafka/ES/对象存储 |
27.11 本章小结
- Redis 的角色必须明确,缓存、锁、队列和状态存储的失败影响完全不同;
- 多租户至少要逻辑隔离,核心业务应物理隔离,Redis DB 不是物理隔离;
- 冷热分层能让 Redis 保持低延迟价值,历史数据应落到数据库或对象存储;
- Redis 与 MySQL、Kafka、Elasticsearch、数仓组合时,要保留各自擅长的职责;
- Redis Stack 提供搜索、JSON、时间序列等能力,但要评估规模、版本和许可证;
- 事件驱动与 CQRS 中,Redis 适合作为最终一致读模型,而不是事实源。
27.12 思考题
- 一个公司只有一个 Redis Cluster 给所有业务使用,你会提出哪些改造步骤?
- Redis Stream 和 Kafka 的队列能力边界在哪里?什么时候必须迁移到 Kafka?
- 为什么账务和支付状态不能只依赖 Redis 事务?
- Redis Search 替代 Elasticsearch 的前提条件是什么?
- 在 CQRS 中,如何检测并修复 Redis 读模型落后于 MySQL 写模型的问题?