尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java后端Redis面试题系统总结:从核心原理到高可用与性能调优

Java后端Redis面试题系统总结:从核心原理到高可用与性能调优 Redis在Java后端面试里几乎和小程序里的登录按钮一样高频。无论你是参加校招还是社招只要是Java后端开发岗位Redis基本是必问板块之一。我把两年里面试和被面试时收集、复盘过的Redis面试题做一次系统汇总按基础知识、持久化、高可用、缓存设计、性能排查来展开顺便把我自己在面试中被问懵过的点也标出来。适合正在准备Java后端开发岗位面试的朋友也适合已经入行但想补一补Redis短板的同学。这篇汇总不是让你背答案而是希望你能顺着思路理解“为什么这么问”“怎么答才显得有实战经验”。1. 为什么Java后端开发者绕不开Redis1.1 Redis在Java后端项目里的典型位置先聊一个很实际的问题Redis在一套典型的Java后端工程里一般会出现在哪里。最常见的位置就是缓存层比如用户信息、配置项、商品详情、热榜列表。以前我们用ConcurrentHashMap或Guava Cache做本地缓存单个服务内还凑合一旦拆分微服务多实例部署之后本地缓存就存在一致性问题于是大家开始把热点数据放到独立的Redis里。第二个典型位置是分布式会话。早期Java Web项目用Tomcat Session后面服务拆成多实例再去做负载均衡一个用户请求可能打到不同实例上Session就丢了。把会话信息放进Redis通过Spring Session一类的组件统一管理登录态才能实现多实例共享。第三个常见位置是计数器场景。帖子阅读数、点赞量、用户积分、库存余量这类高并发写操作如果每次都走数据库行锁会给数据库很大压力。Redis的INCR、DECR调用成本极低配合异步落库可以支撑很大量的计数更新。第四个位置就是分布式锁。在多个Java实例竞争同一资源时可以用Redis的原子命令实现锁比如秒杀场景里防止同一个用户重复下单这个后面单独展开。面试官不会直接问你“Redis放在项目哪里”反而会在聊项目时追问“你项目里为什么引入Redis不用行不行”我建议回答时先把业务背景说清比如高峰期数据库QPS扛不住再说明引入Redis后降低了多少压力最后一定要说代价。因为Redis引入了缓存和数据库一致性、过期失效等问题只有把代价也讲出来面试官才会觉得你不是在背目录。1.2 面试官从Redis题中考察的底层能力我既面过别人也被别人面过发现Redis提问背后有一套“漏斗式”逻辑。第一层先问数据结构看你对基础命令熟不熟第二层问持久化、主从、集群看你有没在真实环境部署过第三层问缓存穿透、击穿、雪崩看你在高并发流量下的设计能力第四层才问分布式锁、Redisson这些深水区考察并发和源码理解。所以大家准备面试时不要零散地背网上流传的“Redis四十题”。我习惯把Redis面试题分成四类是什么、怎么用、为什么好、出问题怎么办。“是什么”是数据类型、命令、持久化文件“怎么用”是缓存策略、分布式锁、Session管理“为什么好”是Redis为什么快、为什么用跳表“出问题怎么办”是大Key、热Key、慢查询、雪崩。带着这个分类去复习遇到没见过的题也能往框架上靠。2. Redis基础与核心数据结构面试题这是第一道门也是最容易被轻视的部分。很多Java后端开发每天在Spring Boot里用RedisTemplate对opsForValue()、opsForHash()非常熟但面试官一旦问到“String底层是什么”就卡住了。如果你只停留在API层说明对中间件的理解还不够深。2.1 Redis为什么快“单线程”到底单在哪关于Redis快很多候选人张口就来“纯内存操作”。这个答案只对了一半。内存快是基本前提但真正让它脱颖而出的是线程模型和数据结构设计。Redis 6.0之前核心读写命令是单线程执行的避免了多线程上下文切换和锁竞争。与此同时Redis通过IO多路复用在一个线程里同时监听大量客户端连接有事件来了才处理没有事件就继续等待。类似于一个前台接待多路访客谁按铃了才处理谁。再往深一点Redis做得更讨巧的是命令本身。它的大多数操作是O(1)或O(logN)比如String的读写就是讲内存指针移动和内存拷贝Hash、Set、ZSet也都针对高频操作做了大量优化。测试环境轻松跑到10万QPS但生产环境受网络、连接数影响会有折扣。这里几乎必出追问“既然单线程为什么6.0引入多线程”正确的理解是单线程在IO读写这块会变成瓶颈所以6.0用多线程来处理网络数据的读写和解析而命令执行主流程仍然是单线程。这样既保留了单线程的一致性优势又压榨了网络吞吐。还可以补一句单线程不是没有风险执行复杂Lua脚本或删除超大key都会阻塞后续命令这也是性能问题排查的起点。2.2 五种基本数据类型和底层实现这部分适合用“场景命令底层”三段式回答不要只干巴巴说类型。我做一个简表方便对照。数据类型常见命令底层实现典型场景StringGET、SET、INCR、DECRint、embstr、raw缓存对象、计数器、分布式IDHashHSET、HGET、HGETALL压缩列表、哈希表存储对象、用户信息、商品属性ListLPUSH、RPOP、LRANGE双向链表、快速列表简单队列、最新列表SetSADD、SREM、SPOP整数集合、哈希表去重、抽奖、共同好友ZSetZADD、ZRANGEBYSCORE、ZINCRBY跳表哈希表排行榜、延时队列面试时容易被追问的是String底层。Redis没有直接用C语言的字符数组而是包装了一个SDS结构记录长度和可用空间。这样O(1)获得字符串长度拼接时不会造成缓冲区溢出同时二进制安全。如果你能说出SDS和C字符串的区别就比单纯说“String就是字符串”强很多。Hash在面试中最常用来和String做对比。如果对象字段经常需要单独更新比如用户昵称、积分建议用Hash如果整体序列化成JSON直接覆盖用String更简单。List看起来能做消息队列但工程上要注意它没有消费确认和死信机制复杂消息场景还是交给RabbitMQ或Kafka。ZSet底层用跳表面试追问“为什么不用红黑树”时可以回答跳表实现简单、范围查找友好平均复杂度也是O(logN)而且Redis作者Antirez在设计时希望代码可读性高。2.3 BitMap、HyperLogLog、GEO、Stream这些加分项现在面试官也越来越喜欢问扩展类型用来判断候选人是否持续关注新特性。BitMap本质上是一个String类型的位图适合做签到和在线状态。比如“统计用户连续登录天数”可以用一个bit代表一天几十万用户占用的内存也非常小。HyperLogLog适合做UV统计也就是独立访客数用PFADD、PFCOUNT就能完成误差在0.81%左右。它最吸引人的地方是用极小的内存统计大量基数比如千万级UV可能只需要几KB内存和用Set去重相比节省很多。GEO从Redis 3.2开始引入底层是ZSet加经纬度编码可以很轻松实现“附近的人”、附近门店使用GEOSEARCH和GEODIST。Stream则是Redis 5.0加入的消息队列原生支持消费组和ACK比List更接近专业MQ但生产上成熟方案通常仍然会选独立消息中间件。如果能在回答基础类型之后主动提这几个扩展类型不仅丰富你的答案还会让面试官认为你有持续学习习惯。3. 持久化、主从复制与高可用这一模块很多开发者在本地单机玩过Redis但生产环境Redis一定不是孤零零一个节点。面试官想确认你不仅会读写还知道数据怎么不丢、故障怎么恢复。3.1 RDB与AOF怎么选为什么会有数据丢失RDB是快照持久化把内存全量数据以二进制格式写到磁盘。触发方式有save和bgsavesave是同步阻塞bgsave则由fork出的子进程负责落盘。RDB文件紧凑、加载快但两次快照之间的写操作如果遇到宕机就会丢失所以它适合做灾备和全量恢复不适合当唯一持久化方案。AOF是把每条写命令追加到日志文件启动时重放日志恢复数据。AOF有三种刷盘策略always每条命令都fsync安全但慢everysec每秒fsync一次性能和可靠性均衡也是默认配置no把刷盘时机完全交给操作系统。AOF相对RDB文件大、恢复慢但最多丢一秒或一条数据。Redis 4.0之后支持混合持久化用RDB做全量快照加上增量命令兼顾加载速度和数据安全。面试里经常问“到底该选RDB还是AOF”。我的经验是如果Redis只当缓存关掉持久化也能忍但只要涉及库存、订单、资金这种不能丢的数据必须开启AOF everysec同时用RDB做定时备份和快速启动。两个一起开时Redis在启动时会优先选择加载AOF文件因为它的数据更完整。3.2 主从复制怎么工作全量复制和部分复制主从复制是高可用的基础一个主节点负责写多个从节点负责读这样能把读压力分散开。核心是基于日志的同步Redis 2.8之后通过PSYNC支持全量复制和部分复制。全量复制大概是这个流程从节点发送PSYNC主节点执行bgsave生成RDB文件同时把新写入命令写进复制积压缓冲区RDB发给从节点之后再持续转发缓冲区里的命令。如果从节点因为网络原因断开重连时只要它需要的偏移量还在主节点的积压缓冲区里主节点就只补发缺失数据不需要重新全量同步。这里有一个非常重要的运维参数repl-backlog-size。如果积压缓冲区配得太小网络抖动期间积压的数据被迫淘汰从节点重连后只能从头全量复制对主从节点都会产生巨大IO压力。大家可以根据业务写入峰值计算比如每秒写入2MB希望容忍10秒断线那至少设置20MB。能说出这个计算逻辑面试官会认为你确实部署过集群。3.3 Sentinel与Cluster哨兵和集群怎么选哨兵模式解决自动故障转移。哨兵进程监控主从节点的存活状态。当一个哨兵发现主节点异常会标记主观下线多个哨兵都认为异常时进入客观下线状态再选举其中一个哨兵执行failover。Failover会把某个从节点提升为新主节点客户端通过Sentinel获取主节点地址主从切换对业务透明。Cluster则解决分片和水平扩展问题。Redis Cluster把16384个哈希槽分配到多个主节点每个key通过CRC16算法计算后对16384取模定位到具体的槽。每个主节点可以带从节点做冗余主节点故障时从节点顶上。由于数据分散在不同节点集群写性能和容量都能随节点数量扩展。Cluster面试中经常追问“为什么哈希槽是16384不是65536”CRC16是有65536种输出但Redis作者在设计时考虑到了节点间心跳包的大小。槽数量越大槽位信息在心跳消息里占用空间越多而16384个槽对于上千节点的集群规模已经足够还不会让控制消息过重。同时槽数量取2的14次方也有均分的便利。能说出消息量适中这个点说明你看过源码或官方文档面试官往往会更认可。4. 缓存设计、穿透击穿雪崩与分布式锁这部分是面试重灾区几乎每个Java后端岗位都会聊。核心原因是真实业务中只要引入Redis就必须考虑高并发下的缓存异常场景。这里必须把概念和处置方案讲透。4.1 穿透、击穿、雪崩怎么区分和应对先记一个简洁的区分口诀穿透是查根本不存在的数据击穿是一个热点数据刚好过期雪崩是大量数据同时过期或Redis整体宕机。三个词一字之差应对方法完全不同。缓存穿透最直观的表现是恶意请求不断用一个不存在的ID打到数据库。因为缓存里没有数据库里也没有每次请求都会穿透到DB。解决办法是先做参数校验非法ID直接拒绝然后可以缓存空值但是要设短过期时间更专业的方案是布隆过滤器在查缓存之前判断这个key“大概率是否存在”如果布隆过滤器说没有直接返回NotFound数据库不会被摸到。缓存击穿通常发生在爆款商品。某个key正好过期瞬间大量并发去数据库查数据库可能被打崩。两个经典方案是互斥锁和逻辑过期。互斥锁的思路是只让一个线程去数据库查其他线程等待结果逻辑过期则是缓存里不设置物理过期时间只存一个业务过期时刻查询返回旧值后台线程异步刷新。高并发下我偏向逻辑过期因为互斥锁会阻塞很多线程逻辑过期能做到响应快速代价是短暂读到旧数据。缓存雪崩有两个来源。第一个是大量key在同一秒过期解决方法是给过期时间加随机值比如TPS瞬间均匀打散第二个是Redis进程挂了这时必须依赖持久化快速恢复、冗余节点和熔断限流。多级缓存也是个好思路比如本地缓存Caffeine兜住一部分流量再打Redis最后才是数据库。4.2 缓存和数据库一致性Cache Aside、延迟双删这是几乎必选的经典题。我建议至少把Cache Aside讲清楚再补充延迟双删。Cache Aside的思路是读的时候先读缓存缓存没有则查数据库然后写缓存写的时候先更新数据库再删除缓存。为什么用删除缓存而不用更新缓存因为并发更新缓存容易互相覆盖删除缓存可以让下一次读请求重新加载。逻辑很简单但面临并发场景会有问题。举个例子线程A读到旧数据还没写回缓存线程B更新数据库并删除缓存之后线程A才把旧数据写进缓存这时候缓存里就是脏数据了。延迟双删就是为了规避更新数据库之后先删缓存等待几百毫秒再删一次。第二次删除可以把并发读线程写进的旧数据清掉。还需要注意延迟双删并不能彻底保证强一致如果第二次删除失败脏数据会一直存在。生产上更稳健的做法是借助MQ异步重试删除或者用Canal订阅Binlog同步更新缓存。回答时最好点明业务取舍金融类强一致场景不应该只依赖缓存应该直接写数据库、读数据库大部分非核心场景接受最终一致就够了。面试官听到这里就知道你不是一个只会套模板的人。4.3 Redis分布式锁从SETNX到Redisson这个问题几乎成为Java后端岗位的标配。最简单的分布式锁是SETNX但老版本有两个大坑一是SETNX成功之后服务挂了没有设置过期时间锁永远不释放二是加锁和设置过期时间如果不原子中间一旦宕机同样会死锁。所以官方推荐用SET key value NX EX seconds一条命令完成加锁value放唯一标识比如UUID释放锁时先判断value是否为当前线程持有再执行删除判断和删除必须放到Lua脚本里保证原子性。有了基础锁面试官会继续追问“锁过期了业务还没执行完怎么办”这时可以提Redisson看门狗。Redisson加锁时如果没有指定leaseTime会默认启动一个定时任务每隔一段时间给锁续期默认锁30秒看门狗每10秒续一次。业务结束后再释放锁避免锁被其他线程抢走。这个机制解答了锁过期时间难估的痛点如果你在项目里真的用过Redisson是很好的加分项。再往下面试官可能问哨兵模式下Redis锁会不会丢失。最标准的方法是提Redlock但注意Redlock本身也有争议。我现在的回答立场是生产环境尽量不用Redlock而是采用Zookeeper或etcd这类可靠一致性组件做分布式锁除非系统对性能和可用性有极特殊要求。在能单节点部署Redis的场景下Redisson单主节点锁配合看门狗已经覆盖绝大多数秒杀、防超卖场景。5. 性能调优与生产问题排查这部分最能体现后端实战能力。面试官通常会结合项目问“你们的Redis为什么变慢了怎么发现怎么解决”答不上来前面八股文再好也会打折扣。5.1 内存淘汰策略与过期清理机制Redis清理过期key的策略是惰性删除加定期删除。惰性删除是访问key时检查是否过期过期就删除定期删除是每隔一段时间抽查一批过期key并清理。这样做省性能但总会有漏网之鱼所以还需要内存淘汰策略兜底。内存淘汰策略主要有几类noeviction、allkeys-lru、allkeys-lfu、allkeys-random、volatile-lru、volatile-ttl。生产环境的取舍要分场景。如果Redis里除了缓存还存了业务数据我建议用noeviction内存满时直接报错让业务方知道有问题如果确定Redis只是缓存而且故意不会存重要业务数据用allkeys-lru或allkeys-lfu更合理。注意volatile-开头的策略只有设置了过期时间的key才参与淘汰没设置过期时间的key会一直占内存容易被绕过去。Redis 4.0引入LFU后很多情况下比LRU更合适。LRU按最近访问时间淘汰但一个key可能偶发一次访问就在缓存里续很久LFU统计访问频率和访问历史能更好过滤冷数据。如果面试官问“你线上怎么配的”你可以回答“我们看业务是热点比较集中所以用allkeys-lfu并对部分缓存key设置了最小访问频次既防止冷数据占内存又能保障热点数据不频繁重建”。这么说会显得你有真实调优经验。5.2 大Key、热Key、慢查询怎么排查大Key指单个key占用内存过大比如一个Hash有几百万字段或者一个String有几十MB。大Key风险很大执行HGETALL、DEL这类命令会阻塞Redis线上可能出现数百毫秒甚至数秒的抖动。排查可以用redis-cli --bigkeys扫描它会用游标遍历所有key并统计最大key但注意它在生产环境也会有一定开销建议在低峰期执行。清理大Key优先用UNLINK异步删除不要用DEL硬删除。如果大Key是Hash结构可以拆成多个小Hash按照业务维度分片。热Key是某些key访问量极高比如大促时某个爆款ID可能导致单节点CPU、带宽被打满。解决思路有三个方向本地缓存拦截一部分请求把热点key复制成多份加后缀分散到不同节点或者把热点Key所在的槽迁移到空闲节点。面试时最好结合客户端缓存聊比如在Java层用Caffeine做一级缓存Redis做二级缓存既把压力挡在应用层又不会牺牲实时性太多。慢查询也是高频排查项。用SLOWLOG GET能看到执行时间较长的命令常见原因包括大Key的O(N)操作、复杂的Lua脚本、MGET大量key。优化的手段包括减少范围命令比如HashMap一次性获取全部字段改成多获取一部分批量操作使用Pipeline把热点数据落到本地缓存降低单节点压力。5.3 常用监控命令与生产注意事项会看Redis状态是后端的重要能力。按经验列几个核心命令INFO能看内存、连接数、持久化状态CLIENT LIST能看谁的连接占满了SCAN可以代替KEYS遍历key避免阻塞MONITOR可以实时打印客户端命令但生产环境不建议长时间开启因为会拖垮Redis性能。Redis 6之后还支持ACL可以精确控制每个账号的权限比如禁止线上账号执行FLUSHALL、KEYS、CONFIG这类高危险命令这是很多团队忽略的安全点。SCAN是一个很有嚼头的面试点它能返回游标并分批次遍历但游标不保证单调递增甚至可能重复返回元素。因为它基于哈希槽的位移方式所以代码里看到SCAN返回值里有0时才表示遍历结束。我在线上第一次就踩过KEYS *的坑直接把单节点CPU打满后来才改成SCAN游标式遍历。把这个点讲出来比单纯背命令要好很多。6. 面试复盘我踩过的坑和答题建议最后这部分比前面所有题更关键。面试不只是考知识点还考你怎么组织表达、怎么面对追问不慌。我用自己经历过的失败案例给大家三个建议。6.1 三个容易翻车的Redis问题第一个“说一下Redis为什么快”。很多人的答案只有“内存快”三个字这只能拿一半分。要把内存、IO多路复用、高效数据结构、全局哈希表这些串起来还要补一句“单线程执行也有隐患”。面试官通常喜欢看你主动补反面这比背优点更能打动他们。第二个“Redis为什么用跳表不用红黑树实现ZSet”。很多人只背了“跳表实现简单”就没了。我建议再往下说一层跳表的区间查找方便、代码实现简单、不需要复杂的节点旋转和颜色标记红黑树虽然也能实现有序集合但范围查询和边界处理更麻烦。对Redis这么追求简洁的项目跳表是更务实的选择。第三个“如果AOF文件损坏了怎么办”。大多数人答不出。其实Redis提供redis-check-aof工具可以从损坏处修复文件、丢弃尾部无效数据让实例先启动起来。虽然数据会丢一点但至少能恢复服务。这个冷门命令答出来能给面试官留下实操过的印象。6.2 怎样把背过的“八股文”讲成真实经验我的个人经验是不要按题目顺序背而是准备两个真实场景作为“故事线”。比如你做过一个商品详情页那么就能把缓存穿透、击穿、雪崩、一致性串起来商品ID是外界传入的可能被打穿透我加了参数校验和布隆过滤器爆款商品有击穿风险我用了逻辑过期异步刷新后台改价格后我用延迟双删保证最终一致高峰期内存紧张我设置allkeys-lru。一条线把知识点串起来面试官听起来会觉得你真正处理过问题。分布式锁也一样可以落到你的具体业务“秒杀系统里用Redisson做防超卖加锁后先查库存再扣减锁的粒度是sku维度避免锁住整个商品提高了并发。”当面试官追问时你再解释看门狗和续期机制。每次回答都是解决问题的过程而不是罗列知识点。从这个角度准备比刷一百道题更有效因为大部分Redis面试题都会在你准备的这几个故事里自然浮现。我个人面试时最大的体会是当面试官把某个Redis问题往下追问到“线上遇到过吗”时如果你能掏出监控命令、排查思路、参数调整记录就已经赢过了大多数只会背答案的人。建议大家在复习每一道题时都问自己一句“这个解决方案我能在本地写一个2分钟的小脚本验证吗这个命令线上我真的执行过吗”纸上得来终觉浅Redis这种中间件尤其如此。把基础知识、高可用、缓存设计和排查经验串成一张网面试时自然会从容很多。
返回列表