写在前面经过前面14天的学习我们已经全面掌握了Redis的核心知识。今天是Redis系列的最后一篇我们将系统梳理Redis面试高频考点帮助大家查漏补缺顺利通过面试。文章目录写在前面一、Redis基础面试题1.1 Redis是什么有什么特点1.2 Redis为什么这么快1.3 Redis单线程为什么还能高并发1.4 Redis和Memcached的区别二、数据结构面试题2.1 Redis有哪些数据类型各有什么应用场景2.2 Redis String的内部编码有哪些2.3 Redis Hash的底层实现2.4 Redis ZSet为什么使用跳表而不是红黑树2.5 Redis List的底层实现三、持久化面试题3.1 Redis持久化机制有哪些3.2 RDB和AOF如何选择3.3 AOF重写是什么3.4 Redis重启时如何选择恢复方式四、高可用面试题4.1 Redis主从复制的原理4.2 哨兵模式的原理4.3 Redis Cluster的原理4.4 Cluster和哨兵的区别五、缓存应用面试题5.1 什么是缓存穿透、击穿、雪崩5.2 如何保证缓存和数据库一致性5.3 为什么是删除缓存而不是更新缓存六、分布式锁面试题6.1 Redis分布式锁如何实现6.2 Redis分布式锁的可靠性如何保证6.3 Redis分布式锁和Zookeeper分布式锁的区别七、性能优化面试题7.1 如何排查Redis性能问题7.2 如何优化Redis内存使用7.3 为什么KEYS命令在生产环境禁用八、其他高频面试题8.1 Redis如何实现消息队列8.2 Redis如何实现延迟队列8.3 Redis如何实现限流8.4 Redis如何实现分布式Session8.5 Redis内存满了怎么办九、学习路线总结9.1 Redis学习路线图9.2 面试重点总结十、参考资料十一、互动话题一、Redis基础面试题1.1 Redis是什么有什么特点答案RedisRemote Dictionary Server是一个开源的、基于内存的高性能键值对数据库。主要特点特点说明高性能基于内存读写速度极快10万QPS丰富数据类型String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、Geo、Stream持久化RDB和AOF两种持久化方式高可用主从复制、哨兵模式、Cluster集群单线程核心处理流程采用单线程模型支持事务提供事务支持保证原子性1.2 Redis为什么这么快答案原因说明基于内存数据存储在内存中读写速度极快单线程模型避免多线程上下文切换开销IO多路复用使用epoll实现高并发连接处理高效数据结构使用跳表、压缩列表等高效数据结构协议简单RESP协议简单高效详细解释单线程模型Redis核心处理流程采用单线程避免了多线程的锁竞争和上下文切换开销。IO多路复用Redis使用epollLinux实现IO多路复用单线程可以处理大量并发连接。高效数据结构字符串使用SDSSimple Dynamic String列表使用quicklist有序集合使用跳表小数据量使用压缩列表1.3 Redis单线程为什么还能高并发答案纯内存操作内存访问速度极快纳秒级非阻塞IO使用epoll实现IO多路复用单线程处理多个连接避免锁竞争单线程不需要加锁避免了锁开销CPU亲和性单线程可以充分利用CPU缓存注意Redis 6.0引入了多线程用于网络IO处理核心命令执行仍然是单线程。1.4 Redis和Memcached的区别答案对比项RedisMemcached数据类型丰富5种基础扩展仅支持String持久化支持RDB/AOF不支持集群原生支持Cluster需要客户端分片事务支持不支持内存管理更灵活更简单性能高高略快于Redis适用场景缓存数据存储纯缓存二、数据结构面试题2.1 Redis有哪些数据类型各有什么应用场景答案数据类型底层实现应用场景StringSDS缓存、计数器、分布式锁Hashziplist/hashtable对象存储、购物车Listquicklist消息队列、文章列表Setintset/hashtable标签、共同关注ZSetskiplistdict排行榜、延时队列BitmapString签到、布隆过滤器HyperLogLogString基数统计GeoZSet地理位置Streamlistpack消息队列2.2 Redis String的内部编码有哪些答案编码条件特点int整数值且范围在long内内存占用最小embstr字符串长度≤44字节一次分配紧凑存储raw字符串长度44字节SDS实现可修改查看编码OBJECT ENCODING mykey2.3 Redis Hash的底层实现答案Redis Hash底层使用两种编码ziplist压缩列表条件元素数≤512所有值长度≤64字节特点内存紧凑适合小数据量时间复杂度O(N)hashtable哈希表条件不满足ziplist条件特点O(1)时间复杂度扩容负载因子1时扩容2.4 Redis ZSet为什么使用跳表而不是红黑树答案对比项跳表红黑树实现复杂度简单复杂范围查询高效需要中序遍历内存占用略高较低并发友好更好一般插入删除简单需要旋转调整Redis选择跳表的原因实现简单跳表比红黑树更容易实现和维护范围查询高效跳表在找到起点后可以顺序遍历效率更高内存效率虽然跳表内存略高但Redis主要瓶颈不在内存2.5 Redis List的底层实现答案Redis 3.2之后List使用quicklist快速列表实现quicklist是ziplist的双向链表结合了ziplist的内存紧凑和链表的快速插入删除可以配置每个ziplist的大小和压缩策略# 配置参数 list-max-ziplist-size -2 # 每个ziplist大小 list-compress-depth 0 # 压缩深度三、持久化面试题3.1 Redis持久化机制有哪些答案机制RDBAOF方式快照日志追加文件大小小大恢复速度快慢数据安全性可能丢失更安全系统资源CPU消耗大IO消耗大适用场景备份、主从复制数据安全要求高3.2 RDB和AOF如何选择答案推荐方案同时开启RDB和AOF场景推荐方案数据安全要求高开启AOF允许少量数据丢失只开启RDB生产环境同时开启RDBAOF纯缓存场景可不持久化配置建议# RDB配置 save 900 1 save 300 10 save 60 10000 # AOF配置 appendonly yes appendfsync everysec3.3 AOF重写是什么答案AOF重写原理随着写操作增加AOF文件越来越大重写会创建一个新的AOF文件只保留最终数据的写入命令重写过程中不影响正常服务重写触发条件# AOF文件大小超过上次重写后大小的100% auto-aof-rewrite-percentage 100 # AOF文件最小64MB才触发重写 auto-aof-rewrite-min-size 64mb重写流程Redis创建子进程进行重写子进程根据内存数据生成新的AOF文件父进程继续处理命令写入AOF重写缓冲区子进程完成后父进程将重写缓冲区追加到新文件用新文件替换旧AOF文件3.4 Redis重启时如何选择恢复方式答案Redis启动时恢复数据的优先级如果开启AOF优先使用AOF恢复如果只开启RDB使用RDB恢复如果AOF文件损坏可以使用redis-check-aof修复# 修复AOF文件redis-check-aof--fixappendonly.aof# 检查RDB文件redis-check-rdb dump.rdb四、高可用面试题4.1 Redis主从复制的原理答案主从复制流程从节点连接主节点 ↓ 发送SYNC命令 ↓ 主节点执行BGSAVE生成RDB ↓ 主节点发送RDB给从节点 ↓ 从节点加载RDB ↓ 主节点发送缓冲区写命令 ↓ 进入增量复制阶段复制方式方式说明全量复制第一次同步或断开时间较长增量复制断开后重连只同步差异部分异步复制主节点写完立即返回异步同步4.2 哨兵模式的原理答案哨兵功能监控持续检查主从节点是否正常通知节点故障时通知管理员或其他应用自动故障转移主节点故障时自动选举新主节点故障转移流程哨兵检测主节点下线主观下线 ↓ 多个哨兵确认客观下线 ↓ 哨兵选举领导者 ↓ 领导者选举新主节点 ↓ 更新其他从节点的复制目标 ↓ 通知客户端新主节点地址配置示例sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 1800004.3 Redis Cluster的原理答案核心概念哈希槽将数据划分为16384个槽分配给不同节点数据分片每个节点负责一部分槽Gossip协议节点间通信交换状态信息槽位计算slot CRC16(key) % 16384MOVED重定向当客户端访问的key不在当前节点时返回MOVED错误告知正确的节点。4.4 Cluster和哨兵的区别答案对比项Cluster哨兵数据分片支持不支持高可用支持支持最小节点数6个3个扩展性支持在线扩容不支持客户端需要集群客户端普通客户端复杂度较高较低适用场景大数据量小数据量五、缓存应用面试题5.1 什么是缓存穿透、击穿、雪崩答案问题原因解决方案缓存穿透查询不存在的数据布隆过滤器、空值缓存缓存击穿热点key过期互斥锁、热点预热缓存雪崩大量key同时过期随机过期时间、多级缓存详细解答缓存穿透现象查询不存在的key请求穿透到数据库解决布隆过滤器过滤不存在的key或缓存空值缓存击穿现象热点key过期瞬间大量请求打到数据库解决加互斥锁只允许一个线程查询数据库缓存雪崩现象大量key同时过期或Redis宕机解决随机过期时间、多级缓存、熔断降级5.2 如何保证缓存和数据库一致性答案常用方案Cache Aside模式// 更新数据publicvoidupdate(Stringkey,Objectvalue){// 1. 先更新数据库db.update(key,value);// 2. 再删除缓存cache.delete(key);}// 读取数据publicObjectget(Stringkey){// 1. 先查缓存Objectvaluecache.get(key);if(value!null){returnvalue;}// 2. 查数据库valuedb.query(key);// 3. 写入缓存if(value!null){cache.set(key,value,expire);}returnvalue;}延迟双删publicvoidupdate(Stringkey,Objectvalue){// 1. 删除缓存cache.delete(key);// 2. 更新数据库db.update(key,value);// 3. 延迟后再次删除缓存Thread.sleep(500);cache.delete(key);}5.3 为什么是删除缓存而不是更新缓存答案对比项删除缓存更新缓存复杂度低高性能高懒加载低每次写都更新一致性较好可能不一致并发问题较少较多原因分析并发安全删除是幂等操作更新可能被覆盖性能考虑很多场景缓存可能不会被读取数据新鲜删除后下次读取时加载最新数据六、分布式锁面试题6.1 Redis分布式锁如何实现答案基本实现# 加锁 SET lock:key value NX PX 30000 # 解锁Lua脚本保证原子性 if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 endJava实现publicbooleanlock(Stringkey,Stringvalue,longexpireTime){returnredisTemplate.opsForValue().setIfAbsent(key,value,expireTime,TimeUnit.MILLISECONDS);}publicbooleanunlock(Stringkey,Stringvalue){Stringscriptif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;LongresultredisTemplate.execute(RedisScript.of(script,Long.class),Collections.singletonList(key),value);returnresult!nullresult1L;}6.2 Redis分布式锁的可靠性如何保证答案问题解决方案死锁设置过期时间误删锁使用唯一标识校验锁超时看门狗自动续期单点故障Redlock算法主从切换丢锁多节点部署Redisson实现RLocklockredisson.getLock(myLock);try{lock.lock();// 自动续期// 执行业务}finally{lock.unlock();}6.3 Redis分布式锁和Zookeeper分布式锁的区别答案对比项RedisZookeeper实现方式SET NX 过期时间临时顺序节点一致性最终一致强一致性能高中锁释放主动删除或过期Session断开自动释放可重入需要自己实现天然支持适用场景高并发高可靠七、性能优化面试题7.1 如何排查Redis性能问题答案排查步骤查看慢查询日志SLOWLOG GET 10检查内存使用INFO memory检查大keyredis-cli--bigkeys检查客户端连接CLIENT LIST实时监控MONITOR7.2 如何优化Redis内存使用答案优化方法说明选择合适的数据结构使用编码效率高的类型控制key长度使用简短的key名使用压缩编码ziplist、intset设置过期时间及时清理无用数据开启内存碎片整理activedefrag避免大key拆分大key7.3 为什么KEYS命令在生产环境禁用答案时间复杂度O(N)需要遍历所有key阻塞主线程Redis单线程KEYS执行期间无法处理其他请求影响可用性大量key时可能导致服务不可用替代方案使用SCAN命令SCAN 0 MATCH user:* COUNT 100八、其他高频面试题8.1 Redis如何实现消息队列答案方案实现特点ListLPUSH BRPOP简单不支持消费组Pub/SubPUBLISH SUBSCRIBE实时不持久化StreamXADD XREAD支持消费组可持久化Stream实现# 生产消息 XADD mystream * field1 value1 # 消费消息 XREAD GROUP mygroup consumer1 COUNT 1 STREAMS mystream 8.2 Redis如何实现延迟队列答案使用ZSet实现延迟队列// 添加延迟任务publicvoidaddDelayTask(StringtaskId,longdelayTime){longexecuteTimeSystem.currentTimeMillis()delayTime;redisTemplate.opsForZSet().add(delay:queue,taskId,executeTime);}// 消费延迟任务publicvoidconsumeDelayTask(){while(true){longnowSystem.currentTimeMillis();SetStringtasksredisTemplate.opsForZSet().rangeByScore(delay:queue,0,now,0,10);for(StringtaskId:tasks){// 尝试获取任务LongremovedredisTemplate.opsForZSet().remove(delay:queue,taskId);if(removed0){// 执行任务executeTask(taskId);}}Thread.sleep(100);}}8.3 Redis如何实现限流答案方案1计数器限流publicbooleanallowRequest(Stringkey,intlimit,longperiod){longcountredisTemplate.opsForValue().increment(key);if(count1){redisTemplate.expire(key,period,TimeUnit.SECONDS);}returncountlimit;}方案2滑动窗口限流publicbooleanallowRequest(Stringkey,intlimit,longperiod){longnowSystem.currentTimeMillis();longstartnow-period*1000;// 移除过期请求redisTemplate.opsForZSet().removeRangeByScore(key,0,start);// 统计当前窗口请求数longcountredisTemplate.opsForZSet().count(key,start,now);if(countlimit){redisTemplate.opsForZSet().add(key,UUID.randomUUID().toString(),now);returntrue;}returnfalse;}方案3令牌桶限流-- Lua脚本实现令牌桶localtokensredis.call(get,KEYS[1])iftokensfalsethentokensARGV[1]endiftonumber(tokens)0thenredis.call(decr,KEYS[1])return1elsereturn0end8.4 Redis如何实现分布式Session答案// 存储SessionpublicvoidsetSession(StringsessionId,Objectdata){Stringkeysession:sessionId;redisTemplate.opsForValue().set(key,data,30,TimeUnit.MINUTES);}// 获取SessionpublicObjectgetSession(StringsessionId){Stringkeysession:sessionId;returnredisTemplate.opsForValue().get(key);}// 删除SessionpublicvoiddeleteSession(StringsessionId){Stringkeysession:sessionId;redisTemplate.delete(key);}8.5 Redis内存满了怎么办答案内存淘汰策略策略说明noeviction不淘汰写入报错allkeys-lru所有key使用LRU淘汰volatile-lru设置过期时间的key使用LRU淘汰allkeys-lfu所有key使用LFU淘汰volatile-lfu设置过期时间的key使用LFU淘汰allkeys-random所有key随机淘汰volatile-random设置过期时间的key随机淘汰volatile-ttl淘汰即将过期的key配置maxmemory 4gb maxmemory-policy allkeys-lru九、学习路线总结9.1 Redis学习路线图基础阶段 ├── Redis简介与安装 ├── 数据类型与命令 ├── 持久化RDB/AOF └── 主从复制 进阶阶段 ├── 哨兵模式 ├── Cluster集群 ├── 缓存应用穿透/击穿/雪崩 └── 分布式锁 高级阶段 ├── 性能优化 ├── 内存管理 ├── 监控运维 └── 源码分析9.2 面试重点总结知识点重要程度考察频率数据类型与应用场景★★★★★必考持久化机制★★★★★必考主从复制原理★★★★☆高频哨兵模式★★★★☆高频Cluster集群★★★★☆高频缓存三兄弟★★★★★必考分布式锁★★★★★必考性能优化★★★★☆高频十、参考资料Redis官方文档十一、互动话题恭喜你完成了Redis系列的学习这个系列对你有帮助吗欢迎反馈学习心得面试中遇到过哪些Redis问题欢迎分享还想学习哪些技术欢迎留言建议Redis系列文章完结撒花感谢大家的陪伴