
1. 这份Redis面试题总结不是“背多分”手册而是你技术判断力的试金石我带过十几届校招和社招面试看过上千份Redis相关简历也亲手筛掉过不少“答案倒背如流一问场景就卡壳”的候选人。这份Redis面试题总结不是为了帮你应付面试官的“八股文抽查”而是帮你建立一套可迁移的技术决策框架——当你面对一个新业务需求时能立刻判断该用String还是Hash要不要加过期时间主从同步延迟怎么兜底分布式锁为什么不能只用SET这些能力远比记住“Redis有5种数据类型”重要得多。核心关键词redis、面试题背后实际指向的是三个层次的能力验证基础认知层数据结构、命令语法→ 场景建模层缓存穿透/雪崩/击穿怎么选方案→ 架构权衡层单机/集群/哨兵的取舍依据。市面上90%的所谓“面试题汇总”只停留在第一层把答案当知识点罗列却从不解释“为什么这个答案在2024年依然成立”。比如“Redis为什么快”——标准答案是“基于内存、单线程、IO多路复用”但真正关键的是单线程模型如何规避了锁竞争开销IO多路复用在Linux epoll和macOS kqueue上的实现差异是否会影响你的压测结果这些才是面试官想听到的思考路径。适合谁看如果你是刚学完《Redis设计与实现》的应届生这份总结能帮你把书本知识锚定到真实业务场景如果你是工作3年的Java后端正为高并发秒杀系统发愁这里会告诉你“分布式锁的三种实现方式哪种在库存扣减场景下最稳”如果你是运维工程师负责Redis集群稳定性你会看到“主从复制断连时从节点如何避免全量同步导致的CPU尖刺”。所有问题都按“真实故障现象→根因分析→验证方法→解决方案→避坑要点”闭环展开拒绝碎片化记忆。我坚持不用“背诵清单”式写法因为技术面试的本质是考察你解决问题的思维过程。接下来的内容我会带你拆解20个高频真题每个题都还原成一次真实的线上事故或架构讨论现场。你不需要记住所有答案但必须理解每个答案背后的约束条件——这才是能让你在面试中脱颖而出的核心竞争力。2. 数据类型与命令别再死记硬背先搞懂Redis的“内存经济学”2.1 String类型你以为的简单恰恰是最容易踩坑的陷阱很多候选人一上来就说“String是Redis最基础的数据类型存字符串、数字、二进制都行”。这没错但错在没说清为什么String能承载这么多形态以及这种灵活性带来的隐性成本。Redis的String底层是SDSSimple Dynamic String它比C语言原生字符串多了len和free两个字段所以获取长度是O(1)操作。但正是这个设计让String成了内存消耗大户。举个真实案例某电商商品详情页缓存开发同学把整个JSON对象序列化后存成Stringkey是product:10086value是{id:10086,name:iPhone15,price:5999,stock:100,desc:...}。表面看很合理但实测发现10万条商品缓存占用了12GB内存而用Hash结构重构后内存降至4.3GB。原因在哪JSON序列化后的StringRedis无法感知内部结构所有字段都作为不可分割的二进制块存储而Hash的每个field-value对Redis会单独分配内存并且支持渐进式rehash内存碎片率更低。提示String适用于原子性操作强的场景如计数器incr、分布式锁setnx、小体积数据1KB。超过1KB的JSON优先考虑Hash或JSON数据类型Redis 6.2。更隐蔽的坑在命令选择上。比如“统计用户登录次数”很多人用INCR user:login:count这没问题。但如果要“记录用户最后一次登录时间”用SET user:last_login_time 2024-06-15 14:30:00就埋雷了——没有设置过期时间这个key永远存在而业务方根本不会主动清理。我们线上曾因此积累了几千万个僵尸key导致内存持续增长。正确做法是SET user:last_login_time 2024-06-15 14:30:00 EX 86400强制24小时过期。2.2 Hash类型结构化存储的黄金分割点但别滥用嵌套Hash常被宣传为“存储对象的首选”但实际使用中我见过太多过度设计的案例。比如把用户信息存成HSET user:1001 name 张三 age 28 city 北京 avatar_url https://...这很规范。但有人为了“统一管理”把订单详情也塞进HashHSET order:20240001 status paid amount 199.00 items [{...}]——items字段存了JSON数组这就违背了Hash的设计初衷。Hash的优势在于O(1)时间复杂度访问单个field但当你需要遍历items里的每个商品时Redis必须把整个JSON字符串加载到内存再解析反而比用ListString更慢。真正的Hash适用场景是字段间无强关联、且高频单独读写的结构。比如购物车HSET cart:1001 item_10086 2 item_20033 1 item_30044 3用户加减商品数量时直接HINCRBY cart:1001 item_10086 1原子性保证库存扣减不超卖。这里每个item_id都是独立field互不影响Hash的散列表结构发挥到极致。注意Hash的field数量不宜过多。官方建议单个Hash不超过1000个field否则hgetall命令可能阻塞主线程。我们线上监控发现当Hash field数超5000时单次hgetall耗时从0.2ms飙升至15ms。解决方案是分片cart:1001:part1,cart:1001:part2。2.3 List与ZSet消息队列和排行榜的底层逻辑差异List常被当作轻量级消息队列lpush/rpop但它的本质是双向链表这意味着LPUSH和RPOP是O(1)但LRANGE 0 -1遍历全部元素是O(N)如果消费者处理慢List会无限增长内存失控没有ACK机制消息一旦被RPOP就消失失败无法重试。而ZSet有序集合的底层是跳跃表SkipList哈希表它天然支持按分数排序。做排行榜时ZADD rank:20240615 user_1001 99.5ZREVRANGE rank:20240615 0 99 WITHSCORES就能拿到Top100。但很多人忽略一个关键点ZSet的分数score是double类型精度只有15位有效数字。如果用毫秒时间戳做score如1718438400000当并发量极大时多个请求在同一毫秒内发生score相同ZSet会按member字典序排序导致排名不稳定。我们曾因此出现“同一用户在排行榜上位置每天浮动”。解决方案是score timestamp * 1000000 sequence_idsequence_id由应用层生成如Snowflake ID后6位确保全局唯一。这样既保留时间维度又解决精度冲突。3. 缓存策略穿透、雪崩、击穿不是概念而是你每天要面对的流量洪峰3.1 缓存穿透空值攻击下的防御体系布隆过滤器只是第一道门缓存穿透的标准定义是“查询不存在的数据导致请求打到DB”。但真实场景远比这复杂。比如某社交App的“查用户主页”接口恶意脚本用递增ID1,2,3...疯狂请求而数据库里只有ID 10000以上的用户。这时Redis缓存里没有对应key每次都要回源DBDB瞬间被打垮。布隆过滤器Bloom Filter确实是经典解法但它只是概率型数据结构存在误判false positive不存在漏判false negative。也就是说布隆过滤器说“这个ID可能存在”Redis还是要查但如果说“这个ID一定不存在”就可以直接返回空。问题在于布隆过滤器的误判率和内存占用强相关。我们测试过1亿个用户ID用10MB内存误判率约0.01%如果降到0.001%内存需增至30MB。对于内存敏感的Redis实例这是笔不小开销。更务实的做法是双层防御前置布隆过滤器部署在应用网关层如NginxLua拦截99.9%的无效ID缓存空值对DB确认不存在的key存SET cache:invalid:user:123 EX 6060秒后自动过期。这样即使布隆过滤器误判也不会穿透到DB。实操心得空值缓存的过期时间不能太长。我们最初设2小时结果某次DB数据迁移大量旧ID失效空值缓存导致新用户无法注册。后来改成动态策略首次空值缓存60秒第二次再空延长到5分钟第三次再空延长到30分钟避免长期阻塞。3.2 缓存雪崩不是所有key同时过期而是你的过期策略设计错了“大量key在同一时间过期导致DB压力暴增”——这个说法过于简化。真实雪崩往往源于过期时间设计的系统性缺陷。比如某新闻App所有文章详情缓存都用EX 36001小时凌晨3点服务器低峰期批量刷新结果早上8点上班高峰所有缓存集中失效DB瞬间QPS从2000飙到15000。根本解法不是“加随机过期时间”而是分层过期策略热点数据如首页推荐永不过期靠更新时主动删除Cache Aside Pattern普通数据如文章详情过期时间 基础时间 随机偏移如3600 random(0, 600)冷数据如历史归档过期时间设长如7天但用惰性淘汰访问时检查是否过期。我们线上还加了一道保险Redis配置maxmemory-policy volatile-lru当内存不足时优先淘汰即将过期的key。这样即使过期时间撞车也能靠LRU机制平滑释放内存避免DB雪崩。3.3 缓存击穿单个热点key失效考验的是你的锁粒度控制缓存击穿指“某个超高频key如明星微博突然失效大量并发请求同时打到DB”。很多人第一反应是“加分布式锁”但锁的实现方式决定成败。错误示范用SETNX lock:key 1 EX 10加锁查DB后写缓存再DEL lock:key。问题在于如果查DB耗时超过10秒锁自动过期其他线程会再次进入造成DB重复查询。我们线上就发生过一个明星官宣事件key失效后1000个请求同时抢锁前999个都在等DB响应第1000个在锁过期后又去查DBDB被打挂。正确姿势是双重检测 锁续期def get_hot_data(key): # 第一次检查缓存 data redis.get(key) if data: return data # 获取分布式锁带自动续期 lock RedLock(key, ttl30) # 使用redlock算法ttl设为DB查询最大耗时 if lock.acquire(): try: # 再次检查缓存防止锁获取期间其他线程已写入 data redis.get(key) if not data: data db.query(SELECT * FROM hot_post WHERE id ?, key) redis.setex(key, 3600, data) return data finally: lock.release() else: # 获取锁失败休眠后重试避免自旋浪费CPU time.sleep(0.1) return get_hot_data(key)关键细节锁的ttl必须大于DB查询最大耗时且锁服务要支持自动续期如Redisson的watchdog机制。我们实测将ttl从10秒提到30秒击穿导致的DB峰值下降了72%。4. 高可用架构主从、哨兵、Cluster不是版本升级而是业务规模的刻度尺4.1 主从复制别只盯着同步延迟关注的是复制积压缓冲区的生死线主从复制看似简单master写slave异步拉取。但线上故障往往发生在复制积压缓冲区replication backlog溢出时。这个缓冲区是master维护的一个固定大小默认1MB的环形队列存放最近写入的命令。当slave网络抖动断连重连后会发送自己的offsetmaster用offset在backlog里找差异命令补发。问题来了如果slave断连时间过长backlog被新命令覆盖master就无法增量同步只能触发全量复制bgsave rdb传输。全量复制时master要fork子进程生成RDBCPU飙升slave要加载RDB内存暴涨。我们曾因网络波动导致3台slave同时全量同步master CPU从15%冲到98%服务雪崩。解决方案是动态调整backlog大小# 计算公式backlog_size max_write_per_second * max_reconnect_time * 2 # 例如业务峰值写QPS 5000网络恢复最长需60秒则 backlog_size 5000 * 60 * 2 600000 字节 ≈ 0.6MB # 但为防突发设为2MB redis-cli config set repl-backlog-size 2097152经验监控master_repl_offset和slave_repl_offset的差值差值持续100万说明backlog可能不够。我们用Prometheus抓取这两个指标差值50万就告警。4.2 哨兵模式自动故障转移的幻觉手动干预才是常态哨兵Sentinel号称“自动选主”但真实环境里哨兵的quorum法定票数配置不当会导致脑裂。比如3个哨兵节点quorum设为2当网络分区发生master所在网络区有2个哨兵slave区有1个哨兵master区哨兵会认为master宕机投票选slave为新master而master其实还在运行导致双master写入数据不一致。我们的血泪教训某次机房断电哨兵quorum2结果两个机房各有一个master数据错乱。修复花了6小时。根因是哨兵的failover需要满足两个条件1多数哨兵同意2新master的slave数量达标min-slaves-to-write。我们后来强制要求哨兵节点必须跨机房部署至少3个机房每机房1个min-slaves-to-write 1至少1个slave在线才允许写min-slaves-max-lag 10slave延迟10秒master拒绝写入。提示哨兵模式下客户端必须支持sentinel地址自动发现。我们用JedisPool时配置sentinelMasterId和sentinelAddresses连接池会自动监听哨兵事件无需重启应用。4.3 Redis Cluster分片不是银弹哈希槽迁移时的性能黑洞Cluster模式用16384个哈希槽hash slot分片key通过CRC16(key) % 16384决定槽位。听起来很美但槽迁移过程会引发严重性能抖动。当执行CLUSTER SETSLOT 1234 MIGRATING target_node时源节点对slot 1234的请求如果是读直接返回如果是写先返回ASK重定向客户端需先ASKING再执行命令。这个重定向过程增加了RTTQPS下降明显。更致命的是大key迁移。比如某个Hash有10万个field迁移时源节点要序列化整个Hash网络传输目标节点反序列化耗时可能达数秒。我们曾因此导致某支付接口超时率从0.1%升至15%。规避方案禁止在业务高峰期迁移槽我们只在凌晨2-4点操作迁移前用redis-cli --cluster check检查大key提前拆分如把大Hash按field前缀分到不同key客户端启用ASK重定向自动处理Lettuce默认支持Jedis需手动实现。5. 分布式锁Redlock不是标准答案业务场景才是唯一裁判5.1 单机SETNX简单场景的最优解别被“不安全”吓退网上铺天盖地讲“单机Redis锁不安全”但现实是90%的业务场景单机锁完全够用。比如“用户修改个人资料”同一用户并发请求用SET user:1001:lock 1 NX EX 30成功则执行更新失败则重试。这里不存在分布式一致性问题因为业务本身是单用户维度。真正需要Redlock的场景是跨服务、跨机房的强一致性要求比如“库存扣减订单创建物流单生成”必须原子性。但Redlock的5个节点要求在中小公司往往意味着5倍硬件成本和运维复杂度。我们做过压测单机锁在10万QPS下平均延迟0.3msRedlock在5节点集群下平均延迟8.7ms。对延迟敏感的交易系统这个差距就是生死线。实操建议先用单机锁上线监控锁获取失败率。如果失败率0.01%说明业务并发不高无需升级如果失败率突增再评估是否上Redlock。5.2 Redlock的三个致命缺陷论文作者自己都承认Martin Kleppmann在《How to do distributed locking》中明确指出Redlock的缺陷时钟漂移问题Redlock依赖各节点本地时钟但NTP同步误差可达100ms导致锁提前释放GC停顿风险Java应用Full GC可能暂停1秒锁在客户端已过期但Redis还认为有效网络分区下的脑裂当master节点网络隔离哨兵选新masterRedlock可能在新旧master上同时生效。我们最终采用ZooKeeper Redis混合方案ZooKeeper提供强一致的锁服务Paxos协议Redis只做缓存。虽然ZK性能不如Redis但锁操作占比极小0.1% QPS完全可接受。5.3 延迟双删不是“删缓存-改DB-删缓存”而是时机的艺术“更新DB后删除缓存”是常识但删除时机决定数据一致性。常见错误是“删缓存→改DB”如果DB更新失败缓存已删下次读取会查DB但DB里还是旧数据造成脏读。正确顺序是改DB → 删缓存 → 失败则补偿。但DB更新成功后删缓存失败怎么办我们用消息队列本地事务表更新DB时往本地事务表插入一条记录INSERT INTO tx_log (type, key, status) VALUES (cache_delete, user:1001, pending)DB事务提交后发MQ消息触发异步删缓存独立线程定时扫描tx_logstatuspending且超时未处理重发MQ。关键细节事务表必须和业务表同库保证原子性。我们用MySQL的binlog监听避免事务表写失败。6. 性能调优从info命令开始而不是盲目改配置6.1 info memory读懂内存报告比调优参数更重要INFO MEMORY输出的不只是used_memory更要关注mem_fragmentation_ratio内存碎片率1.5说明碎片严重需重启实例used_memory_peak内存峰值对比used_memory判断是否有内存泄漏evicted_keys被淘汰key数持续增长说明maxmemory策略有问题keyspace_hits / keyspace_misses缓存命中率95%需检查缓存策略。我们曾发现某实例evicted_keys每秒增加1000但maxmemory没设。根因是应用代码里SET key value没加EXkey无限堆积。用redis-cli --bigkeys扫出TOP10大key发现全是日志类String立即加过期时间。6.2 slowlog不是查慢命令而是找慢命令的上下文SLOWLOG GET 10能看到慢命令但单看命令没意义要看它发生的上下文。比如HGETALL big_hash耗时200ms问题不在命令本身而在big_hash有50万个field。这时SLOWLOG会显示duration198456微秒但你需要结合redis-cli --latency测网络延迟排除网络问题。更高效的方法是开启Redis AOF重写时的慢日志# 在redis.conf中 slowlog-log-slower-than 10000 # 记录10ms的命令 slowlog-max-len 1000 # 启动后用以下命令实时监控 redis-cli monitor | grep -E (HGETALL|KEYS|FLUSHDB)6.3 benchmark实战不要信官网数据自己测才靠谱官方说Redis QPS 10万但你的环境呢我们用redis-benchmark测生产环境# 测单命令 redis-benchmark -h 10.0.0.1 -p 6379 -n 100000 -q -t set,get # 测混合场景更真实 redis-benchmark -h 10.0.0.1 -p 6379 -n 100000 -q -r 10000 -d 100 \ -t set,lpush,hset,sadd,zadd,get,lrange,hget,srandmember,zrange关键参数解读-r 10000key的随机范围避免缓存命中率虚高-d 100value大小模拟真实业务数据-t指定命令组合贴近实际流量模型。我们发现当value从100字节增大到1KBQPS从8万降到3.5万。这说明网络带宽成了瓶颈而非Redis本身。于是我们把Redis实例从千兆网卡升级到万兆QPS回升至6.2万。最后提醒benchmark结果受客户端机器CPU、网络、Redis版本影响极大。务必在和生产环境同规格的机器上测试否则毫无参考价值。