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