RedisNotes

第 27 章:架构模式与生态:把 Redis 放到正确位置

zjc 于 2026-01-27 发布

这是《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 0SELECT 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
命令类型 禁用 KEYSFLUSHALL、全量 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 冷热迁移策略

常见迁移方式:

  1. TTL 自动清理;
  2. 定时任务扫描过期数据;
  3. 访问频率标记,低频数据降级;
  4. binlog 同步温库;
  5. 分析结果回写 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 缓存边界

适合:

不适合:

27.5.2 队列边界

Redis Stream 可以做轻量队列,但要回答:

  1. 数据丢失是否可接受;
  2. 堆积是否可能超过内存;
  3. 是否需要长期回放;
  4. 消费组和 pending 恢复是否满足需求;
  5. 是否需要跨机房容灾。

如果答案是“海量、可回放、长期保留、多消费者生态”,Kafka 更合适。

27.5.3 锁边界

Redis 分布式锁适合效率锁:防止重复执行任务。对于正确性关键场景,要考虑:

  1. 网络分区下锁是否可能失效;
  2. 业务执行时间是否超过锁 TTL;
  3. 是否有数据库唯一键兜底;
  4. 是否需要 fencing token;
  5. Redlock 是否真的必要。

如果业务不能接受重复执行,必须设计业务幂等,而不能只相信锁。

27.5.4 主存储边界

Redis 作为主数据存储需要满足:

  1. 数据模型能全部用 key 表达;
  2. 内存成本可承受;
  3. 持久化策略满足 RPO;
  4. 备份和恢复演练完成;
  5. 集群扩缩容方案明确;
  6. 有完整审计和对账机制。

多数业务系统中,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 支持路径级修改,不必每次反序列化整个对象。适合字段较多且需要局部更新的文档。

边界:

  1. 仍然要控制文档大小;
  2. 需要客户端支持;
  3. 复杂查询依赖 Search;
  4. 不等同于通用文档数据库的完整事务能力。

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

适合:

不适合超大规模复杂搜索、复杂聚合、深度分词和完整分析型查询。

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 开源版本分叉出来的项目,命令协议和运维习惯高度接近。选型时要关注:

  1. 所需版本和许可证;
  2. 客户端兼容性;
  3. 云厂商支持;
  4. 社区活跃度;
  5. 模块和商业功能需求;
  6. 长期维护承诺。

27.7.3 Redis vs etcd / Consul

etcd 和 Consul 更适合:

Redis 更适合:

不要用 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[自助控制台]

平台能力:

  1. 统一命名规范和 SDK;
  2. 命令白名单;
  3. 大 key 拦截;
  4. 慢查询和热点 key 监控;
  5. 租户 QPS 和内存配额;
  6. 缓存命中率和回源指标;
  7. 备份恢复演练;
  8. 容量预警和扩容流程;
  9. ACL 与 mTLS;
  10. 故障预案和值班 Runbook。

这不是为了增加层级,而是为了把散落在各团队的经验沉淀成默认安全边界。

27.10 架构检查清单

上线前逐项确认:

类别 检查项
数据 是否可丢、可重建、可审计
一致性 失效策略、补偿机制、版本冲突处理
容量 key 数量、value 大小、增长率和峰值
性能 P99 延迟、QPS、连接数、分片均衡
可用性 哨兵/Cluster、客户端重试、熔断降级
安全 ACL、TLS、网络隔离、审计
运维 监控、备份、扩缩容、迁移、回滚
成本 内存、带宽、云服务和人力
边界 是否更适合 MySQL/Kafka/ES/对象存储

27.11 本章小结

27.12 思考题

  1. 一个公司只有一个 Redis Cluster 给所有业务使用,你会提出哪些改造步骤?
  2. Redis Stream 和 Kafka 的队列能力边界在哪里?什么时候必须迁移到 Kafka?
  3. 为什么账务和支付状态不能只依赖 Redis 事务?
  4. Redis Search 替代 Elasticsearch 的前提条件是什么?
  5. 在 CQRS 中,如何检测并修复 Redis 读模型落后于 MySQL 写模型的问题?