Redis
Redis(Remote Dictionary Server)是开源的内存数据结构存储,2009 年由 Salvatore Sanfilippo 创建。Redis 可用作数据库、缓存和消息队列,以其极高的性能(单节点 10 万+ QPS)和丰富的数据结构著称。
适用场景:热点数据缓存、分布式锁、实时计数器、轻量级消息队列、排行榜、分布式 Session、限流、位图统计。各场景的完整方案与选型对比见 使用场景。当需要持久化、丰富的数据结构或单线程原子性保证时,Redis 优于 Memcached。
核心机制
单线程模型
Redis 是单线程处理命令(6.0 起 IO 读写用多线程,但命令执行仍是单线程)。
单线程带来两个关键收益:一是没有线程切换和锁竞争,配合 IO 多路复用(一个线程同时监听成千上万个连接,有请求才处理),单实例即可支撑 10 万+ QPS;二是所有命令天然原子——执行过程中不会被其他命令插入,因此不需要额外的并发控制。
数据结构
Redis 支持五种核心数据结构:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Sorted Set(有序集合)。每种数据结构都有对应的底层实现和典型应用场景。
String 是最基础的数据结构,用于缓存、计数器、分布式锁等场景。Hash 适合存储对象类型数据。List 支持两端操作,可用于消息队列。Set 支持集合运算,适合去重、标签等场景。Sorted Set 按分数排序,天然适合排行榜。
底层实现:Redis 对每种结构都有多种编码,按数据规模自动切换——小数据用紧凑的内存结构,大数据用通用结构:
| 结构 | 小数据编码 | 大数据编码 |
|---|---|---|
| String | int(纯整数)/ embstr(短字符串) | raw(SDS 动态字符串) |
| Hash | 压缩列表 ziplist | 哈希表 hashtable |
| List | 压缩列表 ziplist | 双向链表 + quicklist |
| Set | 整数集合 intset | 哈希表 hashtable |
| Sorted Set | 压缩列表 ziplist | 跳表 skipList + 哈希表 |
例如 Sorted Set 数据量超过阈值后改用跳表:一种多层链表,每层跳过若干节点,查询时间复杂度 O(log n),是"有序 + 快速查找"的折中结构。
持久化机制
Redis 提供 RDB 和 AOF 两种持久化方式。RDB(快照)在某个时间点把全量数据写入临时文件再原子替换,文件紧凑、适合容灾恢复、启动效率高,但两次快照之间的数据可能丢失;AOF(追加日志)记录所有写操作命令,按 always、everysec、no 三种策略刷盘,数据安全性更高,但文件体积大、恢复慢。Redis 4.0+ 支持 AOF 重写(把命令压缩成最小集合)和混合持久化(RDB 快照 + 增量命令追加,兼顾恢复速度与数据完整性)。
数据重要时建议同时开启 RDB 和 AOF,使用 AOF 恢复数据。
淘汰策略
当 Redis 内存使用达到上限时,会触发淘汰策略。常用策略包括 volatile-lru(淘汰有过期时间的最近最少使用数据)、allkeys-lru(淘汰任何最近最少使用数据)、volatile-ttl(淘汰即将过期的数据)等。
数据呈幂律分布(Power Law,即"少数 key 被绝大多数请求访问"的二八分布形态,缓存场景普遍如此)时推荐 allkeys-lru,设置键的过期时间本身也消耗内存,因此 allkeys-lru 比 volatile-lru 更省内存。
集群架构
主从复制
单机 Redis 读写能力有上限,宕机就全丢,主从复制是第一步扩展:把数据复制到多个从节点,实现读写分离(主写从读)分担读压力,同时也是一份现成的备份。
从节点启动时向主节点发起全量同步——主节点生成 RDB 快照发给从节点加载,之后的新写命令以增量流方式持续推送。复制是异步的:主节点写完就返回、不等从节点确认,因此主节点宕机瞬间未同步的写可能丢失;且从节点只读,写入仍集中在主节点,写扩展要靠下面的 Cluster。
哨兵(Sentinel)
主从架构下主节点宕机后,原本要人工把某个从节点提升为主节点、再改客户端配置,服务中断时间长。哨兵把整个过程自动化:多个哨兵进程(通常 3 个以上,独立于 Redis 部署)持续监控主服务器,主节点失联后先互相投票确认客观下线(避免单个哨兵误判),再由选出的哨兵执行故障转移——把某个从节点提升为主节点、通知客户端新地址,全程无需人工介入。
哨兵只解决高可用(自动切换),不解决数据分片问题;要水平扩展得上下面的 Cluster。
集群(Cluster)
Redis 3.0+ 引入的分布式方案,使用 CRC16 算法将数据分片存储在 16384 个槽位中。每个节点负责一部分槽位,支持自动故障转移和数据自动分片。
Redis 并不能保证数据的强一致性,集群节点间使用异步复制。
配置管理
配置 提供了 Redis 配置文件的完整参考,包括网络配置、持久化配置、内存配置、安全配置、集群配置等。
关键配置项:
maxmemory:最大内存限制maxmemory-policy:淘汰策略选择appendfsync:AOF 刷盘策略cluster-enabled:集群模式开关
客户端使用
Java 客户端
生产环境推荐使用 Redisson,它封装了完善的分布式锁逻辑,支持 watchdog 自动续期。Jedis 是较基础的客户端,提供较全面的 Redis 命令支持。
与 Memcached 对比
Redis 相比 Memcached 的主要优势:
- 支持更丰富的数据结构(5 种 vs 只有 String)
- 支持数据持久化
- 支持主从复制和集群
- 单核性能更高(小数据场景)
Memcached 的优势在于多核性能(大数据场景)和更简单的架构。
常见问题处理
缓存穿透
大量请求查询不存在的数据,缓存和数据库都没有,每次请求都直击数据库。布隆过滤器是一种极省内存的概率型数据结构:把已存在 key 的多个哈希位标记为 1,查询时若发现位为 0 则必然不存在,可直接拦截;位全为 1 则"可能存在"(有误判率),放行到缓存兜底。
| 方案 | 做法 | 适用场景 |
|---|---|---|
| 缓存空值 | 查询不到的数据写入空值,设置较短过期时间 | 数据变化不频繁 |
| 布隆过滤器 | 请求先过布隆过滤器,不存在直接返回 | 数据量可控 |
缓存击穿
某个热点 key 在失效瞬间,大量并发请求击穿缓存直接打到数据库。与穿透不同,击穿是单个热点 key 的问题。
| 方案 | 做法 |
|---|---|
| 永不过期 | 热点数据不设置过期时间,通过异步更新 |
| 分布式锁 | 只允许一个请求重建缓存,其他等待 |
| 本地锁 | 使用本地互斥锁,单机场景 |
| 提前重建 | 定时任务在过期前主动更新缓存 |
缓存雪崩
缓存集中失效或缓存服务全盘宕机,导致大量请求直接打到数据库。与击穿不同,雪崩是批量 key 或全局的问题。
| 阶段 | 措施 | 说明 |
|---|---|---|
| 事前 | Redis 高可用 | 主从+哨兵,或 Redis Cluster |
| 事中 | 限流降级 | 本地缓存 + Sentinel 限流兜底,保住数据库 |
| 事后 | 持久化恢复 | RDB/AOF,重启后快速恢复 |
三者区别:穿透是查询不存在的数据(绕过缓存层),击穿是单个热点 key 失效,雪崩是批量 key 同时失效或服务宕机。预防措施组合:热点数据永不过期防击穿、过期时间加随机值防集中失效、Redis 高可用防宕机、限流降级兜底、布隆过滤器防穿透。
缓存与数据库一致性
- Cache Aside Pattern(旁路缓存):读时先查缓存,miss 则查库并写缓存;写时先更新数据库、再删除缓存。删除而非更新缓存,是为了避免并发写时缓存被旧值覆盖
- 延迟双删:写库后删缓存,延迟一段时间再删一次,防止并发读写导致脏数据
- 最终一致性方案:通过 Canal(MySQL 的 binlog 增量订阅组件)监听 binlog,变更发生时异步更新缓存——数据库是唯一事实来源,缓存跟随其变化
内存溢出
- 检查
maxmemory配置是否合理 - 使用
INFO memory查看内存使用情况 - 配置合适的淘汰策略
大 Key 问题
Redis 单线程执行命令,读取、删除或序列化一个超大 value(如超过 10KB 的字符串、百万成员的 Hash)会长时间占用执行线程,期间所有其他命令排队等待,表现为整体卡顿;大 key 在集群迁移时也会长时间阻塞。
治理分两步:先用 redis-cli --bigkeys 扫描定位,再把大 value 拆成多个小 key,或用 Hash 按业务维度分片存储,让单次操作只触碰一小片数据。
热点 Key 问题
单个 key 被高频访问(如秒杀商品、爆款新闻)时,所有请求都打到承载该 key 的同一节点,即使集群有多个分片也无法分担,单节点 CPU/带宽被打满。
常见解法:本地缓存 + Redis 多级缓存,让热 key 的读流量在应用内消化;读写分离分散压力;或给热 key 加随机后缀拆成多个副本 key 分散到不同分片,读时随机取一个。