news 2026/10/5 10:53:48

Redis核心技术与实战:从缓存加速到分布式锁与集群高可用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis核心技术与实战:从缓存加速到分布式锁与集群高可用

Redis 这玩意,我第一次接触还是因为线上接口慢到被业务方打电话催,当时第一反应就是查数据库索引,结果索引没问题,单纯就是热点数据把数据库连接打满了。后来把一批热门数据挪进 Redis,接口直接从 300ms 干到 10ms 以内,那会儿才知道什么叫“缓存中间件”。后面几年从单机缓存一路折腾到主从、哨兵、Cluster,再踩遍分布式锁和持久化恢复的坑,算是彻底把 Redis 的脾气摸清了。这篇不整官方文档那套,按我真实落地顺序来:先搞懂它到底解决什么问题,再动手安装配好,然后逐个讲数据类型、缓存治理、持久化、分布式锁、高可用集群这些硬核场景,最后把连接超时、大 Key、慢日志这些实战排查经验也一并分享。不管你是刚接触 Redis 的新手,还是已经在生产环境维护 Redis 实例的开发,这篇差不多能让你把高频知识点和实操坑一次带走。

1. Redis到底解决了什么问题,先想明白再上手

1.1 从“数据库太慢”这一件事说起

很多新手一开始就说“我要给项目加 Redis”,但问他为什么加,回答通常是“大家都用了,肯定有用”。这种用法最危险,因为方向错了后面全是补救。Redis 最核心的价值就一句话:把频繁读取的数据从磁盘挪到内存,把几十毫秒的查询降成微秒级返回。

举个例子,你一个商城首页要展示热销商品列表,如果每次请求都从 MySQL 里查,QPS 高了以后数据库连接被占满,响应时间直线上升。但如果你把热销商品列表序列化后放进 Redis,Key 设计成product:hot:list,缓存命中时直接从内存返回结果,不碰数据库。压测数据我实测过:单机 MySQL 极限状态也就几千 QPS,而同一台机器上的 Redis 单实例读性能能到 10万+ QPS,差了不止一个量级。

但注意,Redis 不是用来存全量数据的,内存贵是现实问题。它解决的是“读多写少、热点集中、实时性要求高”这类数据场景,比如会话信息、商品详情、排行榜、验证码、接口频率限制、分布式锁等。理解这一点,你就不会拿它当万能存储乱塞数据。

1.2 数据结构才是它真正的护城河

很多人拿 Redis 和 Memcached 对比,觉得都是内存缓存,有啥区别。最本质的区别就是数据结构。Memcached 只能存key-value,你所有的复杂度都在应用层解决;而 Redis 自带五大数据类型,每一种都对应一类典型问题。

简单列下使用心法而不是八股文:

  • String:最通用的缓存形态,也适合做计数器、分布式 ID、验证码,因为 Redis 的 INCR/DECR 是原子操作。
  • Hash:适合存对象,比如用户信息、商品信息,可以只修改其中一个字段,不用整个对象反序列化再写进去。
  • List:支持双端操作,天然适合做简单消息队列、最新消息列表。
  • Set:自动去重,还能做并集、交集、差集,适合做标签系统、共同好友这类场景。
  • ZSet:带权重排序的集合,是排行榜、延时队列的最佳拍档,也能用 score 存储时间戳实现定时任务扫描。

初学阶段我建议把每个类型至少用一个真实案例串起来,死记命令背得快忘得也快,但如果你能说明白“为什么这个场景用这个结构”,面试和实战都不太会翻车。

1.3 部署形态的选择:单机、主从、哨兵、Cluster

这个阶段很容易犯的错是“先装个单机再说”。单机 Redis 确实能跑,但它同时意味着单点故障:内存挂掉、进程挂了、机器重启了,缓存直接清零。你要评估业务容忍度再决定架构。

我给一个比较务实的选型建议:

  • 并发量不大、缓存可丢失、运维能力一般,可以用单机 Redis,但一定开持久化。
  • 需要高可用、读多写少,做主从复制,写走主、读走从,主挂了还能手动提升从节点。
  • 需要自动故障转移,加哨兵(Sentinel),监控主节点状态,主挂了自动选新的主。
  • 数据量很大或写并发高到单机撑不住,上 Cluster 集群,把数据通过哈希槽分散到多个节点。

在我维护过的业务里,大部分项目其实到“主从+哨兵”就够了,直接上 Cluster 反而因为跨节点的复杂操作和客户端字段限制带来不少额外负担。这个决策后面专门讲集群的时候再展开。

2. 手把手把 Redis 装起来:Windows、macOS、Linux 一次说清

2.1 Windows 下安装:免安装解压版最省心

Redis 官方并不直接提供 Windows 版本,但开源社区有维护编译好的 Windows 版本,常见的有 5.0.14.1 这类镜像版本。安装方式推荐直接下载 zip 免安装包,解压后目录里有redis-server.exe和redis-cli.exe,比安装包更干净。

启动方式很简单,命令行进到目录后执行:

redis-server.exe redis.windows.conf

然后另开一个窗口测试:

redis-cli.exe -p 6379 127.0.0.1:6379> ping PONG

能返回 PONG 就说明服务已经正常跑起来了。我习惯把 Redis 注册成 Windows 服务,避免每次开机手动开窗口:

redis-server.exe --service-install redis.windows.conf --service-name RedisService redis-server.exe --service-start --service-name RedisService

注意 Windows 版本的 Redis 更新普遍滞后于官方版本,所以生产环境如果是 Linux 服务器,别用 Windows 版本跑,仅适合本地开发调试。

2.2 macOS 下用 Homebrew 一装到底

macOS 上安装推荐用 Homebrew,简单到没什么技术含量:

brew install redis

安装完成后可以先用前台方式验证:

redis-server /opt/homebrew/etc/redis.conf

如果希望开机自启,用 brew services:

brew services start redis

Homebrew 的配置文件默认在/opt/homebrew/etc/redis.conf(Apple Silicon 路径)或/usr/local/etc/redis.conf(Intel 版本),需要改密码、持久化参数时就去这里改,改完重启服务生效。

2.3 Linux 上通过 Docker 快速部署

Linux 上最省事的方式是用 Docker,尤其适合测试环境快速拉起一个实例:

docker run -d --name redis \ -p 6379:6379 \ -v /data/redis:/data \ redis:7.0 redis-server --appendonly yes

这里把容器的/data目录挂载到宿主机,开启 AOF 持久化后,即使容器删掉重建,数据也还在。生产环境我会用 docker-compose 管理,把端口、密码、持久化配置都固化在文件里,方便团队复用。

不过有一点要提醒:Docker 跑 Redis 在 I/O 密集场景下,磁盘持久化性能比裸机部署有损耗,尤其是 AOF 重写时频繁 fsync 可能会引起阻塞。追求极致性能的生产环境,我更建议用二进制方式直接部署在物理机或虚机上,Docker 更多用于测试、CI、临时环境。

2.4 配置密码和访问控制,别裸奔上线

Redis 默认无密码,且默认绑定本机127.0.0.1。你在本地开发无所谓,但一旦部署到服务器,如果protected-mode没关且没设密码,公网扫到直接就能连进来,可以刷数据、写定时任务、植入挖矿程序。我见过不止一次有人 Redis 被入侵,就是因为裸奔。

设置密码的两种方式,运行时动态设置仅临时生效:

127.0.0.1:6379> CONFIG SET requirepass "your-strong-password"

要永久生效,在配置文件中加一行:

requirepass your-strong-password

同时建议修改默认端口,别用 6379,可以改成五位数高位端口,降低被扫描到的概率。更安全的做法是开启 ACL,给不同业务配不同的用户权限:

ACL SETUSER appuser on >app_password ~cache:* +@read +@write -@admin

这样一个业务一个账号,权限只开到它需要的那部分 key 前缀上,出问题也能定位到是谁在误操作。

3. 五种核心数据类型与高频命令实战

3.1 String:不止能存字符串,还能做计数器

String 是 Redis 里最基础的类型,SET key value、GET key就是它的日常形态。但它真正值钱的特性是原子自增自减,也就是 INCR、DECR、INCRBY、DECRBY 这些命令。

比如实现一个接口限流:用户每分钟最多调用 10 次,可以用过期时间和自增配合:

127.0.0.1:6379> SET user:123:limit 0 EX 60 NX 127.0.0.1:6379> INCR user:123:limit

第一次设置时用EX 60指定 60 秒过期,NX保证不存在时才设置,之后每次请求 INCR 一次,当返回结果大于 10 时拒绝请求。这套方案实现限流非常轻量,而且 INCR 原子性不用加锁,极端并发下也不会计数错乱。

做分布式 ID 也是类似思路,用 INCR 生成全局唯一单调递增编号,比数据库自增 ID 更抗压,不过要注意 Redis 持久化出问题时有 ID 回退的隐患,所以对绝对严谨的 ID 场景要谨慎。

3.2 Hash:对象存储的正确打开方式

Hash 类型相当于一个 key 对应多个 field-value,适合存对象。例如用户信息:

HMSET user:1001 name "zhangsan" age 28 city "beijing" HGET user:1001 name HGETALL user:1001

如果换成 String 存,通常就两种做法:要么把整个对象序列化成 JSON 作为 value,要么拆成多个 key。JSON 方案的问题是改一个字段要整体读改写,大对象频繁更新很亏;多 key 方案的问题是 key 数量爆炸且不利于管理。用 Hash 就舒服很多,可以单独更新某个 field,内存消耗也比 JSON 小。

但 Hash 也有坑:field 不支持单独设置过期时间,只有整个 key 能设 EXPIRE。所以如果你需要“用户 30 分钟不活跃就删除 session”这种细颗粒度过期,Hash 不太好实现,要么整体过期,要么靠应用层清理。

3.3 List:消息队列和最新列表都能干

List 是双向链表,可以从左边 LPUSH,右边 RPOP,实现先进先出:

LPUSH task:queue "{json data}" RPOP task:queue

更推荐用阻塞版本BRPOP task:queue 0,当队列为空时客户端挂起等待,而不是轮询空转。这比数据库表做任务队列省心很多,也避免了空查询打爆数据库的问题。我早期做异步任务的时候,就是用BRPOP实现了简单的生产者消费者模型,代码量极小。

List 的另一个经典场景是存最近列表。比如用户最新浏览记录,用LPUSH写入,再用LTRIM key 0 99裁掉超过 100 条的数据,列表永远只保留最近 100 条,非常高效。但要注意,List 中间插入、按索引取值的操作是 O(N),数据量大了以后要避免高频使用LINDEX这类命令。

3.4 Set 与 ZSet:去重、标签、排行榜一次搞定

Set 适合做自动去重的集合,比如用户标签:

SADD user:1001:tags "java" "redis" "mysql" SISMEMBER user:1001:tags "redis" // 判断是否存在

不同用户之间的共同标签用交集,推荐关注列表用并集,差异用户用差集,这些都是 Set 的原生操作,在应用层写起来非常绕的需求,Redis 一条命令就出来了。

ZSet 是带分数的有序集合,每个成员跟一个 double 类型的 score,按 score 排序。排行榜是最常用的场景:

ZADD leaderboard:game1 100 "playerA" ZADD leaderboard:game1 90 "playerB" ZREVRANGE leaderboard:game1 0 9 WITHSCORES

这段命令就是取排行榜前 10 名,按分数从高到低。ZSet 还能用 score 存时间戳实现延时队列,启动一个定时任务去ZRANGEBYSCORE key 0 now,把到期的任务取出来消费即可。这个思路比轮询数据库高效很多。

3.5 综合小案例:实现一个热门商品排行榜

把上面这些串起来,写一个简单但完整的热门商品榜。商品每次被点击时,用 ZSet 累加热度:

ZINCRBY product:hot:rank 1 "product:101" ZINCRBY product:hot:rank 1 "product:102"

页面加载时取热度前 20 名的商品 ID,再回查数据库补齐名称、价格、图片。为了防止榜单无限膨胀,定期清理过期商品,可以用ZREMRANGEBYSCORE product:hot:rank 0 min_score把低热度商品移除掉。

这里面 ZINCRBY 是原子操作,并发点击不会丢失计数。如果还需要“过去 7 天热门榜”,可以按天拆分 key,比如product:hot:rank:20250120,最后对不同日期的有序集合做 ZUNIONSTORE 聚合,就是时间段维度排行榜。这个案例做出来,你对 Redis 的熟练度基本就超过大部分“会用 SET/GET”的人了。

4. 缓存治理:穿透、击穿、雪崩,一次讲透

4.1 缓存穿透:查一个根本不存在的数据,打爆数据库

缓存穿透的意思是,请求的数据在缓存和数据库里都不存在,于是请求直接落到数据库。恶意攻击者可以故意请求不存在的用户 ID,如果每次都穿透到数据库,数据库压力会很大。解决方案有三个层次:

第一层,接口参数校验。非法的参数直接拦截掉,别往下走。第二层,缓存空值。如果数据库查不到,就往 Redis 写一个空值,并设置较短的过期时间,比如 60 秒,避免同一 key 反复穿透。第三层,布隆过滤器。把所有可能存在的 ID 先放进布隆过滤器,请求进来先判断 ID 在不在过滤器里,不在就直接返回,连缓存都不用查。

布隆过滤器的原理值得多说两句。它用多个哈希函数把元素映射成位数组上的多个点,判断一个元素“一定不存在”很准确,但判断“存在”有小概率误判,因为不同元素可能把位数组上的位置重叠了。Redis 4.0 之后提供了 RedisBloom 模块,可以用 BF.ADD 和 BF.EXISTS 来操作。设置合理的容量和误判率很关键,容量太小会导致误判率快速上升。

4.2 缓存击穿:热点 key 过期的一瞬间,请求全打到数据库

缓存击穿和穿透非常像,但击穿针对的是“缓存里有但正好过期”的某个热 key。这个 key 过期后,大量并发请求瞬间涌到数据库,相当于缓存防线出现了一个缺口。

两个主流解法:

互斥锁。在缓存重建期间只允许一个线程去查数据库并回写,其他线程等待。用 SETNX 实现互斥锁,拿到锁的线程查库写缓存,没拿到锁的线程 sleep 几十毫秒后重试读缓存。我一般设置 1 秒内重试多次,避免线程长时间阻塞。这个方案实现简单,能挡住绝大多数击穿场景,缺点是存在加锁、等待的耗时,而且要注意锁的过期时间,防止线程宕掉后锁不释放。

逻辑过期。就是不给缓存设物理过期时间,而是在 value 中存一个业务过期时间戳。读取时发现逻辑过期后,返回旧值,同时异步用一个独立线程去重建缓存并更新过期时间。这种方式能扛住高并发且不会过度阻塞线程,但实现复杂度高,且返回的数据可能短暂过期。如果业务允许读取短暂旧数据,这个方案更优雅。

4.3 缓存雪崩:大量 key 同时过期,Redis 被击溃

雪崩是击穿的扩大版。大量 key 集中在同一时间过期,或者 Redis 实例宕机,导致大批请求直接打到数据库。在大型活动场景里,如果缓存设置了相同的过期时间,就容易引发雪崩。

我的处理习惯是:

  • 过期时间加随机值。比如基础过期时间 10 分钟,再叠加一个 0 到 300 秒的随机偏移量,让 key 的过期时间在时间轴上散开。
  • 设置多级缓存。比如本地缓存 + Redis 缓存,Redis 挂了还能靠本地缓存扛一阵。
  • 用 Redis Cluster 或主从提高可用性,避免单点宕机。
  • 数据库层做熔断降级,超负荷时放弃非核心请求,保证主流程可用。

预防雪崩本质上靠的是“分散”而不是“硬扛”。过期时间打散、流量打散、压力分散,这个思想贯穿所有缓存治理。

4.4 缓存一致性:先更新数据库,还是先删缓存?

缓存一致性是面试几乎绕不开的问题。最经典的两个操作顺序要搞清楚。

先更新数据库再删缓存,是目前实践中最常用的方式。为什么不是先更新缓存?因为并发场景下先写缓存很容易出现脏数据覆盖。删缓存的优势是等下次读请求再去数据库加载最新值,成本低。

但“先更库再删缓存”也有极端竞态问题:线程 A 更新数据库,线程 B 读取旧值并回写缓存,然后线程 A 删除缓存,此时缓存里留下的反而是旧数据。解决思路是延迟双删:删除缓存后,等待几百毫秒,再删一次,保证并发读线程回写的旧缓存被最终清掉。或者更可靠的方式是让缓存写入带上版本号或时间戳,读请求回写时比较版本,旧版本不写入。后者实现成本稍高,但能从根本上避免脏写。

按我现在的习惯,一般用“先更新数据库,再删除缓存”加“延迟双删”兜底,并且对一致性要求极高的数据给缓存加一个比较短的过期时间作为底线兜底。

5. 持久化机制:RDB 和 AOF 到底该怎么选

5.1 RDB 快照:全量备份,恢复快但可能丢数据

RDB 是 Redis 把内存数据全量写入磁盘的文件快照。默认配置是save 3600 1,表示 3600 秒内至少有 1 次写入才触发一次快照;还有save 300 100,300 秒内 100 次写入触发;以及save 60 10000,60 秒内 1 万次写入触发。配置是或的关系,满足任意一条就执行。

RDB 的优点是文件紧凑,恢复速度快,适合做冷备和灾难恢复。缺点是快照会丢失最后一次快照之后的新数据,比如 59 秒内刚写入的数据突然宕机,这部分数据就丢了。快照过程本身用 fork 子进程实现,不影响主线程继续处理命令,但如果数据量很大,fork 瞬间可能因为复制内存页表导致短暂停顿,这也是大实例要注意的点。

5.2 AOF 日志:记录每一次写操作,最多丢 1 秒数据

AOF 的工作方式是把每次写命令追加到日志文件末尾,类似 MySQL 的 binlog。开启方式是配置appendonly yes。刷盘策略有 three 个:

  • appendfsync always:每个命令都 fsync 到磁盘,最安全但也最慢。
  • appendfsync everysec:每秒刷一次盘,最多丢 1 秒数据,性能和安全折中,生产环境常用这个。
  • appendfsync no:由操作系统决定什么时候刷盘,性能最高但丢数据风险也最大。

AOF 文件会不断增长,Redis 会通过bgrewriteaof进行重写,把多条命令压缩成最终状态对应的最少命令数。比如对一个 key 做了 100 次 SET,重写后就只剩最后一条 SET。

AOF 的缺点也很明显:文件比 RDB 大不少,恢复时重放日志比加载 RDB 慢,而且 fsync 策略配置不当会明显影响写性能。我记得第一次用 always 策略跑批量任务,原本几毫秒的写请求直接变成几十毫秒,后来果断改回 everysec。

5.3 混合持久化:两个都要,鱼和熊掌兼得

从 Redis 4.0 开始支持混合持久化。开启aof-use-rdb-preamble yes后,AOF 文件开头会先写一段 RDB 格式的数据,后续再追加命令。这样重启恢复时,先加载 RDB 快速恢复大部分数据,再重放少量 AOF 命令补全增量,既快又少丢数据。

现在的版本默认已经开启混合持久化。我的生产配置通常是:

appendonly yes appendfsync everysec aof-use-rdb-preamble yes

同时保留 RDB,因为 RDB 文件体积小,适合定期备份到对象存储或者异地机房。这样“恢复快、丢数据少、备份方便”三个目标都能覆盖。

5.4 数据恢复的实操过程与踩坑记录

恢复数据时有两种典型路径。如果只有 RDB,把 dump.rdb 放到配置指定的目录,重启后自动加载。如果有 AOF,Redis 启动时会优先加载 AOF 文件,因为 AOF 包含的数据更完整。

我踩过的一个经典坑是:某次手动后台重启 Redis,启动日志提示加载成功,但数据少了最后一小段。排查发现是 AOF 文件损坏,重启时 Redis 拒绝加载。此时如果用redis-check-aof --fix修复,它会截断损坏的尾部命令,但代价是丢掉尾部部分数据。所以关键教训是:持久化文件必须定期做备份,且备份要验证能否成功加载,不要等到事故发生时才发现 RDB 文件已经损坏或残缺。

还有一个细节:RDB 触发非常频繁会影响性能,比如把 save 参数调得过于激进。一般不建议为了“更安全”而把save 60 10000改成save 10 100,因为频繁 fork 写盘会让 Redis 性能大幅下降。选一个合理的业务容忍度,配合 AOF,才是稳妥做法。

5.5 数据安全的进一步建议

关于数据安全,我建议在配置层面做一个兜底:

  • 开启 AOF,用 everysec 刷盘。
  • 保留 RDB 做凌晨冷备,备份文件至少保留最近 7 天。
  • 绝对不要在生产环境用CONFIG SET save ""关掉持久化,除非你只把 Redis 当临时缓存且能接受全部丢失。
  • 用SHUTDOWN SAVE正常关闭 Redis,而不是直接 kill -9。虽然 AOF 下 kill 也不至于丢太多数据,但优雅关机能保证 RDB 完整性。

6. 分布式锁:SET NX EX 就能实现,但坑很多

6.1 为什么需要分布式锁

日常开发里,多个 JVM 实例同时处理同一条订单数据,比如同时执行扣减库存。JVM 内synchronized只能锁住当前进程,其他实例照样并发执行,结果就是超卖。分布式锁的本质是让多个进程通过一个公共组件(Redis、ZooKeeper 等)来协调,保证同一时刻只有一个进程能执行关键操作。

Redis 做分布式锁的核心命令就是 SET 的扩展参数:

SET lock:order:1001 unique_token NX EX 30

NX表示只有当 key 不存在时才设置成功,EX 30表示锁 30 秒后自动过期。如果设置成功,说明拿到了锁;逻辑处理完后,删除锁释放。删除锁时一定要用 Lua 脚本校验持有者身份,防止误删别人的锁。

6.2 释放锁的细节:别把别人的锁删了

最典型的坑是:线程 A 拿到锁,执行时间超过了锁的过期时间,锁自动释放,线程 B 拿到锁开始执行。这时线程 A 执行完毕,直接 DEL 删锁,结果把线程 B 的锁删掉了。解决方式是设置锁的 value 为唯一标识,比如 UUID,删除前先 GET 判断是否属于自己的锁,再用 Lua 脚本保证判断和删除的原子性。

常见做法:

if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

注意这个脚本必须用 EVAL 执行,保证 check 和 del 两步之间不会被其他命令插入。但是即便这样,还存在一个无解问题:如果不做锁续期,锁就可能在业务没结束前过期;如果做了续期,又增加了实现复杂度。这也是为什么很多人建议直接用 Redisson,它的看门狗机制会自动续期。

6.3 Redisson 与看门狗续期

Redisson 是 Java 生态里最常用的 Redis 客户端之一,它封装了完整的分布式锁 API,并自带看门狗机制。简单说,你拿锁时设置默认 30 秒过期,Redisson 会启动一个后台线程,每 10 秒检查一次,如果锁还在且业务线程还活着,就自动把过期时间续到 30 秒。业务执行完释放锁时,续期线程也会被取消。

用 Redisson 写分布式锁大概是这种感觉:

RLock lock = redissonClient.getLock("lock:order:1001"); boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS); if (locked) { try { // 业务逻辑 } finally { lock.unlock(); } }

tryLock 的参数里包含了等待时间、租约时间等。如果你用了看门狗,不传 leaseTime 的话,默认会自动续期。这套方案解决了我上面说的“锁过期导致业务冲突”和“误删锁”两个问题,生产代码里强烈推荐。

6.4 主从切换和 RedLock 的学术争论

这里要提一个进阶话题:Redis 主从架构下,锁写入主节点后,主节点还没同步到从节点就宕机了,从节点被提升为主节点,锁数据就丢了,另一个线程就可能拿到同一把锁。对于严格不允许并发执行的场景,这是个隐患。

业界有 RedLock 方案:向多个独立的 Redis 节点同时加锁,超过半数节点加锁成功才认为锁生效。但 RedLock 在分布式系统领域一直有争议,因为它依赖各个节点的时间假设,在某些极端情况下依然可能失效。我的看法是,如果业务对分布式锁的安全性要求极高,首先要考虑是否应该引入 ZooKeeper 这类线性一致性更强的组件;如果是在 Redis 体系内,尽量使用全哨兵模式下的主节点加锁,并在业务上做好幂等兜底。追求绝对安全只能靠多种机制叠加,没有银弹。

7. 高可用与集群:从主从复制到 Cluster

7.1 主从复制的基本原理

主从复制解决了两个问题:数据副本和高可用基础。主节点负责写,从节点复制主节点的数据,同时负责读流量。配置也很简单,在从节点配置里加一行:

replicaof master-ip 6379

或者运行时就执行REPLICAOF master-ip 6379。复制链路分为全量同步和增量同步,主节点会把当前数据生成 RDB 快照传给从节点,并缓存复制期间的新写命令,从节点载入快照后再继续接收增量命令。

主从存在的坑:从节点如果因为网络闪断和主节点断开,会自动重新连接并尝试增量同步,但如果断连时间过长,复制积压缓冲区里的新命令已经被覆盖,就会退化成全量同步,在大数据量下同步时间会比较久。所以积压缓冲区大小要结合网络稳定性和写入量来设置,默认值太小在抖动频繁的环境会引发反复全量同步的问题。

7.2 Docker 快速搭建一主一从

用 Docker 搭建两个 Redis 实例做一主一从来验证原理,非常方便。先创建自定义网络:

docker network create redis-net

启动主节点:

docker run -d --name redis-master \ --network redis-net -p 6379:6379 \ redis:7.0 --requirepass masterpassword

启动从节点并指定主节点:

docker run -d --name redis-slave \ --network redis-net -p 6380:6379 \ redis:7.0 --replicaof redis-master 6379 --masterauth masterpassword

容器内用 redis-cli 验证:

docker exec -it redis-slave redis-cli -p 6379 INFO replication

如果看到role:slave且master_link_status:up,说明复制链路正常。这时候在主节点写数据,从节点再 GET 就能查到。

注意生产环境里从节点默认是只读模式,如果要让从节点支持写操作,必须显式配置replica-read-only no,但我基本不建议这么做,主从职责清晰比什么都重要。

7.3 哨兵模式:让故障转移自动化

主从手动切换有个致命问题:主节点宕机时,如果没人手动提升从节点,整个写链路就瘫痪了。哨兵(Sentinel)就是为了解决这个问题。它像一个监控进程,不断给主节点发心跳,发现主节点客观下线后,会通过选举选出一个新的主节点,然后把其他从节点重新指向新主。 选型上我会部署三个哨兵节点,形成奇数个,避免因网络分区出现脑裂。哨兵本身也是一个 Redis 进程,用redis-sentinel sentinel.conf启动。核心配置有:

sentinel monitor mymaster master-ip 6379 2

最后的数字 2 表示判断主节点客观下线至少需要 2 个哨兵同意。线上还有两个重要参数:down-after-milliseconds表示多久没有响应就判定主观下线;failover-timeout表示故障转移的超时时间,需要根据网络情况合理调整,太短容易转移失败,太长会导致业务长时间不可用。

实测下来,哨兵本身就经历过一次脑裂场景,最后决定把quorum严格设为哨兵数量的一半以上,同时开启sentinel auth-pass mymaster password和主从密码,防止无鉴权节点干扰选举。

7.4 Redis Cluster:数据分片与多主多从

如果单台 Redis 内存和写入吞吐已经无法满足业务,需要把数据分散到多个节点,这时用 Cluster。它把数据空间划分成 16384 个哈希槽,每个主节点负责一部分槽位,客户端根据 CRC16 算法计算 key 所在的槽位,路由到对应节点。

Cluster 的最小推荐配置是 3 主 3 从。创建集群的流程一般是:

redis-cli --cluster create \ 10.0.0.1:7000 10.0.0.2:7000 10.0.0.3:7000 \ 10.0.0.4:7001 10.0.0.5:7001 10.0.0.6:7001 \ --cluster-replicas 1

--cluster-replicas 1表示每个主节点配一个从节点。Cluster 模式需要注意的坑:

  • 涉及多 key 的操作(比如 MGET、跨 key Lua 脚本)要求 key 必须在同一个槽,可以通过 hash tag 实现,比如把订单和用户 ID 都放到同一个{user:1001}前缀下。
  • 客户端要支持 Cluster 协议,比如 Java 的 Redisson、Jedis 集群模式、Lettuce 集群模式,普通单机客户端连集群会直接报 MOVED 错误。
  • 槽位迁移期间可能涉及较复杂的运维操作,推荐配合 Redis 官方工具或可视化运维平台管理。

如果你们的 Kubernetes 集群里已经比较成熟,也可以直接用 Redis Operator 在 K8s 里创建 Cluster。它本质上是把多节点编排、配置、升级和健康检查全部自动化了,比手动创建繁琐的集群省力很多。但底层原理还是这些,所以我总建议先手动搭一遍再交给 Operator。

7.5 K8s 中部署 Redis 集群的注意点

K8s 部署 Redis Cluster 时,首要问题是网络标识稳定性。Pod 重建后 IP 会变,而 Redis 节点互相通信必须依赖稳定的域名或 IP。目前主流方案是通过 StatefulSet 创建带稳定网络标识的 Pod,加上 Headless Service,让每个节点通过redis-0.redis-headless.namespace.svc.cluster.local这样的域名互相发现。

还要注意存储卷:每个 Redis Pod 都要挂载独立的 PVC,存储 AOF 和 RDB 文件,不能多个节点共用一份存储。一个很常见的错误是把persistentVolumeReclaimPolicy设成 Delete,结果删掉一个故障 Pod 时数据盘也被回收,造成数据无法恢复。生产环境建议用 Retain 或者常规云平台默认策略,至少保证手动恢复的可能。

8. 可视化工具和日常排查

8.1 可视化工具选型:RedisInsight 和 Another Redis Desktop Manager

命令行用多了,还是想看图形的数据变化,尤其排查 key 数量和内存分布的时候,可视化工具能省很多事。我常用的工具有两个。

RedisInsight 是 Redis 官方出的 GUI 工具,界面清爽,功能涵盖连接管理、浏览器、数据库指标、慢日志、命令行、内存分析等。支持 Windows、macOS、Linux,下载安装后直接从界面配置连接即可。它对 Redis 7 的新命令支持最好,我日常调试最新特性都会用它。

Another Redis Desktop Manager 是开源社区维护的工具,界面布局更适合习惯老牌 Redis Desktop Manager 的人,它对连接树、多实例管理、内存分析都有支持。我之前用它排查过节点间 key 分布情况,确实直观。

连接工具使用的通用建议:不要在生产环境直接连线上 Redis 乱执行危险命令,先用INFO、DBSIZE、SCAN这些只读命令观察,修改配置尽量走配置管理系统,而不是用CONFIG SET临时改动。

8.2 经典报错:Command timed out 怎么排查

我在本地开发时经常遇到Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException,这是 Spring Boot 项目里很常见的报错。它的字面意思是 Lettuce 客户端在指定时间内没收到 Redis 的响应,默认超时一般是 60 秒左右(或者你配置的 spring.redis.timeout)。

出现这种报错,我一般按顺序排查:

  1. Redis 是否还活着:redis-cli ping,如果连通很快返回 PONG,说明网络和进程层面没大问题。
  2. 是否连接被耗尽:执行INFO clients查看connected_clients,Spring 默认 Lettuce 连接池连接数通常在 8 到 16 之间,如果被占满,请求只能等待得超时。
  3. Redis 是否有慢命令阻塞:用SLOWLOG GET查慢日志,如果发现有KEYS *、HGETALL大 key、SORT这种耗时命令,就解释得通了。
  4. 是否是网络抖动:检查客户端到 Redis 服务器的网络延迟,观察报错是否是突发性、短时间恢复。如果用 Kubernetes 网络或云上安全组,考虑是否丢包。

有次生产事故就是因为一个大 Hash 的 HGETALL 动辄几秒,直接把连接池占满,所有请求都超时。后来做成字段拆分 + 分页读取,问题立刻缓解。

8.3 慢日志定位和 big key 扫描

Redis 的执行是单线程的,一个慢命令会阻塞所有后续命令,所以“慢”在 Redis 里是大事。慢日志配置:

slowlog-log-slower-than 10000 slowlog-max-len 128

上面的 10000 单位是微秒,也就是 10 毫秒以上的命令会被记录,slowlog-max-len限制最多保存多少条。用SLOWLOG GET 10就能看最近的慢命令。

big key 扫描也用redis-cli --bigkeys扫一遍,它会找出每个类型里最大的几个 key。注意这个命令在数据量大时会比较耗时,建议在低峰期执行。处理 big key 的方法:String 大 value 考虑压缩或拆分,Hash 大 key 考虑按字段拆成多段,List 大 key 考虑按时间或 ID 分段。

另一个被忽视的排查项是内存碎片率INFO memory中的mem_fragmentation_ratio。如果这个值超过 1.5,说明内存碎片比较严重,通常和频繁的大 key 创建/删除有关,可以考虑重启节点或调整 jemalloc 配置来整理碎片。

8.4 Redis 日志怎么看:别放过 warnings

Redis 运行日志经常被忽略,但它对定位问题很有用。重点看这几类内容:

  • WARNING Memory overcommit:需要调整系统 vm.overcommit_memory=1,否则 RDB 持久化可能失败。
  • WARNING The TCP backlog setting of 511 cannot be enforced:高并发下需要调整内核 somaxconn。
  • WARNING you have Transparent Huge Pages enabled:建议关闭 THP,避免 fork 期间触发内存大页导致的延迟波动。
  • 日志里频繁出现Loading RDB produced by version X.Y.Z说明每次启动都在加载 RDB,如果数据量很大,启动时间会很长,可以考虑改用 AOF 并开启混合持久化缩短恢复时间。

9. 高频面试题速查:不只是背八股

9.1 常考的理论问题

面试聊 Redis,高频理论题大概这些:缓存穿透怎么解决、缓存击穿怎么解决、缓存雪崩怎么解决、Redis 为什么快、RDB 和 AOF 区别、Redis 单线程为什么还这么快、分布式锁怎么实现、主从复制原理、哨兵工作原理、Cluster 槽位机制。

网上八股文很多,但我发现最容易翻车的其实是“Redis 为什么快”这道题。常见的回答是“因为单线程 + 内存 + IO 多路复用”,但单线程本身并不代表快,准确说法是:数据在内存中读写、非阻塞 IO 多路复用、单线程避免了锁竞争和上下文切换开销,配合高效的数据结构实现,整体在绝大多数场景下能达到微秒级响应。你要能解释 Select/Epoll 模型为什么能在单线程下支撑高并发,而不是生硬背模板。

9.2 容易被追问的边界问题

面试官喜欢顺着答案追问边界。

比如问缓存一致性,光说“先更新数据库再删缓存”不够,要能说出延迟双删的原理、删除失败怎么办。我一般补充一条:删除缓存失败可以通过订阅数据库 binlog,异步重试删除,这是比较完整的生产级方案。

再比如问分布式锁,光说 SET NX EX 容易被继续追问“锁过期了业务没执行完怎么办”,这就要引出 Redisson 续期,或者手动续期。答到这一层,基本能看出你有没有真实生产经验。

还有 Cluster 的坑:为什么MGET在 Cluster 里可能报错?因为多个 key 可能分散在不同槽。怎么解决?用 hash tag。这种追问如果临时编答案,很容易露馅。

9.3 关于性能压测的实操建议

面试里如果聊到性能,建议你实际跑过压测,而不是背数据。本地可以用 redis-benchmark:

redis-benchmark -t set,get -n 100000 -c 50 -q

这个命令用 50 个并发连接执行 10 万次 SET 和 GET,输出 QPS 和延迟分布。我实测单机 Redis 7 在普通配置下 SET/GET QPS 能到 10 万以上,Pipeline 批量提交更高。压测除了看 QPS,还要关注 p99 延迟,Redis 的 p99 延迟通常在零点几毫秒左右,如果 p99 异常高,大概率是触发了持久化、大数据 key 操作或网络抖动。

9.4 一套自我检查清单

最后分享一下我对 Redis 问题自测的清单,很多线上问题都是照着这个顺序查出来的:

  1. 网络层:telnet 测试端口是否通,ping 排查延迟。
  2. 连接层:INFO clients 看连接数,检查连接池配置。
  3. 命令层:SLOWLOG 查慢命令,BIGKEYS 扫描大 key。
  4. 持久化层:INFO persistence 看 RDB/AOF 是否正常,检查最近一次 save 是否有失败。
  5. 资源层:INFO memory 看内存使用和碎片率,INFO cpu 看上下文切换。
  6. 复制层:INFO replication 看主从偏移量,主从延迟过大时读请求可能读到旧数据。

我实际用过这套流程解决了不少问题,其中最典型的是一次内存暴涨排查,当时表面原因是容器重启后 Redis 从 RDB 加载数据导致内存短暂翻倍,实际上是因为 RDB 和 AOF 同时开启且 RDB 文件偏大。看完 INFO 才发现加载阶段内存开销远超预期,最后通过调整混合持久化配置和限流初始化连接解决了。

10. 几个我亲测有用的实践经验

写到最后,分享一些踩过坑之后沉淀下来的实操经验。不一定每条都适合所有项目,但多数情况下能让你的 Redis 少出问题。

第一,生产环境尽量少用KEYS命令,我见过同事在 Spring 项目里用KEYS user:*扫线上缓存,直接把 Redis 卡住几秒。取而代之用SCAN cursor MATCH pattern COUNT count分批次遍历,注意 SCAN 返回的游标可能不保证完整遍历,所以只适合统计和清理场景,不适合拿来做业务强依赖的快照。

第二,设置合理的maxmemory和淘汰策略。默认的策略是noeviction,内存满了直接拒绝写入,很多业务会莫名其妙报错。线上我一般用allkeys-lru或volatile-lru,具体看缓存是否允许淘汰。这个参数按业务容忍度来,不能照搬公司其他项目配置。

第三,每个 key 都要有命名规范。我习惯用业务名:实体名:ID[:子对象]这个格式,比如order:1001:items。这不仅是可读性问题,后面做权限控制、监控、删除缓存、问题定位都会省很多事。没有规范的 Redis 集群,基本就是一个大型垃圾场。

第四,写脚本的时候一定加过期时间。之前遇到过没设过期时间的 key 慢慢堆积,内存从 4GB 涨到 40GB。后来我写了一个扫描脚本,把所有无 TTL 且不应该是常驻数据的 key 都列出来,逐个确认清理。从此我要求所有缓存写入必须依赖 TTL。如果有些 key 需要长期保留,那就显式说明理由,不要默认留一个不超时的 key。

第五,Redis 版本升级要谨慎。官方 6.x 引入了 ACL、7.x 引入了多部分 AOF 和函数等新特性,但老客户端不一定兼容。我见过一次升级到 7.x 后,旧版本 Jedis 在部分命令上行为异常的问题,所以生产升级前至少要在测试环境完整跑一遍压测和业务回归。

这套东西从安装、数据类型、缓存治理、持久化、分布式锁到集群,基本覆盖了我这几年在项目里真正用到的 Redis 知识点。工具和版本会变,但底层的数据结构思维、并发问题意识和故障排查逻辑是通用的,把这些抓住,后面不管换什么缓存产品都能快速上手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 10:53:46

Linux进程控制完全指南:fork、exit、wait与exec实战

我一直觉得,Linux系统编程里最见功力的地方,不是你会写多复杂的网络程序,而是能不能把进程这几个基本操作玩明白。作为整个系列里第11章的内容,进程控制恰好是承前启后的那一环——前面学的文件、内存、信号,最后都要落…

作者头像 李华
网站建设 2026/10/5 10:52:24

Netty源码拆解:AbstractChannel.register的注册流程与事件传播机制

看Netty源码的时候,很多人第一个卡住的地方不是EventLoop,也不是ChannelPipeline,反而是AbstractChannel里这个看似人畜无害的register方法。它既不像bind那样直观,又不像read那样频繁,但整个Netty的异步模型、线程模型…

作者头像 李华
网站建设 2026/10/5 10:50:29

鸵鸟目标检测数据集:VOC+YOLO双格式419张实拍图

简介:本资源是一份面向计算机视觉初学者与目标检测实践者的鸵鸟图像数据集,适用于YOLO、Faster R-CNN等主流检测模型的训练与验证。数据集共419张高质量JPG图像(1–500KB),全部标注单一类别“ostrich”,并同…

作者头像 李华
网站建设 2026/10/5 10:48:44

ZooKeeper实战指南:分布式协调、锁与Hadoop高可用核心机制解析

做后端这几年,ZooKeeper(业内一般直接叫 ZK)这个名字几乎绕不开。一提到分布式协调、Hadoop 集群、Kafka 的 broker 管理、Dubbo 的服务注册,背后多少都有它的影子。但说实话,很多人对 ZK 的印象就停留在“听说过、好像…

作者头像 李华
网站建设 2026/10/5 10:48:38

科技人物|她们凭什么改写了这场会议的议程?

一场顶会圆桌上,最年轻的那位女性研究者被排在最后一个提问。 她没有顺着趋势发言,而是追问了一句:你们的评测集里,有多少样本来自非英语用户? 会场静了几秒。后来,这个问题长成了一个研究方向。 三条并不相…

作者头像 李华
网站建设 2026/10/5 10:48:19

SpringBoot+Vue+MySQL公寓报修管理系统:从设计到部署全流程解析

毕设做公寓报修管理系统,SpringBootVueMySQL这套组合怎么把论文、代码、部署一次打通,我踩过的坑和梳理好的思路全写在这里。这个标题乍一看就是典型的“系统三件套”,但真正动手做的时候,你会发现难点根本不在写代码,…

作者头像 李华