Redis 的搜索热度这几年一直没掉过,不管是刚入行的新人还是干了三五年的老手,面试题里几乎必有一道“说说 Redis 能做什么”。但说实话,大部分人停留在“Redis 是缓存”这个层面,再往深一点就说不清楚了。我最早接触 Redis 也差不多是这个状态,直到后来在几个项目里真刀真枪地用它扛过大流量、做过分布式锁、排过缓存穿透的问题,才算把它的能力边界摸得比较清楚。
这篇文章就围绕“Redis 是什么”和“它能做什么”两条线展开,不讲太多底层源码,重点把这些年实际项目里验证过的东西拿出来说,顺便把那些容易踩坑的地方也一并讲掉。如果你正在学 Redis,或者准备面试,或者刚要在自己的系统里引入它,这篇应该能帮你少走不少弯路。
1. 先搞清楚 Redis 到底是个什么东西
Redis 全称是 Remote Dictionary Server,翻译过来就是“远程字典服务”。这个命名其实非常准确,它本质上就是一个跑在远程的、巨大的字典表——你往里存一个 key,它能给你返回对应的 value。但同样是存数据,它和 MySQL 那类关系型数据库走的是完全不同的路。
1.1 为什么说 Redis 是内存数据库
最核心的区别在于数据放在哪。MySQL 的数据默认落在磁盘上,每一次查询都要经过磁盘 I/O,即使有缓冲池,热数据也难免有一层磁盘交互的成本。而 Redis 默认把所有数据都放在内存里,内存的读写速度比磁盘快几个数量级,所以 Redis 的单线程模型跑下来,读写性能可以轻松到十万级 QPS,这是它在缓存领域无可替代的根本原因。
这里有个关键点要说明:Redis 虽然默认是内存存储,但它提供了持久化机制,可以把内存里的数据定期或者实时地备份到磁盘上。很多人以为 Redis 重启数据就全丢了,这其实是误解。它有两种持久化方式:RDB 是定时对整个数据快照落地,AOF 是追加记录每一条写命令。重启时 Redis 会从这两种文件里恢复数据。当然,因为数据主体在内存,Redis 能存的数据总量受物理内存限制,这决定了它不适合存大规模全量数据。
另外一个常被问到的点,是“为什么 Redis 那么快,还要刻意说它是单线程的”。这背后其实是一整套设计哲学。Redis 的网络 I/O 和数据操作都在同一个主线程里完成,省去了多线程环境下的锁竞争和上下文切换开销。而且它用了基于 epoll 的 I/O 多路复用机制,一个线程就能同时处理成千上万个客户端连接。你想象一下:一个专门处理取号窗口的柜员,不用来回切换座位,也从不排队等锁,每个人过来递一张号牌,他处理完就接下一个,效率自然高。
1.2 Redis 和其他存储工具的边界在哪里
这部分我是面试官当多了之后,才意识到很多人的困惑不是不懂 Redis,而是分不清 Redis 和 Memcached、Redis 和 MySQL、Redis 和消息队列之间的边界。
和 Memcached 比,Redis 不只是能存字符串,还有 list、hash、set、zset 这些高级数据结构;Memcached 挂了数据就没了,Redis 至少能通过 AOF/RDB 恢复;从功能面上说 Redis 基本可以完全替换 Memcached。和 MySQL 比,Redis 的强项是快,但弱点是数据量受限、查询能力简单、没有事务隔离级别这类的完整数据库语义。所以通常的架构是“MySQL 负责存底账,Redis 负责加快读取”。和消息队列比,Redis 的 list 结构可以做简单的 streaming 场景,但如果你的业务需要消息确认、重试、堆积海量消息,还是上真正的 MQ 更稳妥。
我见过不少项目组一开始图省事,把 Redis 当万能药使,结果又要存大数据又要做复杂查询,最后 Redis 内存暴涨、key 过期策略失控,性能反而拖垮。工具选型不是看谁名气大,而是看清谁适合解决什么问题。
2. 五种基础数据结构,每一种都是为场景而生的
Redis 之所以能用在那么多场景,核心就是它的数据结构设计得非常“懂业务”。它不只是存字符串,而是给了你五种基础类型,每一种都对应了一类常见需求。理解了这五个类型,你对“Redis 能做什么”的理解会立刻上一个台阶。
2.1 String:不只是存文本,还能做原子计数
String 是 Redis 最基础的类型,value 最大支持 512MB,既可以存普通字符串,也可以存数字。对一个数字类型的 value 执行 INCR、DECR、INCRBY 时,Redis 保证这个操作的原子性,也就是并发环境下不会出现加一加丢的情况。
这个特性带来的典型场景是计数器和限流。比如一个视频的播放量,如果每次播放都去更新数据库,数据库压力太大;放到 Redis 里用 INCR 累加,再定期把结果刷回数据库。更常见的是库存扣减,用户下单时先 DECR Redis 里的库存 key,如果结果小于 0 说明超卖了,就直接返回失败,这一套流程比用数据库行锁高效得多。
还有一点要注意:String 常常用来存 JSON 序列化后的对象,这里就涉及热搜词里那个“redis序列化”的常见坑。你用 Jedis 或 Spring Data Redis 存对象时,如果序列化器没配好,落进 Redis 里的可能是一堆带特殊前缀的二进制字符,肉眼根本看不懂。这不是 Redis 的问题,而是客户端序列化策略的问题,后面讲到 Spring Boot 集成时再展开说。
2.2 Hash:一个对象的所有字段,天然适合存这里
Hash 类型相当于一个 field-value 的小型 map,最典型的场景就是存一个对象。比如一个用户的昵称、头像、积分,以用户 ID 为 key,用户属性就是一个个 field,这样更新单个字段非常灵活,不用像 String 把整个 JSON 拿出来改完再放回去。
我做电商后台的时候,购物车就是用它做的。每个用户有一个 cart:{userId} 的 key,商品 ID 是 field,商品数量是 value。用户往购物车里加一件商品,只需执行 HINCRBY cart:10001 itemA 1,既省空间,又天然支持字段级并发修改,比存整个 List 灵活太多。Hash 还有一个开销上的优势:如果 hash 里的字段很少,Redis 会用 ziplist 进行内存压缩,小对象的内存占用比想象中低不少。
2.3 List:双向链表,能当消息队列雏形用
List 是一个双向链表,LPUSH 从左边插入,RPUSH 从右边插入,LPOP 从左边弹出,所以既可以当栈用,也可以当队列用,还能通过 LPUSH + BRPOP 实现阻塞队列。BRPOP 是 List 做轻量级消息队列的关键——它会让读取方阻塞等待,直到列表里有新数据才返回,客户端就不需要写死轮询逻辑。
我自己早期做过一个简单的异步通知程序,就是用 List 做的:请求进来后 LPUSH 到一个队列 key,然后一组 worker 线程 BRPOP 取任务去发短信、发邮件。这个方案比直接调第三方的同步接口稳定不少,请求响应时间从几百毫秒降到了几十毫秒。
但这里要给你提个醒:List 当消息队列用,只能算“能用”,不算是“好用”。如果消费者拿到消息之后处理失败了,这条消息就丢了你需要自己维护重试;如果系统重启,阻塞中的消息也很容易丢。Redis 在 5.0 之后推出了 Stream 类型,支持消费者组、消息确认、Pending 列表,如果你真的想在 Redis 上做消息队列,建议直接研究 Stream,而不是一上来就用 List。前面说的“先用 List 快速解决有没有的问题,等场景复杂再上 MQ”,是业务演进中比较务实的做法。
2.4 Set:无序集合,天生适合做去重和关系运算
Set 的特点是不允许重复元素,还支持 SINTER、SUNION、SDIFF 这几个集合运算,分别对应交集、并集、差集。这些运算让 Set 成为处理“人与人”“用户与内容”这类关系的利器。
最经典的场景是抽奖系统。每个用户参与抽奖时,SADD 进一个活动 key,抽奖时用 SRANDMEMBER 或 SPOP 随机取人,因为 Set 保证了每个用户只出现一次,天然不用担心一个人中奖多次。再比如社交场景里,你要算“我关注的人里面,有哪些也关注了你”,把“我关注的集合”和“你的粉丝集合”做一次 SINTER 交集就能立刻得到结果,比在数据库做 JOIN 快太多。
我做过一个内容平台的“共同好友”功能,用户和用户之间关注关系上百万条,Redis 里每个用户维护一个 Set,两个用户做 SINTER 耗时基本是亚毫秒级,体验和 SQL 里的嵌套查询完全不在一个量级。
2.5 ZSet:带分数的有序集合,排行榜全靠它
ZSet 在 Set 的基础上给每个元素关联了一个 score,Redis 会按 score 从小到大排序。存储结构里包含跳表和哈希表,所以既能快速查到某个元素的 score,也能高效地按分数范围取一批元素。跳表你可以理解成一个多层级索引的链表,每次查找可以跨过多级直接到达目标位置附近,所以即使在百万级数据量上做范围查询,性能依然很稳定。
排行榜就是它的招牌应用。游戏日活榜、文章热榜、积分榜,逻辑都是同一套:用户行为发生时,ZINCRBY 给对应成员的 score 加一个数值,要展示榜单时,ZREVRANGE 从大到小取前 N 名即可。我做直播平台那会儿,礼物榜就是 ZSet 维护的,每收一个礼物调用一次 ZINCRBY,整个榜单毫秒级刷新,运营后台随时看实时排名,数据库里一份流水都没有多写一次。
还有一个容易被忽略的用法是延时队列。score 存任务执行的时间戳,后台轮询时用 ZRANGEBYSCORE 取 score 小于当前时间的所有任务,处理完后 ZREM 删掉。这个方案做定时任务调度非常轻量,我拿它做过订单超时未支付自动取消,稳得很。
3. 它真正能解决的业务问题,比缓存大得多
很多人对 Redis 的理解就停在“缓存”两个字上。确实,缓存是它最常见的职能,但如果你只会把它当缓存用,等于手里拿着一个功能丰富的工具箱,却只拿它当凳子坐。下面这些场景,是我自己在项目里验证过、并且认为价值最高的几个应用方向。
3.1 缓存三件套:穿透、击穿、雪崩怎么解
聊 Redis 缓存,就绕不开这三个反面典型问题。它们不是同一个东西,但经常被混在一起说,面试时也是必问的高频点。我先用最直白的方式把它们的区别讲清楚。
缓存穿透,是指查询一个根本不存在的数据。比如你拿一个不存在的用户 ID 去查,Redis 查不到,只能继续去数据库查,数据库也查不到,于是这次查询没有任何缓存可落,每次这种恶意请求都直接打在数据库上。大量这种请求并发时,数据库很容易被拖垮。解决办法有两个方向:一是对空结果也做缓存,给一个特殊空值并设置较短的过期时间;二是在查询链路前面加布隆过滤器,用很少的内存空间过滤掉那些一定不存在的 key,连 Redis 这一层都可以省掉。
缓存击穿,是指某一个热点 key 在过期的一瞬间,大量请求同时打到数据库。这个问题的关键是“某一个 key”,而不是一堆 key。解决办法是热点数据设置永不过期,或者加互斥锁,让失效后的首次查询只有一个请求去重建缓存,其他请求等锁之后直接拿新缓存。
缓存雪崩,是指大量 key 在同一时间段集中过期,导致数据库瞬间承受大量请求。解决方案也直接:给不同 key 的过期时间加随机值,不要让它们整整齐齐地在同一个时刻过期;或者用多级缓存,把一部分热数据放到本地应用缓存里挡住第一波流量。
这套“三件套”的解法是每个用 Redis 做缓存的人都必须掌握的。我见过很多事故,就是上线前根本没评估过缓存失效后数据库能不能扛住,结果半夜一个热点 key 过期,直接把核心库打挂了。
3.2 分布式锁,不是随便 SETNX 就行
在单机应用里,用 JVM 的锁或者 synchronized 就能解决并发问题。但到了多实例部署,每个实例有自己的锁空间,就得靠一个大家都能访问到的中间件来协调,Redis 就是最常用的分布式锁载体。
实现分布式锁有两条经典路线:早期直接用 SETNX key value,拿到锁就干活,干完 DEL 释放。但裸用 SETNX 有很多坑:比如线程 A 拿到锁后执行时间过长,锁自动过期了,线程 B 又拿到了锁,这时候 A 执行完把 B 的锁删了,相当于锁形同虚设。所以后来大家约定,value 必须存一个唯一标识(比如 UUID),释放锁时先 GET 比较是不是自己存的,只有是自己的才 DEL,而且比较和删除这两步要保证原子性,最稳妥的做法是用一段 Lua 脚本完成。
简单场景用单机 Redis 的 SET key value NX EX seconds 命令就好,但是在主从架构下,如果主节点挂了,锁还没来得及同步到从节点,从节点顶上之后就相当于锁丢了。要解决这个问题,得用 Redisson 的 RedLock 或者让主从复制更可靠。我个人的建议是,如果你的系统里分布式锁是核心依赖,不要自己造轮子,直接上 Redisson,它是用 Lua 脚本实现的看门狗自动续期机制,能避免“锁过期了,任务还没跑完”这类诡异问题。
3.3 热门榜单、抽奖、签到这些玩法,写起来很顺手
榜单前面已经提到了。抽奖可以用 Set,签到也可以用 Set 或者 Bitmap。Bitmap 是 String 类型的位操作能力,一个用户一年的签到记录只需要 365 个 bit,相当于 46 个字节,一万用户也才几百 KB。
具体做法是每天用 SETBIT sign:{userId} {dayOfYear} 1 标记签到,查询时用 GETBIT 看某天是否签到,统计这个月签了多少天可以用 BITCOUNT。位运算高效且省内存,这一招在“签到”“在线状态”“用户活跃统计”这类场景里表现极其出色。做带有连续签到奖励的活动时,还能把签到结果整个取出来做本地位运算,判断连续签到天数,逻辑非常清晰。
3.4 会话共享与登录状态
传统单体应用里,登录状态往往存在应用服务器的 Session 里。应用一旦多实例部署,用户请求被负载均衡分发到不同实例时,Session 就对不上了。常见解法是让负载均衡做会话粘滞,但这会牺牲集群的弹性扩展能力。
Redis 的方案是,把 Session 数据整体挪到 Redis 里,所有实例共享同一个 Redis,任何一次请求都能读到同一个 Session。Spring Session 这个项目就是干这个的,它会把原本存 HttpSession 的内容自动同步到 Redis,应用代码几乎不用改。这个方案我用了很多年,稳定性很好,多实例部署、滚动发布都不用担心踢用户下线。
3.5 限流、计数器、布隆过滤器:那些不那么显眼但很实用的小功能
限流可以直接用 INCR 加过期时间实现“固定窗口限流”。比如限制每个用户每分钟最多请求 10 次,就对这个用户建一个 key,第一次请求时 SET key 1 EX 60,之后每次 INCR,超过 10 就拒绝。固定窗口的缺点是两个窗口交界处可能出现双倍请求,但如果业务要求不极端,这是性价比最高的做法。
布隆过滤器可以通过 Redisson 的 RBloomFilter 直接使用。它的原理是用多个哈希函数把元素映射到一组二进制位,查询时如果所有位都是 1,说明“可能存在”;只要有任何一个位是 0,就一定不存在。误差率可以配置,一般设在 1% 以下完全够用。我在一个新闻推荐系统里用它做“已读去重”,上千万条数据只用了不到 100MB 内存,查询耗时在毫秒级,比在数据库里查历史记录要快太多了。
3.6 用 Redis 做自动补全组件
热搜词里有一条“使用redis构建自动补全组件”,这个点子其实很有价值。用 ZSet 可以实现一个轻量级的关键词联想器:每个词的分数可以设为热度,输入前缀时用 ZRANGEBYLEX 或 ZRANGEBYSCORE 取出候选词,再按用户输入逐字收敛。如果是中文场景,还可以先把拼音首字母也建立索引,做到输入拼音就能联想中文关键词。
这种方案适合数据量在一定范围内、不需要引入 Elasticsearch 的场景。虽然 ES 的搜索能力更全面,但运维成本高得多,如果产品诉求只是输入框下拉提示,Redis 这套方案完全可以一战。
4. 一些不该用 Redis 的错误姿势
Redis 确实强大,但它不是银弹。作为过来人,我见过太多因为乱用 Redis 造成的系统问题,下面这些“错误姿势”你在实际项目里一定要警惕。
4.1 把 Redis 当主数据库存关键业务数据
一个常见的误区是:因为 Redis 快,就直接把核心业务数据往里塞,连 MySQL 都省了。这非常危险。一是 Redis 的数据安全取决于持久化配置,如果只开了 RDB,最多可能丢失最近一次快照之后的所有更新;二是内存容量有限,业务数据一增长就要面临淘汰策略的尴尬;三是 Redis 的查询能力远不如 SQL,统计报表、多维筛选这种需求它会让你写得痛不欲生。
我的原则很明确:关键业务数据必须落数据库,Redis 只做加速层。就算需要 Redis 的高性能,也不能让 Redis 里的数据成为唯一副本。缓存和数据库之间的一致性哪怕做不到强一致,也得通过“先更新数据库、再删除缓存”或者延迟双删来尽量弱化不一致窗口。
4.2 没事就批量写入大 Value
Redis 适合存储小对象,单 key 的 value 如果动辄几 MB,会带来两个问题:一是网络传输时间拉长,单个请求延迟变大,吞吐量直线下降;二是大 key 在持久化、主从复制、扩容迁移时都会成为绊脚石。
我接手过一个项目,因为图省事,把一个业务的所有配置项拼成一个巨型 JSON 塞进一个 key 里,结果每天凌晨主从切换时,这个 key 的复制同步要花几十秒,期间从节点服务能力骤降。后来拆成几百个小的 key,这个问题立刻消失了。推进大的 value,不但要拆,还要监控。Redis 提供的 BIGKEYS 扫描命令能帮你快速发现有哪些异常大 key,建议上线后定期跑一遍。
4.3 一切都要事务,那你应该换数据库
Redis 的事务和传统数据库事务完全是两回事。Redis 的 MULTI/EXEC 只是把多条命令一次性打包执行,中间不会插入其他客户端的命令,但并没有回滚机制。如果执行过程中某条命令语法错误,其他命令照样执行;如果业务逻辑失败,需要你自己通过判断结果来补偿。
所以不要把 Redis 当成强一致事务的解决方案。需要强一致性的业务场景,就不该考虑用 Redis。Redis 的定位是牺牲一部分一致性和能力,换取极高的性能,这一点想通了,你的架构选型会清晰很多。
5. 落地一整套运行体系:安装、可视化、主从与生产部署
掌握了能做什么之后,就该考虑怎么在真实环境里让它稳定运行。这一节我把从安装排查到生产部署的完整链路串一下,这些都是运营一个 Redis 服务必须面对的问题。
5.1 本机安装与首次启动
本地开发最简单的办法是直接用 Docker 跑一个官方镜像:
docker run -d --name redis-local -p 6379:6379 redis:7.2如果不想用 Docker,官方源码编译也很快:
wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 make src/redis-server --daemonize yes编译方式有个后台坑:老版本如果没加 --daemonize yes 参数,启动后会一直占着前台终端,新人很容易误以为启动卡住了。Redis 默认没有密码保护,本机用没问题,但如果你的环境允许外部访问,一定记得在配置文件里设 requirepass,并且把 bind 改成内网 IP 而不是 0.0.0.0。
检查 Redis 是否正常,用 redis-cli ping,返回 PONG 就说明通了。
5.2 可视化客户端怎么选
命令行用顺了之后,日常排查和分析数据结构还是可视化工具更直观。目前主流的有几款,各家的侧重点不太一样:
- Redis Desktop Manager(RDM):老牌工具。后来在原版基础上派生出了 Another Redis Desktop Manager,界面更现代,更新频率也高,对 Redis 6/7 的新命令支持得更好。
- Redis Insight:Redis 官方出的,功能最全,除了常规操作,还有内存分析、慢日志查看、命令行工具,强烈建议至少装一个。
我个人现在的习惯是日常开发用 Another Redis Desktop Manager,排查内存和性能问题时用官方 Redis Insight。可视化工具有时候连不上 Redis,十有八九是 Redis 没设置 bind 为可访问地址,或者防火墙没放行 6379 端口,优先从这两条线索去查。
5.3 主从复制与哨兵模式
Redis 读写分离和故障转移,依靠主从复制和哨兵机制实现。配置主从非常简单,在从节点的配置文件里加一行:
replicaof 192.168.1.10 6379然后在从节点启动 Redis,它就会从主节点全量同步数据,之后持续接收增量命令。全量同步期间主节点仍然可以服务读请求,所以做主从不需要停写。
主从解决了数据冗余和读扩展,但自动故障转移要靠哨兵。哨兵会持续监控主节点,如果主节点失联,会在从节点中推选出一个提升为新的主节点,并更新配置指向新的主。底层原理是它用 Raft 协议来选主,大部分人选型时只需要记住“哨兵保证的是高可用,不是强一致”就够了。
如果要在 Docker 里组建主从和哨兵集群,用 docker compose 编排是当前最常见的生产方式。举个例子,下面这个 compose 片段构建了一主两从三哨兵的拓扑:
services: redis-master: image: redis:7.2 command: redis-server --appendonly yes redis-replica-1: image: redis:7.2 command: redis-server --slaveof redis-master 6379 depends_on: - redis-master redis-replica-2: image: redis:7.2 command: redis-server --slaveof redis-master 6379 depends_on: - redis-master sentinel-1: image: redis:7.2 command: redis-sentinel /etc/redis/sentinel.conf depends_on: - redis-master生产环境用 docker compose 部署 Redis 时,要注意数据持久化和配置挂载。容器里的数据默认在容器可写层,容器一删数据就没了,必须绑定 volume,而且建议开启 appendonly,至少让数据损失窗口降到秒级。
5.4 生产环境部署的几个必查项
上线前我会照着下面这张清单过一遍,缺哪一项都别往后走:
| 检查项 | 推荐配置 | 原因 |
|---|---|---|
| 持久化 | 同时开启 RDB + AOF | 兼顾恢复速度和数据安全性 |
| 内存上限 | maxmemory 设置为物理内存的 60%-70% | 防止 Redis 吃光内存导致系统 OOM |
| 淘汰策略 | 常用 allkeys-lru 或 volatile-lru | 内存满时保证新请求不被无限阻塞 |
| 慢日志 | slowlog-log-slower-than 10000 | 发现大 key 或复杂操作 |
| 安全 | requirepass 强密码、bind 内网地址 | 防止 redis 被劫持利用 |
| 监控 | 接入 prometheus redis_exporter | 内存、连接数、命中率都可视 |
内存参数和淘汰策略是最容易被忽略的。很多人在生产环境直接跑默认配置,Redis 默认 maxmemory 是 0,也就是不限制,一旦业务量上来,内存被撑爆,操作系统就会启用 swap 甚至触发 OOM killer。建议提前预估内存,比如你打算放 20GB 数据,就给 Redis 设置 maxmemory 16GB 再加一个 LRU 淘汰策略,这样它总有办法腾出空间而不是崩掉。
5.5 Spring Boot 集成中的序列化与连接匹配问题
Spring Boot 是 Java 生态最常用的框架,集成 Redis 时有两个高频坑值得单独讲一下。
第一是序列化策略。Spring Data Redis 默认使用 JdkSerializationRedisSerializer,往 Redis 里写入的 key 会带上二进制类型前缀,用可视化工具看是一堆乱码;value 也是一大段序列化字节,不直观,还占空间。一般项目里会自定义配置:key 用 StringRedisSerializer,value 用 GenericJackson2JsonRedisSerializer。这样存进去的 key 可读,value 是 JSON 格式,肉眼可见。
第二是连接地址问题。热搜词里有一条“springboot2.1 redis 连接ipv6地址”,这是 Spring Boot 2.1 时代一个让人头疼的问题。当 redis 配置的 host 是域名而不是 IP 时,域名如果解析出 IPv6 地址,Java 网络库可能会优先尝试 IPv6 连接,而 Redis 默认往往只监听了 IPv4 的 6379,结果就是客户端一直报连接超时。解决办法通常是在 application.yml 的 redis url 里显式指定 IPv4 地址,或者给 JVM 加 -Djava.net.preferIPv4Stack=true,让网络库强制走 IPv4。到了 Spring Boot 3.x,相关处理逻辑已经变了,但对老项目,这个坑时不时还会冒出来。
5.6 Redis 日志分析:从启动日志到慢日志
排查 Redis 问题,第一手信息就是日志。Redis 日志文件默认打印在 stdout 或者 logfile 指定的文件里,启动时如果出现警告,比如 overcommit_memory 被设置成 0,系统在后台保存数据时可能会失败,你需要修改内核参数 vm.overcommit_memory=1 来确保后台子进程能正常申请内存。
除了启动日志,慢日志也很重要。执行以下命令查看执行时间超过阈值的命令:
SLOWLOG GET 10这个命令能列出最近 10 条慢命令。面对一个突发的延迟告警,我通常会先看慢日志里是不是有大 KEY 的 KEYS 命令或者 RANDOMKEY 之类的高耗时操作;如果没有,再看网络层有没有大流量传输;最后看是不是触发了持久化导致的 fork 阻塞。这个排查路径基本能覆盖绝大多数 Redis 性能问题。
5.7 这套体系下的日常演进策略
从一个单独的 Redis 服务,到主从复制,再到哨兵高可用,再到 Redis Cluster 分片集群,是一个循序渐进的过程。我见过很多团队一上来就搭五节点三主三从的 Cluster,其实业务量根本用不到,运维复杂度却是指数的。
如果一个 Redis 实例能扛住你的 QPS,数据量也没超过单机内存,那先保住主从 + 哨兵就足够了。等流量和容量都逼近上限,再平滑迁移到 Cluster。Redis Cluster 的数据分片是通过 key 的 CRC16 值对 16384 个槽位取模来实现的,不同的 key 落在不同的槽位,所以 Cluster 模式下存在多 key 操作的槽位限制,跨槽位的复合操作要么用 Hash Tag 把相关 key 聚到同一个槽,要么在业务层拆开处理。这个约束比单机模式严格得多,也是很多团队迁移时才发现的问题。
6. 面试里 Redis 的高频考点,本质是考你对场景的理解
Redis 是面试高频区,但考察的其实不是你能不能背出命令,而是看你能不能想明白每个技术在真实系统里为什么这么设计。从我和别人对战的面试经验看,“背答案”和“真理解”差距很大,下面这几点是你需要真正想通的。
第一个高频考点是“Redis 为什么快”。标准答案要覆盖四条主线:内存存储、I/O 多路复用、单线程避免加锁、高效的数据结构实现。如果能再提一下 Redis 6.0 开始引入多线程处理网络读写、但命令执行仍然是单线程这一细节,面试官会认为你是真的读过资料而不是背网文。
第二个高频考点是缓存场景的三大问题,穿透、击穿、雪崩,以及对应的解决方案。这个知识点在文章前面已经详细讲了,面试时更重要的是把三者区分开讲明白,不要混为一谈。用一两个自己踩过的案例来辅助说明,非常加分。
第三个高频考点是持久化机制。RDB 和 AOF 的区别要能讲清:RDB 是二进制快照、恢复快、数据丢失窗口大;AOF 是命令追加、可配置每次写都刷盘、安全但文件大。Redis 4.0 开始默认是混合持久化,用 RDB 作为基础快照,再用 AOF 记录增量命令,兼顾恢复速度和数据安全。这个演进逻辑理解了,比硬记配置项强得多。
第四个高频考点是分布式锁和缓存一致性问题。分布式锁前面已经讲了很多,这里补充缓存一致性:比较稳妥的方案是“Cache Aside Pattern”,也就是读请求先读缓存,读不到就读数据库并回填缓存;写请求先更新数据库,再删除缓存。因为删除缓存比更新缓存更安全——更新缓存可能因为并发旧数据把新数据覆盖,删除只是一个 miss,下次读请求会重新从库里拉最新的。
这些面试题,与其说是考 Redis,不如说是在考你有没有真正思考过“为什么”。系统设计中每个选择都是一种权衡,你能把权衡背后的逻辑讲清楚,面试这关基本就稳了。
7. 一些必须要强调的实战心得
文章到这里,核心的东西差不多讲完了。最后把这些年在 Redis 项目里踩过的坑、摸索出来的经验做个总结,这些并不是文档里会写的,希望对你有用。
第一个心得是:先想清楚数据结构再动手。写代码前花十分钟想清楚“这个场景到底该用 String 还是 Hash 还是 ZSet”,比你写完后反复重构高效得多。判据很简单:是单值、是多字段对象、是有序集合、是无需去重集合,还是带分数的排序集合,按这个思路选,基本错不了。
第二个心得是:所有缓存一定要有降级方案。Redis 再稳也有不可用的时候,你的服务必须设计出一个“Redis 挂了业务还能转”的兜底路径,哪怕降级到直接查数据库、哪怕响应变慢,都比整个接口 5xx 强。我之前负责过一个流量很大的读接口,Redis 集群抖动时,靠本地缓存和数据库兜底,硬是没有造成一次线上故障。
第三个心得是:监控和报警要比业务功能先上。Redis 内存、命中率、连接数、慢日志,这些指标一旦异常就要被感知到。发现内存缓慢增长是普通告警,等到 Redis OOM 之后再查,就是事故复盘了。排查 Redis 问题常用的命令组合,我也整理一份在这里:
| 目的 | 命令 |
|---|---|
| 查看整体状态 | INFO stats、INFO memory |
| 查看所有大 key | redis-cli --bigkeys |
| 查看执行缓慢的命令 | SLOWLOG GET 20 |
| 查看当前连接数 | CLIENT LIST |
| 查看 key 的过期时间 | TTL key |
最后一个心得,也是我最想强调的:学 Redis,不要停留在背命令和刷面试题上。自己拿一台服务器,搭一个主从、写几个不同数据结构的 demo,把缓存雪崩的模拟场景真实压一遍,这些操作比你看十篇文档都有效。我带的团队里,凡是能把 Redis 用得游刃有余的人,无一例外都是动手折腾过的。
Redis 这个工具真正的价值,不在于它本身有多厉害,而在于你能不能判断在什么场景下使用它的哪种特性,去解决实际业务里那个等待已久的问题。希望这篇简析能帮你把这一整条链路打通,也欢迎在评论区分享你自己的 Redis 使用故事。