1. 你有实际使用过 Redis 做什么应用么?#
分析
有些同学回答没做过,这种肯定不行。只要了解相关应用场景,就可以说有实验过。
一般而言,没经验的同学说 2 个场景会比较好,太多容易被质疑,太少觉得单薄。
如果实际情况应用多于 2 个,按实际说就行。
回答
有在实习中/工作中/实验室中涉及过 Redis 缓存场景、Redis 分布式锁场景。
2. Redis 缓存是如何应用的?#
分析
一般都是旁路缓存,这里面试官不特别问的话,没必要背一堆缓存模式,只说旁路缓存就行。
回答
我们是用作旁路缓存,在我们订单项目中,是先查询 Redis,没有查 MySQL 并将数据加载到 Redis。
3. Redis 经常作为 MySQL 的缓存来使用,为什么?#
分析
有几个要点:
- MySQL 是磁盘操作,性能不高;
- Redis 内存操作,性能非常高;
- 缓存本质是高速存储临时存储低速存储的数据。
回答
MySQL 是关系型数据库系统,操作数据需要访问磁盘,性能不高,通常就几千 QPS,而 Redis 基于内存并做了很多优化,性能高达 10W QPS,一些热点数据就可以缓存到 Redis,查询时先查 Redis,不存在才查 MySQL,这种 Redis+MySQL 结合的方式可以有效提高系统 QPS。
4. Redis 和 Memcached 有哪些共同点和不同点?#
分析
两者都是缓存中常用组件,所以容易拿来比较。
共同点:
- 内存存储:两者都使用内存作为主要存储方式,访问速度极快,适用于缓存场景;
- 键值存储(Key-Value Store):两者都以键值对(key-value)形式存储数据,并支持 TTL(过期时间);
- 高性能:由于存储在内存中,都能在毫秒级响应数据请求,适用于高并发环境;
- 分布式扩展:两者都支持分布式部署(但 Redis 需要依赖外部 Cluster 方案,而 Memcached 原生支持分布式);
- 应用场景:都用于缓存热点数据、降低数据库压力、提升网站响应速度。
不同点:
| 对比项 | Redis | Memcached |
|---|---|---|
| 数据类型 | 支持多种数据结构(String, List, Set, Hash, ZSet) | 仅支持字符串(String) |
| 持久化 | 支持 RDB 和 AOF 持久化,数据可恢复 | 仅存储在内存,服务器重启后数据丢失 |
| 数据淘汰策略 | 多种 LRU/LFU 及定期清理策略 | LRU 淘汰策略(数据满时删除最久未使用的键) |
| 分布式支持 | 通过 Redis Cluster 实现分布式 | 原生支持分布式(客户端一致性哈希) |
| 事务支持 | 支持 MULTI/EXEC 事务 | 不支持事务 |
| 原子操作 | 多个命令支持原子性(如 INCR、LPUSH) | 仅 INCR/DECR 等有限支持原子操作 |
| Lua 脚本 | 支持 EVAL 执行复杂操作 | 不支持 |
| 发布/订阅(Pub/Sub) | 支持(可用于实时消息推送) | 不支持 |
| Stream(流式数据) | 支持(XADD、XREAD) | 不支持 |
| 内存利用率 | 相对较高,占用额外的元数据 | 更紧凑,适合超大规模缓存 |
| 并发模型 | 单线程(基于事件驱动,内部优化) | 多线程(并发性能较好) |
| 应用场景 | 缓存、队列、排行榜、分布式锁、持久化存储 | 主要用于缓存(高速存取数据) |
回答
Memcached 适合纯字符串缓存场景,数据结构只支持字符串,超高并发下性能较优,适用于 CDN、页面缓存、数据库查询结果缓存等。
Redis 适合更广泛的场景,支持持久化、多数据结构、事务、分布式锁等,适用于排行榜、消息队列、实时统计等。
理论上来说如果只是简单的高性能缓存,选 Memcached;如果需要更多功能,选 Redis,但是后端实际生产过程中,现在基本都是 Redis,因为场景全面、实践经验完善性能也足够高了。
5. Redis 做旁路缓存,如果 MySQL 更新了,此时何去何从?#
分析
经典的缓存一致性问题,从八股上来就是:
Cache Aside
- 原理:先从缓存中读取数据,如果没有就再去数据库里面读数据,然后把数据放回缓存中,如果缓存中可以找到数据就直接返回数据;更新数据的时候先把数据持久化到数据库,然后再让缓存失效;
- 问题:假如有两个操作一个更新一个查询,第一个操作先更新数据库,还没来得及删除缓存,查询操作可能拿到的就是旧的数据;更新操作马上让缓存失效了,所以后续的查询可以保证数据的一致性;还有的问题就是有一个是读操作没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,也会造成脏数据;
- 可行性:出现上述问题的概率其实非常低,需要同时达成读缓存时缓存失效并且有并发写的操作。数据库读写要比缓存慢得多,所以读操作在写操作之前进入数据库,并且在写操作之后更新,概率比较低。
Read/Write Through
- 原理:Read/Write Through 原理是把更新数据库(Repository)的操作由缓存代理,应用认为后端是一个单一的存储,而存储自己维护自己的缓存;
- Read Through:就是在查询操作中更新缓存,也就是说,当缓存失效的时候,Cache Aside 策略是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载,从而对调用方是透明的;
- Write Through:当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库(这是一个同步操作)。
Write Behind
- 原理:在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步地批量更新数据库。这个设计的好处就是让数据的 I/O 操作非常快,带来的问题是,数据不是强一致性的,而且可能会丢。
但是这里需要结合实际经验回答,不能回答得太八股,要展现自己的思考和能力。
回答
我在项目中使用过期时间来兜底,并且在更新 DB 后删除缓存来提升一致性的方式。另外,在做方案设计时候我还考虑过订阅 binlog 的方式,但这种方式额外引入了消息队列和消费服务,成本太高而收益不足,所以还是选择了前者。
我也调研过业界一些团队,包括腾讯云 xx 团队,字节跳动飞书团队,在大部分场景也是相同的选择,甚至一部分连删除都不用,本身对缓存一定程度不一致的容忍还是有的。
6. 如何保证删除缓存操作一定能成功?#
分析
缓存删除是一次 Redis 操作,可能因为网络波动等原因执行失败,提高删除成功率有 2 种思路,一个是失败就扔入重试队列,但是扔入队列这一步也可能失败,所以只能说是提高,第二种就是绕过这个问题,缓存删除一般是为了数据同步,可以使用订阅 MySQL BinLog 的方式来同步数据。
回答
可以引入消息队列,删除缓存的操作由消费者来做,删除失败的话重新去消息队列拉取相应的操作,超过一定次数没有删除成功就像业务层报错。
如果是 MySQL、Redis 缓存同步场景,为了保证成功率,可以用一个消费服务订阅 MySQL binlog 日志,拿到具体要操作的数据,然后再向 Redis 执行缓存同步操作。
7. 业务缓存一致性要求高怎么办?#
分析
既然用了缓存,就始终存在不一致性的时间,只能说尽可能减少这个时间,不可能完全一致,如果需要完全一致就不应该用缓存。
回答
延迟双删是提高一致性的方案,先删除缓存,然后更新数据库,等待一段时间再删除缓存。保证第一个操作再睡眠之后,第二个操作完成更新缓存操作。但是具体睡眠多久其实是个玄学,很难评估出来,这个方案也只是尽可能保证一致性而已,依然也会出现缓存不一致的现象。
8. 如何避免缓存失效?#
分析
缓存正常来说都是有过期时间的,过期时间到了,这时候缓存就会被删除,对于 MySQL(或其它数据源)而言也就是缓存失效了。
回答
首先业务发现缓存失效,是会去读 MySQL 数据重新加载进去的,但是为了尽可能避免缓存失效,我们可以由后台线程频繁地检测缓存是否有效,检测到即将失效了马上从数据库读取数据,并更新到缓存。
另外,在业务刚上线的时候,最好提前把数据缓存起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热。
9. Redis 做秒杀场景可以吗?讲讲思路#
分析
Redis 在秒杀场景主要的应用方式是两个。
一个是把 Redis 当消息队列用,用于削峰,一个是用 Redis 记录库存,进行库存的加减。
回答
Redis 可以用来记录库存,利用 Redis 的高性能进行库存的扣减,一个 Redis 处理 6W 的请求问题不大,100W/s 流量就 20 台 Redis 来支撑,当然,每个节点都要做主从容灾。
另一个方式就是把 Redis 作为轻量级消息队列,来接受请求,但是不如 kafka 这种可靠。
10. Redis 管道有什么用?#
分析
Redis 管道(Pipeline)是一种优化客户端与 Redis 服务器之间通信的机制,主要用于减少网络往返时间(RTT, Round-Trip Time),从而提升性能。
管道的工作原理:管道技术本质上是客户端提供的功能,而非 Redis 服务器端的功能。
- 客户端:
- 客户端将多个命令缓存到本地,而不是立即发送到服务器;
- 当缓存达到一定数量或显式调用 EXEC 时,客户端一次性将所有命令发送到服务器。
- 服务器:
- 服务器按照接收到的顺序依次执行所有命令;
- 执行完成后,服务器将所有结果一次性返回给客户端。
- 结果返回:
- 客户端接收到所有命令的执行结果,并按顺序处理。
管道的使用场景:
- 批量写入:需要一次性写入大量数据时(如初始化缓存、批量插入数据),使用管道可以显著提高性能。示例:批量设置多个键值对;
- 批量读取:需要一次性读取多个键的值时,使用管道可以减少网络延迟。示例:批量获取多个用户的信息;
- 高并发场景:在高并发场景下,使用管道可以减少客户端与服务器之间的通信次数,降低系统负载;
- 事务优化:在事务(MULTI/EXEC)中,使用管道可以避免多次网络往返。
回答
管道技术是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能。
Pipeline 的本质,是将请求在客户端打包,然后一次发送给服务端,服务端处理完成之后,会将结果存起来,等 Pipeline 中所有命令都完成了,再一起回包,这样可以节约很多网络交互的时间。
但使用管道技术也要注意避免发送的命令过大,或管道内的数据太多而导致的网络阻塞。
11. 什么是热 key?#
分析
讲清楚热 key 的定义和常见场景。
回答
热 Key,也称为热点 Key 或热门 Key,就是在 Redis 中,访问频率极高、请求量特别大的某些特定 Key。由于这些 Key 的访问量远远高于其他 Key,就可能会导致以下问题:
- 资源集中消耗:大量请求集中在少数几个 Key 上,可能导致某个节点负载过高,影响系统的整体性能;
- 单点瓶颈:在分布式缓存系统中,如果热 Key 集中在某一个节点上,可能会导致该节点成为性能瓶颈,甚至崩溃;
- 网络带宽压力:频繁访问热 Key 会占用较多的网络带宽,可能引发网络拥塞。
常见场景:
- 电商促销活动:例如秒杀商品的库存信息可能成为热 Key;
- 社交平台:例如某个爆款帖子的点赞数或评论数;
- 游戏领域:例如排行榜 Top1 玩家的分数。
12. 热 key 问题如何发现和解决?#
分析
热 key 的发现需结合 Redis 本身的命令操作和业务统计完成,解决则需通过分散压力和多级缓存策略。
回答
发现热 key 一般有以下几种方案:
- 使用 redis-cli 的 hotkeys 参数可以统计出热 Key 信息;
- 通过 Redis 的 MONITOR 命令找出热 Key;
- 通过业务层定位热 Key,在业务层增加相应的代码,埋点统计高频访问的 key。
而对于热 key 的解决核心是降低单点压力:
- 分散请求:
- 负载均衡:将热 key 加上前缀或者后缀,把热 key 的数量从 1 个变成实例个数,利用分片特性将这 n 个 key 分散在不同节点上,这样就可以在访问的时候,采用客户端负载均衡的方式,随机选择一个 key 进行访问,将访问压力分散到不同的实例中。这个方案有个明显的缺点,就是缓存的维护成本大:假如有 n 为 100,则更新或者删除 key 的时候也需要操作 100 个 key;
- 读写分离:通过主从复制的方式,增加 slave 节点来实现读请求的负载均衡;
- 集群扩容:增加 Redis 实例,利用分片机制分摊压力。
- 多级缓存:
- 将热 key 缓存到本地,构成多级缓存存储结构。
13. 什么是大 key?#
分析
讲清楚大 key 的定义和常见场景。
回答
大 Key 是指在 Redis 中,存储了大量数据的 Key。具体来说,大 Key 通常包含非常大的 Value 值,例如一个巨大的字符串、列表、哈希表或集合等。这些 Key 占用的内存资源较多,可能会对系统的性能和稳定性造成影响。
而且大 Key 也会导致这些问题:
- 内存占用过高:大 Key 会占用大量的内存空间,可能导致缓存系统内存不足,进而引发性能问题或 OOM(Out of Memory);
- 删除或操作耗时:当删除大 Key 或对其进行操作(如遍历、更新)时,可能会阻塞主线程,导致 Redis 响应变慢甚至无响应;
- 网络传输压力:如果需要将大 Key 的数据从缓存中读取到应用层,可能会占用较多的网络带宽,增加延迟。
大 Key 的常见场景:
- 粉丝列表:ZSet 存储千万级用户 ID;
- 用户行为记录:将某个用户的长时间行为记录存储在一个 Key 中;
- 缓存大对象:比如直接用 String 来存储图片/视频元数据。
14. 大 key 问题怎么排查和解决?#
分析
考察面试者是否有使用过一些工具来排查大 Key 问题,以及是否知道场景的解决手段。
回答
排查大 key 问题常见的手段有:
- 使用 redis-cli 的参数:
bigkeys:统计大 Key 信息,集合或列表类型返回元素个数;memkeys:统计大 Key 信息,返回所有数据类型所占内存大小。
- 第三方工具:
- 通过开源项目 redis-rdb-tools 分析 RDB 快照文件进行灵活查询,按照业务需求分析定制化地找出大 key;
- RedisInsight(官方工具):提供图形化大 Key 分析。
- 慢查询日志分析:
- 大 Key 可能触发慢查询。
解决方法:
- 数据拆分:
- 将大 key 拆分为多个小 key,如将一个大 List 拆分为多个小 List;
- 使用 Hash 结构存储数据,将相关字段分开存储。
- 定期清理:
- 设置过期时间,自动删除不再需要的数据;
- 定期运行脚本清理历史数据。
- 优化存储策略:
- 使用压缩算法减少数据大小;
- 将大文件或数据存储在文件系统或对象存储中,Redis 仅存储引用或元数据,比如图片存储到 OSS(对象存储服务)里面,Redis 里面只存对应的访问 URL。
15. Redis 如何处理大 key?#
分析
一般而言 String 类型的值大于 10 KB;Hash、List、Set、ZSet 类型的元素的个数超过 5000 个。
影响:
- 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时。客户端认为很久没有响应;
- 引发网络阻塞:每次获取大 key 产生的网络流量较大;
- 阻塞工作线程:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没法处理后续的命令,一般要用 unlink 去异步删除;
- 内存分布不均:集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较小。
处理:
- 当 value 是 string 时,比较难拆分,则使用序列化、压缩算法将 key 的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗;
- 当 value 是 string,压缩之后仍然是大 key,则需要进行拆分,一个大 key 分为不同的部分,记录每个部分的 key,使用 multiget 等操作实现事务读取;
- 也可以针对 string 大 key 的场景,把 string 的 value 分拆成几个 key-value,存储在一个 hash 中,每个 field 代表一个具体的属性,使用 hget、hmget 来获取部分的 value,使用 hset、hmset 来更新部分属性;
- 当 value 是 list/set 等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片。
16. Redis 支持事务回滚吗?#
分析
Redis 不具备完整的 ACID 特性,执行的命令都没有回滚之说,无论是事务还是 LUA 脚本中的命令,都是一样的。
回答
不支持,Redis 不具备完整的 ACID 特性,执行的命令都没有回滚之说,Redis 提供的 DISCARD 命令只能用来主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果。至于 LUA 脚本也是一样,比如 LUA 里面有 2 个写操作,执行了第一个如果 Redis 挂掉,那么第二个不会执行,第一个也不回撤回。
17. Redis 如何实现延迟队列?#
分析
首先要讲清楚什么是延迟队列:
延迟队列是指把当前要做的事情,往后推迟一段时间再做:
- 在淘宝、京东等购物平台上下单,超过一定时间未付款,订单会自动取消;
- 打车的时候,在规定时间没有车主接单,平台会取消你的单并提醒你暂时没有车主接单;
- 点外卖的时候,如果商家在 10 分钟还没接单,就会自动取消订单。
知道延迟队列是什么之后,不难发现 Redis 中 ZSet 最适合模拟这个功能。
回答
使用 ZSet,ZSet 有一个 Score 属性可以用来存储延迟执行的时间。使用 zadd score1 value1 命令,再利用 zrangebyscore 查询符合条件的所有待处理的任务,通过循环执行队列任务。
18. Redis 可以做消息队列吗?什么时候能用 Redis 做消息队列?#
分析
在某些场景,其实我们并不是一定需要有多可靠、多完善的消息队列,比如用消息队列发短信,我们肯定也经常遇到过,短信没收到的场景吧?没收到重试就行了。
所以,轻量级消息队列也有了市场需要,Redis 就很适合来做一个不那么完善的消息队列。
回答
Redis 可以作为轻量级消息队列。如果是本身业务轻量级,且团队没有已经接入完备的消息队列,这个时候没有必要引入一个重量消息队列,使用 Redis 即可满足要求,没有不能用的组件,只有不合适的场景。