Redis 是目前最流行的键值存储数据库,因其高性能、丰富的数据结构和原子操作,成为后端开发中不可或缺的组件。下面从基础到高级,系统性地梳理 Redis 的核心知识与技能。
一、Redis 是什么?
| 维度 | 说明 | 补充/细节 |
|---|---|---|
| 全称 | Remote Dictionary Server(远程字典服务器) | C 语言编写,开源、内存中的数据结构存储系统 |
| 定位 | 内存中的数据结构存储系统,可用作数据库、缓存和消息中间件 | 核心定位 :高性能的缓存 和数据结构服务器 |
| 核心特点 | 基于内存(极快)、单线程(6.0之前)+ I/O多路复用、支持持久化、丰富数据结构 | 原子性 :所有单命令操作都是原子性的;丰富数据结构是其区别于 Memcached 的核心优势 |
| 性能 | 读写速度可达 10万+ QPS | 性能瓶颈通常在网络 I/O,而非 CPU(单线程模型下) |
| 适用场景 | 缓存、会话管理、排行榜、消息队列、分布式锁、实时分析 | 不适用场景:需要复杂关系查询、海量数据存储(成本高)、强一致性事务(非 ACID) |
二、Redis 支持的数据结构(5+4种,含模块扩展)
| 类型 | 底层实现 | 常用命令 | 使用场景 | 复杂度(平均) |
|---|---|---|---|---|
| String | SDS(简单动态字符串) | SET/GET/INCR/DECR/SETNX/MSET |
缓存对象、计数器、分布式锁、Session | O(1) |
| Hash | 压缩列表(ziplist) / 哈希表(hashtable) | HSET/HGET/HGETALL/HINCRBY |
存储对象(如用户信息)、购物车 | O(1) |
| List | 压缩列表(ziplist) / 双向链表(linkedlist) | LPUSH/RPUSH/LPOP/RPOP/LRANGE |
消息队列、最新消息列表、文章评论 | O(1) (头尾), O(N) (中间) |
| Set | 整数集合(intset) / 哈希表(hashtable) | SADD/SREM/SMEMBERS/SINTER/SUNION |
标签系统、共同好友、抽奖、去重 | O(1) (增删查), O(N*M) (交集/并集) |
| Sorted Set | 压缩列表(ziplist) / 跳表(skiplist) | ZADD/ZRANGE/ZRANK/ZREVRANGE/ZSCORE |
排行榜、延迟队列、优先级队列、带权重的消息 | O(log N) (增删查) |
| Bitmaps | String 的位操作 | SETBIT/GETBIT/BITCOUNT/BITOP |
签到统计、在线状态、用户行为分析、布隆过滤器 | O(1) |
| HyperLogLog | 概率数据结构 | PFADD/PFCOUNT/PFMERGE |
独立访客数(UV)统计、基数估算 | O(1) |
| GEO | Sorted Set 实现 | GEOADD/GEORADIUS/GEODIST/GEOHASH |
附近的人、位置距离计算、地理围栏 | O(log N) |
| Stream | Radix Tree + Listpack | XADD/XREAD/XGROUP/XACK |
专业消息队列、事件溯源、日志收集 | O(1) (追加) |
三、Redis 底层核心机制
1. 内存管理与数据结构
| 机制 | 说明 | 关键细节 |
|---|---|---|
| SDS (简单动态字符串) | Redis 自定义的字符串实现 | 二进制安全 :可存储图片等二进制数据;O(1) 获取长度 :len 字段直接记录;杜绝缓冲区溢出 :free 字段预分配空间,API 会检查;减少内存重分配:空间预分配和惰性释放策略。 |
| 跳表 (Skip List) | ZSet 底层实现之一 | 多层链表结构 :通过"快车道"实现快速查找;实现简单 :相比红黑树,实现和维护更简单;范围查询高效 :ZRANGE 操作性能优异;空间换时间:需要额外指针存储。 |
| 压缩列表 (ziplist) | 小数据量时使用的连续内存块 | 节省内存 :无指针开销,数据紧凑;读写性能 :新增/删除元素可能引发连锁更新(cascade update),导致性能下降;配置阈值 :hash-max-ziplist-entries 等配置决定何时转为哈希表。 |
| 整数集合 (intset) | Set 在元素为整数时使用 | 有序、无重复 :元素按升序排列;升级机制:当插入新类型整数(如从 int16 到 int64)时,会升级整个集合并重新分配内存,此操作不可逆。 |
| 对象系统 (RedisObject) | 所有数据类型的统一封装 | 每个对象包含 type, encoding, lru (LRU算法), refcount (引用计数) 等元数据,是 Redis 实现多态和内存回收的基础。 |
2. 单线程模型 vs 多线程模型 (6.0+)
| 特性 | Redis < 6.0 (单线程) | Redis >= 6.0 (多线程 I/O) |
|---|---|---|
| 命令执行 | 单线程,避免了锁竞争和上下文切换开销 | 核心命令执行仍是单线程,保证原子性 |
| I/O 处理 | 单线程处理网络读写和命令执行 | 多线程处理网络 I/O(读取和写入响应),主线程只负责命令执行 |
| 性能瓶颈 | CPU 成为瓶颈时,无法利用多核优势 | 主要解决网络 I/O 瓶颈,对于大数据量、高并发的网络场景性能提升显著 |
| 线程安全 | 天然线程安全,无需考虑锁 | 命令执行层仍是单线程,核心逻辑保持简单 |
| 配置 | io-threads-do-reads no (默认) |
io-threads 4 (建议设置为CPU核心数) |
3. I/O 多路复用
| 技术 | 说明 | Redis 中的应用 |
|---|---|---|
| select | 早期的 I/O 多路复用模型,有连接数限制(1024) | Redis 已不再使用 |
| epoll | Linux 下的高效 I/O 模型,无连接数限制,基于事件回调 | Redis 在 Linux 上的默认实现,性能极高 |
| kqueue | macOS/FreeBSD 下的高效 I/O 模型,类似 epoll | Redis 在相应系统上的默认实现 |
| 工作流程 | 1. epoll_create 创建实例 2. epoll_ctl 注册/修改 Socket 事件 3. epoll_wait 阻塞等待事件发生 4. 事件返回后,由 Redis 单线程逐个处理 |
这是 Redis 单线程能处理大量并发连接的基石 |
四、Redis 持久化机制(深度对比)
RDB(快照) vs AOF(日志) vs 混合持久化
| 对比维度 | RDB (快照) | AOF (仅追加文件) | 混合持久化 (AOF rewrite) |
|---|---|---|---|
| 原理 | 在指定时间间隔内,将内存数据快照写入磁盘 | 记录所有写命令到 AOF 文件,重启时重放命令 | AOF 重写时:将当前内存数据以 RDB 格式写入 AOF 文件开头,之后的增量命令以 AOF 格式追加 |
| 恢复速度 | 快 | 慢(需重放所有命令) | 快(RDB 部分加载快,AOF 部分增量小) |
| 数据完整性 | 可能丢失最后一次快照后的数据 | 高 (可配置 appendfsync always) |
高(同 AOF,但恢复更快) |
| 文件大小 | 较小,紧凑 | 较大,随时间增长 | 较小(RDB 部分紧凑,AOF 部分增量) |
| 性能影响 | fork 子进程时有短暂性能影响(内存拷贝) |
持续影响写入性能,取决于 fsync 策略 |
重写时有短暂性能影响,平时同 AOF |
| 适用场景 | 备份、灾难恢复、对数据完整性要求不高 | 生产环境首选,追求数据高可靠 | Redis 4.0+ 推荐,兼顾恢复速度和数据完整性 |
| 配置示例 | save 900 1 stop-writes-on-bgsave-error yes |
appendonly yes appendfsync everysec auto-aof-rewrite-percentage 100 |
开启 AOF 后,重写机制自动生效 |
最佳实践 :生产环境强烈建议开启 AOF ,并使用 appendfsync everysec 策略。同时可以开启 RDB 用于定时备份。Redis 4.0+ 默认开启混合持久化,是最佳选择。
五、Redis 高可用与集群方案(详解)
方案对比与选型
| 方案 | 工作原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 主从复制 | Master 写,Slave 读,单向数据同步 | 读写分离,提升读性能;数据备份 | Master 单点故障;手动故障转移麻烦 | 读多写少,对高可用要求不高的场景 |
| 哨兵模式 (Sentinel) | 监控主从节点,自动进行故障转移 | 高可用 :自动故障转移;监控 :节点健康检查;通知:故障通知 | 配置相对复杂;主从切换期间服务短暂不可用(秒级);不解决写能力瓶颈 | 对高可用有要求,但数据量和并发量不是特别巨大的场景 |
| Redis Cluster | 数据分片(16384个槽),多主多从,去中心化 | 高可用 :节点故障自动转移;水平扩展:轻松增加节点提升容量和性能 | 客户端复杂 :需支持 Cluster 协议,处理 MOVED/ASK 重定向;不支持多键操作 (如 MSET 跨槽位);事务仅支持单节点 |
海量数据 、高并发、需要水平扩展的场景 |
主从复制核心流程与问题
全量同步 (Full Sync):
Slave 发送 REPLCONF listening-port 和 PSYNC ? -1。
Master 响应 FULLRESYNC <runid> <offset>,并 fork 子进程生成 RDB 文件。
Master 将 RDB 文件发送给 Slave,Slave 清空旧数据并加载 RDB。
Master 将生成 RDB 期间的写命令存入复制缓冲区 (replication buffer),并发送给 Slave。
Slave 执行这些写命令,达到数据一致。
增量同步 (Partial Sync):
断线重连后,Slave 发送 PSYNC <runid> <offset>。
Master 检查 runid 是否匹配,且 offset 是否在复制积压缓冲区 (repl_backlog_buffer) 内。
如果都满足,则只发送从 offset 开始的增量命令。
否则,退化为全量同步。
常见问题:
主从数据不一致 :网络延迟、Slave 负载过高、maxmemory 配置不当导致 key 淘汰策略不同。
主从延迟:Master 写压力大、Slave 单线程处理慢、网络带宽瓶颈。
Redis Cluster 核心机制
| 机制 | 说明 |
|---|---|
| 数据分片 (Sharding) | 16384 个槽位 (Slot),通过 CRC16(key) % 16384 计算 key 所属槽位。每个 Master 节点负责一部分槽位。 |
| 节点通信 | 节点间通过 Gossip 协议交换信息(如槽位映射、节点状态),实现去中心化。 |
| 请求路由 | 客户端请求到错误节点时,节点会返回 MOVED (永久) 或 ASK (临时) 重定向指令,客户端需更新槽位映射。 |
| 高可用 (Failover) | 集群中每个 Master 都有对应的 Slave。当 Master 失效时,其 Slave 会被选举为新的 Master(通过 Raft 类似的共识算法),并接管其负责的槽位。 |
| 不支持的操作 | 涉及多个 key 的操作(如 MSET key1 val1 key2 val2),如果 key 不在同一个槽位,会报错。需使用 hash tag(如 {user}1001)强制 key 分配到同一槽位。 |
六、Redis 缓存三大问题与解决方案(方案对比)
| 问题 | 核心原因 | 解决方案 | 方案对比与选型 |
|---|---|---|---|
| 缓存穿透 | 查询一个不存在的数据,请求绕过缓存直击数据库。 | 1. 缓存空对象 :将 null 也缓存,设短过期时间。 2. 布隆过滤器:在缓存前加一层过滤器,拦截不存在的 key。 |
缓存空对象 :简单,但会占用少量内存,且数据不一致时需主动删除。 布隆过滤器 :高效,内存占用小,但有误判率(可调),且不支持删除。生产环境常组合使用。 |
| 缓存击穿 | 一个热点 Key 在失效瞬间,大量并发请求直击数据库。 | 1. 互斥锁 (Mutex) :只允许一个线程去数据库加载数据,其他线程等待。 2. 逻辑过期 (永不过期):Key 物理上不过期,通过后台线程异步更新。 | 互斥锁 :实现简单,但高并发下线程等待会增加响应时间。 逻辑过期 :响应快,无等待,但数据一致性较差(可能读到旧数据),实现复杂。推荐互斥锁方案。 |
| 缓存雪崩 | 1. 大量 Key 同时失效 。 2. Redis 服务宕机。 | 1. 过期时间加随机值 :打散失效时间。 2. 高可用架构 :哨兵或集群,保证 Redis 不宕机。 3. 服务降级/限流 :数据库前加限流组件。 4. 多级缓存:本地缓存 (Caffeine) + Redis。 | 过期时间加随机值 是基础,必须做。 高可用架构 是根本保障。 多级缓存 和限流是最后的防线,用于应对极端情况。 |
七、分布式锁(深度剖析)
Redis 实现分布式锁的演进
| 版本 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| SETNX | SET key value NX |
简单,原子性 | 1. 无过期时间,易死锁。 2. 无法保证释放锁的是加锁的客户端(误删)。 |
| SET NX PX | SET key value NX PX 30000 |
1. 原子性。 2. 自动过期,防死锁。 | 仍有误删他人锁的风险(业务未执行完,锁已过期,被其他客户端获取并删除)。 |
| Lua 脚本 | if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end |
1. 原子性。 2. 防死锁。 3. 安全释放 :通过 value 校验,只删除自己加的锁。 |
1. 业务执行时间超过锁过期时间,仍会导致问题。 2. 主从切换时,锁可能丢失。 |
| Redlock | 在 N 个独立 Redis 实例上依次加锁,成功超过半数即算成功 | 1. 解决主从切换锁丢失问题。 2. 容错性更高。 | 1. 实现复杂,性能开销大。 2. 依赖系统时钟,时钟漂移可能导致问题。 3. 官方文档也建议仅在对强一致性要求极高的场景使用。 |
| Redisson | 基于 Redis 的 Java 客户端库,封装了 Redlock 和可重入锁 | 1. 可重入 :同一线程可多次加锁。 2. 看门狗机制 :自动续期,解决业务超时问题。 3. 公平/非公平锁 、读写锁 等多种实现。 4. 封装完善,开箱即用。 | 增加了客户端依赖,但极大地简化了开发。 |
生产环境推荐:
简单场景:使用 SET NX PX + Lua 脚本 的方案,并确保业务逻辑在锁过期时间内能完成。
复杂/高要求场景:直接使用成熟的客户端库,如 Redisson,它内置了看门狗、可重入等高级特性,是事实上的工业标准。
八、Redis 事务与 Pipeline(对比与选择)
| 特性 | 事务 (Transaction) | Pipeline (管道) |
|---|---|---|
| 目的 | 原子性:保证一组命令要么全执行,要么全不执行。 | 性能:减少网络往返次数 (RTT),批量执行命令。 |
| 原子性 | 不保证 :命令在队列中时不保证,EXEC 后也不保证(某条命令失败不影响其他)。无回滚。 |
无原子性:只是批量发送,不保证原子性。 |
| 隔离性 | 不保证 :执行期间其他客户端的命令可能穿插进来(在 EXEC 前后)。 |
不保证:同上。 |
| 错误处理 | 1. 语法错误 :EXEC 前发现,整个事务失败。 2. 运行时错误 :EXEC 后发现,该命令失败,其他命令继续执行。 |
每个命令独立返回结果,需客户端自行判断。 |
| 主要命令 | MULTI, EXEC, DISCARD, WATCH |
pipeline() 对象,最后 execute() |
| 适用场景 | 需要将多个操作作为一个逻辑单元 ,但对严格的数据库事务(ACID)要求不高。例如:INCR 一个计数器,然后 LPUSH 一条日志。 |
需要批量操作 以提升性能,且不关心原子性。例如:一次性 GET 100 个 key 的值。 |
选择建议:
需要保证一组操作的逻辑原子性 (即不希望被其他命令打断),用事务。
只是为了提升批量操作的性能 ,用 Pipeline。
在很多场景下,两者可以结合使用。
九、Redis 常用性能优化(速查表)
| 优化方向 | 问题/现象 | 排查/解决方案 |
|---|---|---|
| 大 Key | 慢查询、内存分布不均、集群迁移困难 | 1. 发现 :使用 redis-cli --bigkeys 或 memory doctor。 2. 拆分 :将一个大 Hash 拆成多个小 Hash-Key。 3. 避免 :不要用 GETALL,使用 HSCAN 游标遍历。 |
| 热点 Key | 单点 QPS 过高,成为性能瓶颈 | 1. 发现 :hotkeys 命令(需插件)或监控平台。 2. 拆分 :将热点 Key 拆分为多个,如 hot_key_1, hot_key_2,通过随机数读写。 3. 本地缓存:在应用层加 Caffeine 等本地缓存。 |
| 慢查询 | 命令执行时间过长,阻塞其他请求 | 1. 发现 :SLOWLOG GET 10 查看慢查询日志。 2. 分析 :检查慢查询命令(如 KEYS, HGETALL 大 Key)。 3. 解决 :用 SCAN 替代 KEYS;拆分大 Key;优化业务逻辑。 |
| 内存溢出 | maxmemory 限制,触发淘汰策略 |
1. 监控 :INFO memory 查看 used_memory。 2. 分析 :memory stats 分析内存分布。 3. 解决 :调整 maxmemory-policy(如 allkeys-lru);增加内存;优化数据结构。 |
| 连接数过高 | CLIENT LIST 显示大量连接 |
1. 优化 :使用连接池 (JedisPool, Lettuce),复用连接。 2. 排查:检查是否有连接泄漏(未正确关闭)。 |
| CPU 100% | 单线程模型下 CPU 跑满 | 1. 排查 :INFO commandstats 查看高频命令。 2. 原因 :通常是执行了复杂命令(如 SORT, ZUNIONSTORE)或大量 Key 过期。 |
| 持久化开销 | fork 子进程时响应变慢 |
1. 配置 :repl-diskless-sync yes(无盘复制,适用于主从)。 2. 硬件:使用 SSD 硬盘。 |
十、企业级最佳实践与场景速查
| 业务场景 | 推荐 Redis 数据结构/方案 | 核心命令/实现 | 关键点 |
|---|---|---|---|
| 高并发缓存 | String + 本地缓存 (Caffeine) | SETEX, GET |
缓存穿透/击穿/雪崩防护;热点 Key 预热;数据一致性策略 |
| 计数器/排行榜 | String (INCR) / Sorted Set | INCR, ZINCRBY, ZREVRANGE |
原子性操作;Sorted Set 可带成员信息,实现复杂排行 |
| 分布式 Session | String + Hash | SETEX user:session_id json_str, HSET user:1 name "A" |
Key 需有统一前缀;设置合理过期时间;序列化用 JSON 或 Protobuf |
| 分布式锁 | Redisson (推荐) / SET + Lua | RLock.lock(), SET key val NX PX |
防死锁 (过期时间/看门狗);安全释放 (Lua校验);可重入 |
| 消息队列 | Stream (专业) / List (轻量) | XADD, XREADGROUP, LPUSH, BRPOP |
Stream 支持消费组 、消息确认 (ACK) 、消息回溯,功能完善;List 简单但无ACK |
| 社交关系 | Set / Sorted Set | SADD, SINTER, ZRANGE |
共同好友用 SINTER;关注列表用 Set 或 List |
| 实时统计 (UV/PV) | HyperLogLog / Bitmap | PFADD, PFCOUNT, SETBIT, BITCOUNT |
HyperLogLog 基数统计,省内存;Bitmap 用于精确签到、在线状态 |
| 地理位置服务 | GEO | GEOADD, GEORADIUS |
基于 Sorted Set 实现,可用于"附近的人"、"打车"等场景 |
| 标签/筛选 | Set / Bitmap | SADD, SISMEMBER, SETBIT |
标签用 Set 实现交集/并集筛选;多维筛选可用 Bitmap 实现 |
| 配置中心 | Hash / String | HSET config_key field val, GET config_key |
监听 Key 的过期或变更消息(需客户端实现或结合 Pub/Sub) |
本文通过多维度的对比表格、深入的机制剖析、方案选型建议以及企业级最佳实践,力求构建一个更加立体和深入的 Redis 知识体系。欢迎交流指正。