
做后端这么多年Redis 几乎是每个项目的标配但实事求地说能把 Redis 讲透的资料非常少。大多数时候我们只是塞了个 RedisTemplate用一个缓存注解集群模式停留在“听说过”的程度面试问到底层原理就含糊其辞。前阵子翻到一套“Redis 全栈小册”标题虽然朴素内容却从基础、应用一路覆盖到原理、集群、拓展和源码。我花了一个周末通读边看边把这些年线上踩过的坑全对上了号。这篇文章就把我认为最值得反复看、也最贴近实际工作的一条 Redis 学习路径整理出来不整虚的想系统掌握 Redis 的开发者照着走就行。1. 基础篇先把 Redis 的“手”和“脚”接上1.1 五大基础数据类型一张表记住用法Redis 的入门门槛低几乎所有人第一次接触都是那几个命令SET、GET、DEL。但真正到生产环境经常能看到有人把所有东西全塞进 StringList 只用来当普通队列ZSet 一辈子没用过。这个现象很普遍原因是大部分人没有把数据类型和应用场景建立映射。我把五大数据类型按“场景记忆法”重新梳理了一遍用起来会顺手很多。String 是最通用的键值模型适合计数器、缓存、分布式锁、Session 共享。命令抓住SET key value NX EX seconds、INCR、GETSET这几个核心就够了。其中INCR是原子操作不用再拿锁去保护计数逻辑秒杀库存扣减的前置计数经常用它。Hash 适合存对象一个 key 对应一个用户、一个商品字段就是对象的属性。相比把整个对象序列化成 JSON 塞进 StringHash 的好处是能单独更新某个字段比如只改用户头像不需要把整个对象取回来再写回去。购物车、用户信息、商品详情这类场景用 Hash 特别合适。List 的双端队列特性让它在消息队列、最新消息列表这种场景很顺手。RPUSH往右推LPOP从左边取天然就是个 FIFO 队列BLPOP还能阻塞等待避免客户端空转。要注意 List 做队列功能时Redis 本身没有确认机制业务消费失败后消息就丢了这个逻辑得靠业务代码兜底。Set 的核心是去重和集合运算标签、关注关系、抽奖名单都能用SADD、SINTER、SUNION一次算出来。关注列表这种双边关系用 Set 分别存粉丝和关注名单算共同好友就是SINTER一个命令搞定比在数据库里用 IN 查询高效得多。ZSet 是加分项排序逻辑交给 Redis 而不是业务代码。每个成员带一个 score排行榜就是ZREVRANGE按时间范围刷帖子就是 ZSet 存时间戳按分数范围筛选就是ZRANGEBYSCORE。ZSet 底层跳表的设计让插入和查询之间取得非常好的平衡这也是后面读源码时会反复遇到的核心结构。除了这五种还有几个容易被忽略的进阶类型。Bitmaps 可以用位运算统计用户签到、在线状态内存占用极低HyperLogLog 做 UV 统计误差在 0.81% 左右但内存只需要 12KBGeo 类型存经纬度做附近的人、附近门店非常方便Stream 是 Redis 5.0 引入的消息队列比 List 那个临时方案完整得多后面拓展篇会单独展开。基础阶段的重点不是背完所有命令而是看到业务场景能想到该用哪个类型这一步做好了后面应用层才不会变形。1.2 环境安装编译、Docker、客户端都不落下环境安装是我见过翻车率最高的环节尤其是刚接触 Redis 的人。官方源码编译是最标准的做法下载稳定版源码包解压后依次执行make和make install装完在/usr/local/bin下就有redis-server和redis-cli。如果有编译依赖问题多半是缺少 gcc 或者 make装好再编译就行。平时做开发我强烈建议直接用 Docker省去编译和系统依赖的麻烦。一条命令就能起一个可用的 Redisdocker run -d --name redis-dev -p 6379:6379 -v /data/redis:/data redis:7-alpine redis-server --appendonly yes这里我把持久化目录挂到了宿主机/data/redis并且开了 AOF。开发环境这样用足够了生产环境再考虑独立部署和集群方案。启动后先别急着写代码用redis-cli ping验证一下返回 PONG 就说明通了。还有一个几乎必踩的坑redis.conf 里的bind 127.0.0.1和protected-mode yes。本地测试没问题但要在服务器上提供服务或者连远程客户端你就会遇到连接失败。正确做法是明确bind内网网卡地址关闭 protected-mode 或者配好密码千万不要图省事直接注释掉 bind 让 Redis 暴露在公网。这类安全问题在真实生产里出过太多事被扫描到然后挖矿的情况并不少见。桌面客户端我推荐 Another Redis Desktop Manager它界面直观能直接看 key 扫描结果、内存占用还能执行命令。不过我还是要多说一句日常排查问题时请先相信redis-cli因为它能看到连客户端界面都不一定暴露的原始信息比如INFO、MEMORY、SLOWLOG这类底层诊断命令命令行比图形界面方便得多。2. 应用篇缓存、锁和一致性这才是生产级用法2.1 缓存穿透、缓存击穿、缓存雪崩怎么打基础阶段学会读写 Redis 之后第二阶段最容易出现的问题是把 Redis 当成“缓存数据库”用也就是先查 Redis没有就查 MySQL查到再回填 Redis。这个模型看起来简单但一旦遇到高并发三个经典问题会轮流来找你缓存穿透、缓存击穿、缓存雪崩。缓存穿透指的是请求的数据在数据库里根本不存在所以 Redis 里也永远没有请求每次都打穿到数据库。如果恶意用一个不存在的 ID 刷接口数据库会被查挂。应对办法有三个入口参数先校验明显不合理的 ID 直接拒绝第二个是布隆过滤器把所有可能存在的数据的特征提前放进去查询前先判断能挡掉大部分不存在的 key第三种是即使查不到数据也把空结果缓存几分钟设置很短的过期时间避免同一空 key 反复穿透。缓存击穿则是某个热点 key 恰好过期了同一瞬间大量请求同时落到数据库。这种情况的典型特征是“只有一个 key但访问量极大”。最常用的解法是互斥锁查询数据库之前先尝试拿分布式锁拿到的线程去查库并回填缓存拿不到的线程先短暂等待再重试。另一种思路是逻辑过期也就是缓存不设置物理过期时间而是塞一个逻辑过期时间字段读的时候发现逻辑过期就异步刷新同时老数据继续提供服务体验会平滑很多。缓存雪崩是大量 key 在同一时间过期导致大量请求同时穿透。最常见的原因就是设置了相同的过期时间比如“统一晚上 0 点失效”“统一 30 分钟”。解法也简单过期时间加一个随机偏移量把集中失效打散再加一层多级缓存兜底请求先走本地缓存再走 Redis数据库永远放到最后同时服务入口做好熔断降级哪怕缓存全挂了也要避免把数据库打满。这里我特别想强调的是顺序。很多人遇到这三个问题会一上来就写布隆过滤器但布隆过滤器只能解决穿透不能解决击穿和雪崩。正确顺序应该是先把过期时间随机化这是最便宜、收益最高的动作再给热点 key 做互斥锁或逻辑过期最后才是上布隆过滤器或空值缓存兜底。每一层解决一类问题互相不能替代。2.2 分布式锁的正确姿势绕开那三个经典坑分布式锁是我面试时最常问、也是线上出问题最多的点之一。先看一个最朴素的写法SETNX lock_key拿到锁之后处理业务最后DEL lock_key。这个写法有三个致命问题。第一个问题是锁没有过期时间。如果拿到锁的进程在业务执行到一半时挂了锁永远释放不了后面的请求全部阻塞。改进方法是给锁加过期时间但大部分人的第一版实现是SETNX之后再EXPIRE两步操作不是原子的进程在 SETNX 和 EXPIRE 之间挂了锁一样会死。正确写法是使用单条原子命令SET lock_key client_id NX PX 30000一次调用完成“加锁 设置过期时间”。第二个问题是释放锁的时候不能随便DEL因为有可能当前线程的锁已经过期另一个线程拿到了锁这时如果原来的线程执行完一DEL就把别人的锁删掉了。所以释放锁之前必须先比较value是不是自己写入的那个客户端标识相等才能删除。比较和删除同样要保证原子标准做法就是用 Lua 脚本因为 Redis 内置执行 Lua 脚本时单线程串行天然不会中间被打断if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end第三个问题是业务执行时间超过锁的过期时间锁自动释放了后面的线程又进来同一个资源被并发操作。这个问题在 Redisson 里叫看门狗它会在锁快过期时自动续期直到业务正常释放。如果你用的是 go-redis 这类库没有内置续期就要自行评估业务最大执行时间把过期时间设得足够长或者在业务里主动续期。顺便说一句 RedLock 的争议。RedLock 试图用多个 Redis 节点来保证分布式锁的可靠性但在网络分区这种极端情况下依然存在理论漏洞分布式系统的大佬们为此争论过很多轮。我的实际建议是绝大多数业务场景单机 Redis 或者哨兵模式下的分布式锁已经足够如果对一致性要求真的极高请考虑 etcd、ZooKeeper 这类带强一致语义的组件而不是强行 RedLock。2.3 缓存与数据库一致性别被“删缓存”带偏缓存和数据库的一致性是应用层最难缠的话题。最被推崇的是 Cache Aside 模式读的时候先读缓存没有就查库并回填写的时候先更新数据库再删除缓存。注意这里是删除缓存而不是更新缓存因为更新缓存可能产生并发写顺序问题而删除之后下一次读自然会回填最新数据。但“先更新数据库再删除缓存”也有自己的问题更新库成功、删缓存失败缓存里还是旧值。解决思路有两种第一种是延迟双删先删除缓存再更新数据库隔一小段时间再删一次这个思路能覆盖大部分问题但不够优雅第二种是订阅数据库 binlog把删除缓存这个动作变成一个异步任务保证最终删除一定会执行。后者在 Canal 这类中间件的帮助下是生产环境比较稳的方案。还有一个很多人容易混淆的点强一致和最终一致的差别。数据库和缓存是两个存储系统在没有引入分布式事务的前提下追求强一致基本是伪命题。正确的目标是把不一致的时间窗口压缩到足够小再通过兜底策略让它最终收敛。所以实际工程里我会做三件事先保证“更新数据库成功之后必须触发删缓存”把失败的动作丢进本地消息表或定时任务重试再对缓存设置一个合理的 TTL即使删除失败过期之后也能自愈这是最重要的兜底最后把对一致性要求极高的场景单独设计不要指望缓存的那点加速一次性解决所有问题。这一节最后提一下旁路缓存的两个性能细节。批量查询时尽量用MGET而不是循环GET一次网络往返能省下大量 RTT缓存对象如果很大压缩算法比如 LZ4 或者 Gzip 可以先处理一遍Redis 的带宽往往是高频场景最先触到的瓶颈。这些细节不需要花费太多成本但对性能的提升非常明显。3. 原理篇Redis 快的原因全部藏在源码里3.1 说它是单线程其实是多路复用Redis 性能好的原因被简化成“单线程没有上下文切换”这个说法太片面。严格来说Redis 处理命令的核心模型是事件驱动的单线程循环但它同时使用了操作系统提供的 I/O 多路复用机制最常见的底层就是 epoll。Redis 主线程只需要在一个事件循环里同时监听大量客户端连接的可读可写事件哪个 socket 有数据就处理哪个没有数据就阻塞等待CPU 不会因为轮询所有连接而空转。单线程最大的收益其实是避免共享数据竞争模型里永远只有一个命令在被执行所以 Redis 内部大量数据结构不需要加锁。Redis 6.0 开始引入了多线程但这些线程只负责网络数据的读写真正执行命令的主流程依然单线程。换句话说多线程解决了网络 I/O 的 CPU 开销但没有破坏命令执行的原子性。理解这个模型对实际调优很有帮助。不要写执行时间长的命令比如KEYS *、大范围SMEMBERS、超大 key 的DEL因为它们是阻塞主线程的。应该用SCAN分批遍历代替KEYS *用UNLINK代替DEL做异步删除。生产环境慢查询日志里出现最多的几乎都是这种“单个命令过大”的问题搞清楚事件循环后就完全能理解了。3.2 底层数据结构每种类型都有两副面孔Redis 每种对外数据类型内部都有不止一种编码。设计这个两层结构的目的很简单小数据用紧凑结构省内存大数据用高效结构保性能。读源码时最有趣的也是这套编码切换逻辑。String 内部用的是 SDS也就是简单动态字符串而不是裸的 C 字符串。SDS 记录了长度获取字符串长度是 O(1)同时有预分配机制追加字符串时不用频繁重新分配内存还能二进制安全地存任意内容。Hash、ZSet 在元素少、值小的时候用 listpack 这类紧凑编码元素超过阈值后自动切换成 hashtable 或 skiplist。比如 ZSet 底层就是listpack dict skiplist的组合dict 负责按 member 查找分数skiplist 负责按 score 排序两个结构一起才实现 O(logN) 的范围查询。List 的结构演变特别说明问题。早期版本用双向链表内存碎片多后来改成 quicklist也就是把多个压缩块用链表串起来到了 Redis 7.x 又引入了 listpack整体设计一直在朝“少指针、少碎片、省内存”的方向走。Set 也是同样道理全部元素都是整数且数量少时用整数集合 intset一旦不满足条件就升级成 hashtable。这部分理解以后有两个实际收益。第一写数据时注意元素规模小对象不要盲目塞大量字段它们会触发编码升级内存占用可能从几十字节跳到几百字节第二排查内存问题时用OBJECT ENCODING key能看到到底用的是哪种编码这比猜要快得多。3.3 持久化RDB 和 AOF 怎么选别再靠感觉持久化是很多人配置上最随意的一环。RDB 是内存快照通过 fork 子进程生成二进制文件恢复速度快但可能会丢失最后一次快照之后的数据AOF 记录每一条写命令通过 appendonly 开启配合 fsync everysec最多丢失一秒数据但文件体积大恢复速度相对慢。具体配置上生产环境我一般这样取舍RDB 适合做备份和快速重启恢复。定时任务每天做一次BGSAVE冷备份放到异地或者对象存储这个是灾难恢复的底线。AOF 适合保证数据安全。appendfsync设成 everysec在性能和可靠性之间最平衡always 模式每写一条命令都 fsync对性能影响明显除非数据极度重要否则不建议。Redis 4.0 之后有了 AOF 重写机制是将当前内存状态转成命令写入新 AOF跟 RDB 的原理类似都是为了压缩文件体积。Redis 7.x 进一步引入了 AOF 与 RDB 的混合持久化AOF 文件头部用 RDB 格式存全量数据后面再追加增量命令兼顾恢复速度和文件大小。如果用的是较新版本直接开启混合持久化是合理的选择。还有一个实际教训很多人部署 Redis 默认不开 AOF一旦机器重启几小时的数据直接没了。如果业务允许最多丢一分钟数据请至少把 appendonly 打开如果完全不能容忍丢失那要评估的是 OS 层和网络层的稳定性而不是只靠 Redis 自己。4. 集群篇单机跑不动了怎么横向扩展4.1 主从复制最朴素的读写分离单机 Redis 做到了高吞吐的起点但无法应对两个问题单点故障和数据容量的水平扩展。主从复制是第一步结构上一台主节点接收写请求多台从节点接收读请求写数据异步同步给从节点。配置从节点最简单的方式是REPLICAOF master_ip master_port或者直接写进 redis.conf然后从节点默认只读。主从同步的核心是复制积压缓冲区。第一次连接用全量同步主节点生成 RDB 快照发给从节点从节点加载完 RDB 后主节点再把同步期间的写命令通过积压缓冲区补发。后续同步变成增量同步只需要把断线期间的命令补发所以从节点的断线恢复很快只要它的断开时间没有超过缓冲区的覆盖范围。主从模式有几个实践要点。有人说主节点不要开持久化这个说法是错误的主节点至少开 AOF因为从节点的数据源头就是主节点主节点重启如果没持久化可能直接用从节点数据反向追主造成数据丢失。从节点数量不是越多越好每个从节点都会增加主节点的网络输出压力合理做法是挂 2 到 3 个从节点或者用从节点的从节点来分摊。最要紧的是主从复制是异步的写主节点成功后读从节点可能还没有读到强一致场景不能直接依赖从节点读。4.2 哨兵让故障转移自动发生主从模式把故障转移留给了人工哨兵就是来解决这个问题的。Sentinel 是一个独立进程监视主节点和从节点状态在主节点下线后自动选举一个从节点升主并修改其他从节点的主从关系最后把新主节点的地址通知给客户端。哨兵判断下线分两个阶段。单个哨兵自己发现某个节点down了这叫主观下线当多个哨兵都报告同一个节点不可达达到配置的 quorum 数量就更新为客观下线开始触发故障转移。故障转移时选举从节点的依据是优先级、复制偏移量、运行 ID 这几项的综合排序尽量选数据最完整、延迟最低的从节点。生产配置哨兵有两个容易忽略的细节。第一哨兵至少要部署 3 个节点因为仲裁需要多数派如果只有 2 个一个哨兵挂掉就永远达不到 quorum。第二客户端要有哨兵地址而不是固定主节点地址否则主节点切换后老客户端还往旧主节点写等于全部写入失败。像 Jedis、Redisson、go-redis 都有对应的哨兵模式配置启用之后客户端会自动感知主节点变化。给一个最小哨兵配置参考sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 12代表至少两个哨兵同意才算客观下线down-after-milliseconds是判断节点失联的时长parallel-syncs控制在故障转移时同时允许几个从节点同步新主节点设 1 是为了避免网络冲击。这几个参数直接决定故障转移的快慢可以根据业务容忍度调整。4.3 Cluster 模式16384 个槽是怎么分的如果数据量已经超过单机内存读写性能也到了瓶颈就用 Redis Cluster。Cluster 把数据按 key 做 CRC16 哈希对 16384 个槽位取模每台主节点负责一段槽位。客户端根据槽位和节点槽位路由表直接找到对应节点。如果一个 key 不在当前节点上节点会返回MOVED错误同时带上正确节点的地址客户端收到后更新本地路由缓存并重新定向。Cluster 的数据分片有两个特点。一个是使用{hash tag}可以让多个 key 落在同一槽位比如{user:100}.cart和{user:100}.orders这样它们就可以被放进同一个 Lua 脚本或者事务里操作跨槽事务会直接报错这是 Cluster 和单机使用习惯最大的区别。另一个是槽位重分配扩容或缩容时用redis-cli --cluster reshard命令在线迁移槽位迁移过程中 key 会被标为临时迁移状态客户端收到ASK重定向后需要重新请求目标节点这个细节必须由客户端库处理使用集群版客户端连接时一般都已经封装好了。Cluster 模式下每个分片依然可以配置从节点。某个主节点挂掉后它的从节点会被选为主节点接替槽位这就是集群的故障转移如果主节点和它所有从节点都挂了这个分片的槽位就不可用整个集群在默认情况下也拒绝请求所以生产环境每个分片至少保证一个从节点并且考虑把主从分散到不同物理机。主从、哨兵、Cluster 三者怎么选很多团队容易搞混。我的判断是数据量和 QPS 都不高先上主从并搭配哨兵这是性价比最高的高可用方案单机内存已经吃紧数据量超过单台机器承载范围才考虑 Cluster。不要为了“集群听起来高级”就用 Cluster它会带来跨 key 操作限制、客户端复杂度上升、运维难度增大等额外成本。5. 拓展篇Redis 不止缓存还能干这些事5.1 Stream、Lua、Pipeline把 Redis 用出花Redis 是一个通用存储层很多能力平时被缓存场景掩盖了。Stream 是 5.0 引入的消费模型支持消费者组、消息确认、消息回溯基本能覆盖轻量级消息队列的诉求。相比部署一套独立的 MQ如果团队规模不大、性能要求中等用 Stream 可以省掉一个中间件但它没有堆积淘汰、多级存储这类功能服务端就是内存积压太多会占用大量内存。Lua 脚本是 Redis 实现原子多步操作的利器。比如扣库存需要三步判断库存是否足够、扣减库存、记录流水如果分三次发送中间一步挂了状态就不一致。放进一个 Lua 脚本里执行Redis 保证脚本执行期间其他命令不会插入。我见过不少团队买了昂贵的中间件来解决库存扣减问题其实一条 Lua 脚本加一个 key 就搞定了关键就是理解 Redis 单线程执行脚本这个性质。Pipeline 解决的是多命令网络开销问题。平时每条命令都是一次网络往返100 条命令就是 100 次 RTT。Pipeline 把多条命令打包成一次发送一次性拿回所有响应。实际测试中大量小命令的前提下吞吐量可以提升一个数量级。要注意 Pipeline 不等于事务它只是把请求合并发送执行过程中其他客户端的命令也可能穿插进来真正需要事务语义还得用 MULTI/EXEC 或者 Lua。日常开发里还能用 Bitmaps 做签到统计和在线状态用 HyperLogLog 做 UV 统计用 Geo 做附近门店查询。这些功能本质上都是 Redis 对特定数据结构的高效组织把它当成一个多功能工具箱而不是单纯缓存很多业务逻辑就会被简化代价是外部系统数量减少。5.2 监控与性能定位bigkey、慢查询一个都不能少Redis 上线后运维成本最集中的三件事是内存异常增长、命令延迟突刺、偶发连接超时。排查这些问题的入口是三个命令INFO、SLOWLOG、CLIENT LIST。INFO memory能看内存使用量跟碎片率。MEMORY DOCTOR会给内存健康建议MEMORY USAGE key能估算单个 key 的占用。bigkey 排查常规做法是redis-cli --bigkeys它会分段扫描并统计各类数据中最大的 key这个命令对线上风险较低但还是建议在低峰执行。慢查询用SLOWLOG GET查看能直接定位哪些命令执行时间超阈值阈值上限slowlog-log-slower-than默认是 10000 微秒如果业务对延迟敏感建议调到 1000 微秒。连接异常跟CLIENT LIST里的标记有关。如果出现大量客户端处于 blocked 状态要查是否有长时间阻塞命令连接数过高时先看maxclients是否够用再看是否有客户端没关连接的泄漏问题。设置maxmemory和淘汰策略maxmemory-policy是最重要的兜底手段一般缓存场景用allkeys-lru带持久化的业务用noeviction更安全让 OOM 暴露问题而不是悄悄淘汰数据。延迟突变最容易被忽略的因素是持久化。RDB 的BGSAVE会 fork 子进程fork 瞬间如果内存很大会有一段时间的停顿AOF 的 fsync everysec 一般情况下没问题磁盘延迟上升也会直接影响写命令。线上排查时可以用INFO stats里的 expired_keys、keyspace 命中率来辅助判断。6. 源码篇读 Redis 源码推荐这条路线6.1 从入口到命令分发三步建立起全局观新手读源码最容易迷路因为 Redis 代码量不小。我建议三条主线命令是怎么从网络进来、数据是怎么存进内存、持久化是怎么把数据落盘的。第一条线从src/server.c的 main 函数开始初始化完配置、网络监听、事件循环之后请求到达就会激发读事件输入缓冲里的命令被解析后通过命令查找表 dispatch 到具体的处理函数。比如SET对应t_string.c里的setCommand。这种方式下看源码只要抓准命令分发入口基本不会迷路。第二条线看对象系统object.c和t_*.c文件。创建一个 key 时要先创建 redisObject通过 type 和 encoding 决定底层数据结构再调用底层实现的 API。看完这条线你就会理解为什么同一个命令在不同编码下实现完全不同也会理解OBJECT ENCODING背后到底查的是什么。第三条线看rdb.c和aof.c。RDB 的序列化、反序列化AOF 的追加、重写、加载逻辑都在这里面。配合着BGSAVE的实现可以看懂 fork 子进程与写时复制的配合。这也是生产排查里理解“为什么大内存实例 BGSAVE 会导致延迟”最直接的突破口。6.2 源码里值得抄的数据结构与工程技巧读源码千万别只是看热闹Redis 的代码里有很多可以直接抄进自己项目的设计。第一个是命令注册表。所有命令集中在commands.c里每条命令都声明了名字、参数、回调函数、可用的 flag。这个设计把命令元信息和实现解耦加新命令只需要改注册表非常干净。我在自己负责的网关项目里也借鉴了这个模式注册新接口时不用到处加 if-else。第二个是内存估算和编码切换的时机。Redis 每个类型都有list-max-listpack-size、hash-max-listpack-entries这类配置平衡内存和性能。它们背后其实是工程里的经典思路小数据用紧凑格式大数据用通用格式临界点用配置暴露出来。理解和调优这些参数比对别人现成配置照抄要有效得多。第三个值得关注的是惰性删除和内存淘汰策略。Redis 的过期 key 不是靠定时器一个个扫出来的而是依赖过期键惰性访问、定期抽样和淘汰机制的组合。源码里expire.c的activeExpireCycle控制抽样频率evict.c负责内存淘汰。这套“懒 定期 淘汰兜底”的思路对解决各种资源回收问题都有启发。如果有精力networking.c里事件循环、读写缓冲区的处理也值得精读。它的代码风格严谨变量命名、错误处理都很规范是学习 C 工程实践很好的教材。不过这一段依赖前面的事件模型基础建议放在熟悉源码结构之后再看。7. 实战问题排查实录踩过的坑帮你提前排雷7.1 高频问题速查表我把过去几年线上遇到的高频 Redis 问题整理成一个速查表方便对照。现象可能原因排查命令/手段解决方向本地能连远程连不上bind、protected-mode检查 redis.conf用 netstat 看端口监听bind 内网地址配密码再开放端口内存突然暴涨bigkey、内存碎片redis-cli --bigkeys、MEMORY DOCTOR拆分大 key考虑 lazyfree 异步删除命令延迟突刺AOF fsync、RDB fork、慢命令SLOWLOG GET、INFO stats调低 slowlog 阈值控制大 key避免频繁 BGSAVE主从数据不一致异步复制延迟INFO replication看 lag关键数据不要读从节点或者改读写逻辑写入报 MISCONF无法 RDB 持久化看日志中的磁盘不足增大磁盘或临时关闭持久化尽快定位根因连接数打满客户端泄漏CLIENT LIST、INFO clients检查连接池是否合理复用排查未关闭的连接分布式锁失效锁过期时间太短看业务执行耗时用看门狗续期或按最大耗时设过期时间表里的问题我基本都亲手遇到过。最典型的是远程连不上的问题查了半天网络最后发现是 protected-mode 在作祟这种基础配置一定要在最开始就确认好避免浪费半天时间。7.2 我的十条避坑清单最后分享几条最容易复制的避坑经验都是普通文档里不会写的。第一不要在 redis.conf 里用默认密码方式。生产环境用官方 ACL 功能给不同业务配不同账号权限能避免一个密码泄露导致整个 Redis 全被扫。第二不要随便用FLUSHALL或者FLUSHDB如果真要清空先确认是否做了备份再考虑方向。线上我见过有人一条命令清掉整个测试环境数据从此养成了习惯危险命令一律加别名确认。第三所有涉及过期时间的配置都要考虑业务高峰的余量。像验证码、优惠券这种业务过期时间设成刚好等于业务逻辑时间一旦网络抖动或者任务积压就会产生诡异问题。第四写缓存之前评估 key 数量。如果 key 数量动辄千万级先规划好命名规范和定期淘汰策略否则 Redis 迟早被没用的 key 塞满。第五连接池不要开得过大。很多人以为连接池越大越好实际上 Redis 是单线程处理命令连接过多会让事件循环被各种 I/O 打断吞吐量和延迟反而更差。常规服务 200 到 500 个连接已经足够压测是验证连接池大小的唯一标准。第六热点 key 要主动治理。比如爆款商品、热门帖子可以把同一条数据复制多份用随机后缀分散到多个 key 和节点上避免单 key 打爆单节点。第七批量操作时记得用 MGET 和 pipeline一份时间能省下九十份的网络开销。第八Redis 里默认没有主键约束业务侧的幂等设计不能省。重复消费、重复扣款这类问题一定要靠业务建唯一标识和 Lua 脚本兜底。第九升级版本要谨慎关键小版本之间行为可能有变化尤其是持久化格式、复制协议、内存编码这几种建议先在测试环境把版本差异测透。第十也是最重要的一条把 Redis 当成一个会“丢失数据”的组件来设计。Redis 即使开了 AOF依然存在进程崩溃、磁盘问题、误操作等风险。重要的业务数据不能只放在 Redis 里要留一份在数据库或者设计好重建缓存的链路。抱有这种预期之后你在做架构决策时就会更稳不会因为 Redis 挂了导致整个系统一起崩。我带团队时经常要求新人把这条学习路径完整走一遍基础、原理、集群、源码每多补一层线上出问题时你的底气就多一分。许多人一开始只看命令怎么用遇到故障就慌根源就是缺了原理和集群这两层。选择哪些资料不重要重要的是你有没有把这套完整的知识框架搭起来。Redis 的全栈之路没有捷径但按这个顺序走确实能少踩很多我踩过的坑。