6.9 KiB
id, title, type, domain, stack, tags, level, related, week
| id | title | type | domain | stack | tags | level | related | week | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| backend.redis.interview | Redis 面试题 | interview | backend |
|
|
intermediate |
|
|
Redis 面试题
基础
【基础】Redis 为什么快?
参考答案:数据在内存里;核心命令执行是单线程的,没有锁竞争和线程切换;使用 IO 多路复用(epoll)同时处理大量连接;数据结构针对场景做了优化(如压缩列表、跳表)。Redis 6 起网络读写可以多线程,命令执行仍是单线程。
追问:Redis 6 的多线程是怎么回事?→ 多线程只用于网络读写(解析请求、写回响应),命令执行仍然是单线程,所以命令的原子性不变。
【基础】Redis 有哪些数据类型?各举一个使用场景。
参考答案:String(缓存、计数器、验证码)、Hash(对象、购物车)、List(最新消息列表)、Set(标签、去重、共同关注)、ZSet(排行榜、延迟队列)、Stream(轻量消息队列);另有 Bitmap(签到)、HyperLogLog(UV 统计)、Geo(附近的人)。
追问:排行榜为什么用 ZSet?→ 有序集合按分数自动排序,插入和更新都是 O(log N),可以直接按排名范围查询。
【基础】过期的 key 是怎么删除的?内存满了怎么办?
参考答案:惰性删除(访问时发现过期就删)+ 定期删除(周期性抽样检查过期 key)。内存达到 maxmemory 时按淘汰策略处理:noeviction(拒绝写入)、allkeys-lru、allkeys-lfu、volatile-lru(只淘汰设置了过期的 key)等,缓存场景常用 allkeys-lru / allkeys-lfu。
追问:大 key 有什么危害?→ 读写和删除都会阻塞主线程、占用网络带宽、集群中导致数据倾斜。删除大 key 用 UNLINK(异步删除),并拆分数据结构。
【基础】RDB 和 AOF 的区别?
参考答案:RDB 定时生成内存快照,文件小、恢复快,但可能丢失最后一次快照之后的数据;AOF 追加记录写命令,可配置每秒刷盘,丢数据更少,但文件更大,需要重写压缩。生产常两者都开,Redis 4 起支持混合持久化。
追问:生产环境一般怎么配置持久化?→ 常见是开启 AOF(everysec)+ RDB 备份,Redis 4.0+ 可以使用混合持久化;只做缓存的场景可以不开持久化。
进阶
【进阶】缓存穿透、击穿、雪崩分别是什么?怎么解决?
参考答案:穿透是查询不存在的数据,每次都打到数据库——缓存空值、参数校验、布隆过滤器。击穿是热点 key 过期瞬间大量请求同时回源——互斥锁 / singleflight 只让一个请求回源,或逻辑过期后台刷新。雪崩是大量 key 同时过期或 Redis 不可用——过期时间加随机值、Redis 高可用、服务限流降级。
追问:布隆过滤器有什么缺点?→ 存在误判(判断存在时可能不存在),不能删除元素(普通布隆过滤器);数据变化时需要重建。
【进阶】怎么保证缓存和数据库的一致性?
参考答案:常用旁路缓存:先更新数据库,再删除缓存。删除而不是更新,是为了避免并发写导致缓存写入旧值。仍有极小概率不一致,可以用延迟双删、订阅 binlog 异步删除、短 TTL 兜底。缓存只能做到最终一致,要求强一致的数据(余额、库存最终值)以数据库为准。
追问:为什么不先删缓存再更新数据库?→ 删除后、更新前的读请求会把旧值写回缓存,不一致窗口更大。
【进阶】Redis 分布式锁怎么实现?有什么问题?
参考答案:SET key 随机令牌 NX PX 过期时间 加锁;用 Lua 脚本比较令牌后删除来解锁,防止误删别人的锁。问题:业务执行时间超过过期时间会导致两个持有者同时存在(续期 / 看门狗);主从异步复制,主节点宕机时锁可能丢失(Redlock 有争议);所以关键业务还要用数据库唯一约束、乐观锁等做最终保证。
追问:锁过期了业务还没执行完怎么办?→ 自动续期(看门狗);业务本身做好幂等和数据库层面的条件更新,不能完全依赖锁的正确性。
【进阶】Redis 的主从、哨兵、集群分别解决什么问题?
参考答案:主从复制解决读扩展和数据冗余;哨兵(Sentinel)监控主节点,故障时自动选新主,解决高可用;Cluster 把数据按 16384 个槽分到多个主节点,解决容量和写扩展,每个主节点也可以有从节点。Cluster 下多 key 操作(包括 Lua)要求 key 在同一个槽,可以用哈希标签 {}。
追问:集群模式下多个 key 的操作有什么限制?→ 多 key 命令和 Lua 脚本要求 key 在同一个槽,用哈希标签 {order}:1 让相关 key 落在同一个槽。
场景题
【场景题】秒杀场景怎么防止超卖和重复购买?
参考答案:库存提前加载到 Redis;Lua 脚本原子完成“是否已购买 → 库存是否充足 → 扣减 → 记录用户”;成功后发 MQ 异步创建订单,数据库唯一约束兜底;失败或超时未支付回补库存;前端、网关层层限流。详见库存预扣场景。
追问:Redis 扣了库存但创建订单失败怎么办?→ 回补库存(补偿);或者 Redis 只做预扣,以数据库为准,异步对账修正。
【场景题】线上 Redis 突然变慢,怎么排查?
参考答案:SLOWLOG GET 看慢命令(KEYS *、大 key 的 HGETALL / DEL、复杂 Lua);INFO 看内存、连接数、命中率、是否在做 RDB / AOF 重写(fork 导致的卡顿);--bigkeys / MEMORY USAGE 找大 key;检查是否有热点 key 集中在一个节点。处理:改用 SCAN、UNLINK,拆分大 key,热点 key 加本地缓存。
追问:怎么找出慢命令?→ SLOWLOG GET 查看慢日志;--bigkeys / MEMORY USAGE 找大 key;INFO 看内存、客户端数、持久化状态。
【场景题】一个接口要按用户限流,每分钟最多 60 次,多实例部署,怎么做?
参考答案:用 Redis 共享计数。简单做法是固定窗口 INCR + 过期时间;要求精确时用 ZSET 滑动窗口(Lua 脚本里删除过期记录、计数、写入);允许突发用令牌桶。返回 429 并带上重试时间。要考虑 Redis 故障时的策略(放行还是拒绝)。详见分布式锁和限流。
追问:固定窗口有什么问题?→ 窗口边界处可能出现 2 倍的突发请求(上一个窗口末尾和下一个窗口开头各 60 次),需要平滑时用滑动窗口或令牌桶。