ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PHP面试必问Redis核心知识:从数据类型到分布式锁的实战解析

PHP面试必问Redis核心知识:从数据类型到分布式锁的实战解析 这两年面过不少PHP岗位也帮身边朋友做过模拟面试Redis基本上是所有PHP面试里绕不开的一块。很多候选人一听到Redis核心知识第一反应就是背五种数据类型String、Hash、List、Set、ZSet背得滚瓜烂熟。但真往深了问一句你们项目里Key是怎么设计的或者缓存穿透你们怎么防的不少人就卡住了。这篇文章我不打算给你列一份标准答案背诵清单而是把面试官真正想听的东西拆开讲清楚从数据类型到持久化从缓存雪崩到分布式锁每一块都说说是怎么在生产环境里落地、被问到的时候怎么回答才能拿高分。适合准备跳槽的PHP开发也适合刚接触Redis、想搞懂核心概念的初学者。1. 面试官考Redis不只是让你报数据类型1.1 从简历上熟悉Redis四个字说起很多PHP候选人简历里都写着熟悉Redis但真正聊起来往往停留在会用的层面SpringBoot整合一下、存个Session、做个缓存顶多再知道个分布式锁。可面试官心里很清楚Redis在PHP项目里承担的角色远不止缓存两个字。它可以做计数器、做排行榜、做消息队列、做分布式锁、做限流器、做布隆过滤器甚至做附近的人这种地理位置计算。所以面试官问Redis表面在问知识点实际在判断两件事第一你有没有在真实项目里用过它而不是只在本地写过Demo第二你遇到问题的时候是能自己分析原理去解决还是只会网上搜一段代码贴上去。我有一个很直观的感受面试官问Redis支持哪些数据类型的时候本质上不是考察记忆力而是在给你递话。他希望你顺着这个话题讲出应用场景、底层结构、踩过的坑从而判断你的实战深度。如果你只回一句五种String、Hash、List、Set、ZSet那这道题就变成了送分题你等于把一个展示自己的机会直接扔掉了。1.2 面试官判断用过还是背过的三个信号根据我多次参与技术面试的经验面试官问Redis相关问题时主要看三个信号来判断候选人是不是真的在项目里用过。第一个信号是Key的命名习惯。真的用过Redis的人开口就是项目里我们的Key规范是业务名:对象名:唯一ID比如 order:payment:20260701001而背题的人只会说Key就是字符串。这个差异特别明显因为生产环境里Redis的Key命名规范直接影响到后续的排查、维护和集群迁移。第二个信号是是否主动谈到坑。比如说到Hash类型时真正用过的人会提到Hash在底层有两种编码ziplist和hashtable当字段少值小的时候用ziplist省内存或者用HGETALL拉大Hash对象时会有阻塞风险。这类细节是背八股文背不出来的只有真正写代码Debug过的人才会有体感。第三个信号是遇到异常时的处理方式。比如线上Redis连接超时了你怎么排查是大规模缓存失效导致的还是big key拖垮了网络还是连接池配置不合理。面试官问这类开放性问题时就是想听你的排查路径是否清晰。所以我一直跟身边准备面试的朋友说Redis这部分不要当文科背要当成理科理解。每一种数据类型的底层结构、每个命令的时间复杂度、每个参数配置的含义都应该知道为什么。2. 五种数据类型怎么答才能拿高分场景、底层与命令细节2.1 String类型最基础却最容易被问出破绽String是Redis里最基础也是用得最多的类型PHP项目里缓存一个用户信息、存个验证码、做访问量计数器用的都是它。但面试官问String时往往不满足于能存字符串而是会往下追问几个层次。第一层是问你String底层用的什么结构。Redis的String并不是C语言原生字符串而是封装了一个叫SDSSimple Dynamic String简单动态字符串的结构。SDS相比C字符串有两个核心优势一是O(1)复杂度获取字符串长度因为SDS结构中直接保存了len字段二是自动扩容不会出现缓冲区溢出。很多人在这个点上一带而过其实你可以多讲一句SDS在Redis 3.2之后分成了sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64这几种类型根据字符串长度不同选用不同header目的是省内存这一句话就能让你的回答和其他人拉开差距。第二层是追问String的三种编码方式。int编码用于整数embstr编码用于短字符串raw编码用于长字符串。这里有个很经典的坑如果一个字符串原来被存成int编码你对它做APPEND操作导致它变长了编码就会从int转成raw这个转换过程是不可逆的。面试官问这个通常是想看的你能不能聊出内存优化的思路。第三层就是应用场景。PHP里最典型的两个场景是缓存和计数器。计数器用INCR/DECR命令实现INCR本身是原子操作底层就是单线程执行命令天然线程安全。我在项目里用它做过发帖数统计、点赞数统计也做过秒杀场景的库存扣减。还有一个细节用INCR做自增统计时DB里存的值可能不是最终值要考虑定时刷盘和主从一致性的方案。命令层面还有个高频考察点SETNX、SETEX、GETSET这些命令分别解决什么问题以及SETNX和SET NX EX这种组合写法有什么区别。后文讲分布式锁的时候会展开。2.2 Hash类型对象存储的实战价值和ziplist陷阱Hash类型在设计上就是为了存储对象。比如一个用户对象有username、age、email这些字段如果用String存要么序列化成JSON整存整取要么拆成多个Key这两种方式都不理想。序列化成JSON的问题是修改其中一个字段时必须整个对象读出来反序列化、修改字段、再序列化写回高并发下会有性能隐患。拆成多个Key的问题是会产生大量Key且无法保证这些Key的原子性操作。Hash类型原生支持对一个对象的多个字段分别操作HMSET/HGET/HDEL/HLEN修改一个字段不需要动整个对象这在PHP里很适合存用户资料、购物车、文章详情这类结构化数据。但Hash这里有个高频陷阱就是底层编码转换。当Hash对象里的字段数量小于hash-max-ziplist-entries配置值默认128且每个字段的value长度小于hash-max-ziplist-value配置值默认64时Redis会用ziplist压缩列表存储来节省内存一旦超过条件就会转为hashtable哈希表结构。转换后内存占用会明显上升而且这个转换是不可逆的元素删回128个以内也不会变回ziplist。面试时你要能说出为什么Redis要这么做因为小对象用ziplist连续内存存储没有额外指针开销省内存省得很可观但这个机制也意味着生产环境里要关注大对象问题——如果某个Hash的值特别大HGETALL会一次性拿到所有数据造成网络阻塞和内存飙升。针对这种场景我实际项目里的做法是给每个Hash对象预估最大值在配置上做限制同时用HSCAN分批遍历避免HGETALL一把梭。代码上PHP的phpredis扩展用法大概是$redis new Redis(); $redis-connect(127.0.0.1, 6379); $redis-hMset(user:10001, [username 张三, age 28, email zhangsanexample.com]); // 只取部分字段不要用 hGetAll $user $redis-hMGet(user:10001, [username, email]); // 对大Hash做增量遍历 $cursor null; do { $result $redis-hScan(user:10001, $cursor, *, 100); // process $result } while ($cursor 0);2.3 List类型消息队列旧方案与阻塞语义List类型底层在Redis 3.2之前是ziplist或linkedlist3.2之后重构为quicklist也就是把多个ziplist用双向链表串起来兼顾了内存紧凑性和两端操作的效率。面试时能说出来quicklist这个结构说明你不是停留在List就是链表的层面。场景方面List最常见的用途就是简单的消息队列LPUSH消息、BRPOP消费消息。BRPOP是阻塞读取如果队列空就挂起等待超时时间可以设置。在老项目里这确实是最简单的队列实现不需要额外引入RabbitMQ或Kafka这类中间件。但你要知道它的局限性无法支持消息确认机制消费者把消息取走了但处理失败消息就丢了也不支持延迟消息、死信队列这些高级特性。所以在面试中如果你主动讲我们用List做过简单的异步队列但后来数据量上来之后换成了专业MQ这就是一个非常加分的实战信号说明你对技术选型有判断力。List还有一个很特殊的用法是作为时间线/动态流存储。比如用户发了新动态用LPUSH塞进粉丝的feed列表最新动态排前面Limit分页用LRANGE取。这个场景也经常被问到它考察的是对List双向链表语义的理解。2.4 Set与ZSet去重、抽奖和排行榜的底层差异Set的底层是哈希表或者整数集合intset。当元素全是整数且数量不多时用intset存储非常省内存不满足条件就转为hashtable。Set的典型场景是去重、抽奖、点赞、关注关系。比如抽奖功能SADD把参与用户ID塞进集合SRANDMEMBER随机取几个面试时你甚至可以聊到SRANDMEMBER和SPOP的区别——前者不删除元素适合抽奖但用户还能继续参与的场景后者随机弹出并删除适合抽完就剔除的玩法。ZSet是Redis里比较特殊也最有含金量的一种类型。它在Set的基础上增加了score字段每个成员都带一个分数内部用跳跃表skiplist加哈希表实现。ZSet的灵魂在于排序ZADD添加元素时指定scoreZREVRANGE按分数从大到小取数据ZINCRBY给某个成员的分数加分。排行榜功能是ZSet最典型的应用比如文章的实时热度榜每次被阅读就把分数加一然后定期取Top N。面试官问到ZSet底层时很容易追问一个经典问题为什么ZSet用跳跃表而不用平衡树比如红黑树这个问题的答案有几个层次第一跳跃表实现比红黑树简单很多调试和查错成本低第二跳跃表的区间查找性能很不错支持从某个分数段直接遍历第三Redis对有序集合的主要操作就是单点插入删除和区间遍历不需要红黑树那样复杂的旋转平衡操作。我的建议是面试时把这三层全部说出来面试官一般会很满意。// PHP Redis 实现一个简单的阅读排行榜 $redis-zIncrBy(article:rank, 1, article:1001); $redis-zIncrBy(article:rank, 1, article:1002); $top10 $redis-zRevRange(article:rank, 0, 9, true); // 带score返回2.5 关于Redis 7新数据类型别在面试里翻车这里再多说一句。很多人只知道五种经典类型但Redis 5.0之后加入了Stream类型Redis 7.0又对Stream做了增强。Stream定位就是专门的消息队列支持消费者组、消息ACK、Pending Entries List解决的就是List做队列时消息会丢的问题。如果面试官聊到你们用Redis做队列遇到过什么问题你顺着说我们后来看了Stream的消费者组机制它能记录每个消费者的消费位置消费失败的消息还能查看和重新投递这就不只是知道五个类型的水平了。面试时提到Stream要谨慎因为一旦主动抛出来就要接得住后续追问建议是真的有实际使用或深入研究过再说否则容易暴露深度不足。3. 持久化机制RDB和AOF的取舍以及一句话答好的技巧3.1 RDB快照为什么fork子进程不会阻塞主线程Redis持久化是面试必问题而且往往是第一轮技术面就会问到。很多PHP开发平时只管调用很少关心Redis重启后数据从哪来所以这个问题最能筛选出候选人有没有真正运维过Redis。RDB的核心原理是fork一个子进程由子进程把当前内存里的全量数据生成快照写到磁盘。这里有个很容易被问到的点Redis是单线程的生成全量快照为什么不会阻塞主进程答案是fork出来的子进程复制了父进程的内存页表子进程做RDB时读取的是fork那一刻的内存数据快照而主进程继续处理命令。如果主进程在快照期间修改了某些内存页就会触发写时复制Copy On Write机制系统为子进程复制一份修改前的内存页。所以RDB的阻塞风险主要来自fork瞬间的内存页表复制如果Redis实例特别大fork也可能会卡个几十毫秒甚至更久这也是很多大厂要求Redis实例内存上限的原因之一。RDB的触发方式有几种手动执行SAVE或BGSAVE命令、配置文件里设置save规则比如save 900 1表示900秒内至少1次修改就触发一次快照、关闭Redis时自动保存。这里有个小坑SAVE是同步阻塞的生产环境千万不要手动执行SAVEBGSAVE才是后台异步的。有些运维不熟悉Redis直接在线上执行SAVE直接把服务干停了这种情况我见过不止一次。3.2 AOF日志三种刷盘策略和rewrite机制AOF和RDB的思路完全不同。RDB存的是某一时刻的内存数据快照AOF则是把Redis收到的每一条写命令追加到日志文件里Redis重启时重放这些命令来恢复数据。AOF持久化的核心配置就是appendfsync有三个值always表示每条写命令都强制刷盘最安全但性能损耗最大everysec表示每秒刷一次盘兼顾性能和数据安全这也是默认配置no表示交给操作系统决定什么时候刷盘性能最好但可能丢的数据最多。AOF还有一个非常重要的机制叫AOF重写rewrite。因为AOF会随着运行不断变大里面全是历史命令重写就是基于当前内存中的数据重新生成一份最小的AOF文件把冗余的命令去掉。触发重写的条件是auto-aof-rewrite-percentage和auto-aof-rewrite-min-size比如AOF文件比上次重写时增长了100%且大于64MB就触发。另外BGREWRITEAOF命令可以手动触发AOF重写。AOF重写过程中Redis同样使用fork子进程的方式所以和RDB一样需要考虑fork阻塞风险。面试时拿RDB和AOF做对比是个非常经典的问题我给一个可以直接套用的简洁回答结构RDB是内存快照恢复速度快、文件紧凑但快照之间有数据丢失窗口AOF是命令日志数据完整性高最多丢一秒数据但AOF文件大、恢复速度慢。如果追求数据安全用AOF甚至设成appendfsync always如果追求性能和简化运维可以只开RDB生产环境比较稳妥的做法是同时开启Redis默认优先用AOF恢复。3.3 混合持久化Redis 4.0之后的最优解我不知道还有多少人面试时只知道RDB和AOF二选一实际上Redis 4.0之后已经支持混合持久化了。这个选项目前在真实项目中已经是主流做法面试说出去会显得跟进了新东西。混合持久化的逻辑是AOF重写的时候不再只写命令而是先把当前内存数据以RDB格式写到AOF文件开头再记录后续增量命令。这样Redis重启恢复时先加载RDB部分快速恢复大部分数据再重放少量增量命令既比纯AOF恢复快又比纯RDB丢的数据更少。配置项是aof-use-rdb-preamble默认开启。2018年左右我在一个电商项目里调过Redis持久化当时线上用的是纯AOF everysecAOF文件一度膨胀到好几个G恢复一次要很久。后来改成混合持久化之后AOF重写完的文件体积小了很多重启恢复时间从十几分钟降到一分钟内。这个真实经历在面试中非常加分因为你把为什么用混合持久化的前因后果都讲清楚了而不是背一句Redis 4.0之后支持混合持久化。4. 缓存穿透、击穿、雪崩三个连环坑和完整对策4.1 三个缓存故障到底是什么怎么和面试官讲清缓存穿透、击穿、雪崩这几个概念几乎是Redis面试必问中的必问。但很多人把它们混在一起回答的时候逻辑不清。面试官最烦的就是候选人说反正都是缓存出问题了就加个互斥锁。所以这几个概念一定要厘清缓存穿透是指查询一个根本不存在的Key请求直接打到数据库。由于数据库里没有这个数据缓存里自然也不会有于是一个恶意请求用不存在的ID连续刷每次都会打穿Redis直接压到数据库。这种情况用空值缓存和布隆过滤器来解决。缓存击穿是指某一个热点Key在缓存过期的那一瞬间突然有大量并发请求同时访问这个Key结果缓存里没数据请求全部穿透到数据库数据库瞬间压力暴增。它和穿透的区别是穿透查的是不存在的数据击穿查的是真实存在但刚好过期的热点数据。解决方案是互斥锁或逻辑过期。缓存雪崩是指大量Key在同一时间段集体过期或者Redis实例直接宕机导致大量请求同时打到数据库。它和击穿的区别是击穿是一个热点Key过期雪崩是很多人Key一起失效或者Redis整体不可用。解决方案是过期时间加随机值、做多级缓存、做限流降级。4.2 空值缓存、布隆过滤器、互斥锁的细节展开先说缓存穿透的解法。空值缓存听起来特别简单——查不到数据就把空值也放进缓存设置一个短过期时间比如5分钟。但有个细节容易被忽略如果某个恶意请求拿随机不存在的ID疯狂刷空值缓存会写入大量垃圾KeyRedis内存会快速膨胀。所以生产环境一定要给这种空值缓存设计一个短过期总数量限制的组合策略。布隆过滤器是更优雅的解法。把数据库里存在的所有ID提前存入布隆过滤器查询前先判断这个ID是否存在如果过滤器说不存在直接返回根本不会走到缓存层。不过布隆过滤器有几个特点必须说清楚它判断不存在是准确的判断存在是概率性的也就是会有一定的误判率。误判率可以通过位数组大小和哈希函数个数来控制。还有布隆过滤器不支持删除操作所以对删除数据频繁的场景不友好一般配合定期重建或使用布谷鸟过滤器处理。缓存击穿最经典的解法是让重建缓存的操作只允许一个请求去执行其他请求等待。这就是互斥锁方案业界也经常叫缓存重建锁。实现思路是在缓存失效时先尝试获取一个分布式锁拿到锁的请求去数据库拉数据、重建缓存没拿到锁的请求可以短暂sleep后重新读缓存或者直接把旧逻辑过期值返回。这里要特别注意死锁问题锁必须设置过期时间释放锁还要用Lua保证原子性这部分到后面会细讲。互斥锁方案有个小的变种叫逻辑过期思路是Value在缓存里永远不设置物理过期时间而是在Value里塞一个逻辑过期时间戳。每次读取时判断这个时间戳是否过期如果没过期直接返回如果过期了先尝试拿分布式锁去后台重建缓存同时把旧数据返回给调用方。这种方案的好处是热点Key根本没有空窗期——其他请求始终能拿到旧数据缺点是旧数据会短暂返回给用户对一致性要求高的场景不适合。4.3 缓存雪崩的预防和降级别只说加随机值缓存雪崩最常见的触发原因有两个一是批量Key同时过期二是Redis实例宕机。针对批量Key同时过期最普及的做法是在设置缓存过期时间时加一个随机值把过期时间打散。比如原本都是10分钟过期现在每个Key设置成600秒加上一个随机秒数。我在项目里的写法是$ttl 600 random_int(0, 60); $redis-setex($cacheKey, $ttl, $value);但面试官如果只听到加随机值往往还会继续问那如果Redis真的宕机了怎么办这时候你至少得有以下几个层次的对策首先要有高可用架构Redis主从加哨兵或者直接用集群方案这是最基础的兜底其次应用层要做降级策略数据库连接池和调用方的超时时间、重试次数都需要限流防止数据库被打爆再有就是多级缓存本地缓存如PHP的APCu作为Redis之上的一层缓存即使Redis不可用部分请求还能靠本地缓存顶住。缓存这块面试还有个大热点是缓存与数据库一致性。经典的Cache Aside Pattern旁路缓存模式你应该能脱口而出读的时候先读缓存缓存没有就读数据库再写缓存写的时候先更新数据库再删除缓存。虽然业界对到底先更DB还是先删缓存有争议但比较公认的结论是Cache Aside模式下选择先更新数据库再删除缓存比先删缓存再更新数据库更稳妥。具体原因是因为先删缓存后在重写缓存之前如果有并发读进来不仅会压垮数据库还可能导致数据不一致。更进一步的延迟双删方案是用在要求比较严格的场景先删缓存、更新DB、等几百毫秒再删一次缓存就是为了解除并发窗口期的脏数据。5. 分布式锁从最原始SETNX到Redisson思路逐层展开5.1 SETNX加锁没问题释放锁才是真正的坑点分布式锁是Redis面试里的高阶考点。PHP项目场景最常见的锁是秒杀、防重复提交、任务调度防止多实例重复执行。比如同一个定时任务部署在两台机器上到点后两台机器同时执行如果没有分布式锁就会重复处理。用Redis做分布式锁的核心思想就是多个进程争抢同一个Key谁设置成功谁就拿到锁。最原始的实现方法是SETNX命令也就是如果Key不存在则设置。但网上很多老文章里的写法有个大坑先SETNX抢锁抢到后再用EXPIRE设置过期时间。这两个操作不是原子的如果进程在SETNX之后、EXPIRE之前崩溃了这个Key就永远不删除锁就永久死锁了。所以正确的姿势是用Redis 2.6.12之后提供的扩展SET命令一步到位$redis new Redis(); $redis-connect(127.0.0.1, 6379); $lockKey lock:order:payment:10001; $requestId uniqid(, true); // 用唯一标识来区分是不是自己的锁 $lockAcquired $redis-set($lockKey, $requestId, [NX, EX 30]); if ($lockAcquired) { try { // 执行业务逻辑 } finally { // 释放锁必须检查value是否是自己再用Lua删除 $lua LUA if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end LUA; $redis-eval($lua, [$lockKey, $requestId], 1); } }这里两个细节是面试问得最多的。第一个是为什么释放锁要用Lua脚本因为判断value是否是自己的和删除Key是两个操作如果拆开就有可能在判断完之后、删除之前锁过期被别的请求抢到了然后你把自己的和解锁操作把别人的锁删了。Lua脚本保证了这两个操作的原子性。第二个细节是为什么要给value设置一个唯一标识因为如果不判断value一个请求的锁过期后另一个请求抢到了锁第一个请求执行完之前的DEL会把第二个请求的锁意外删除导致锁完全失效。用唯一标识做到谁加的锁谁释放是分布式锁的底线。5.2 锁自动续期Redisson看门狗的底层逻辑上面的加锁方案有一个隐患如果业务执行时间超过了锁的过期时间比如锁设了30秒但业务代码执行了60秒30秒时锁就自动释放了其他请求就能进来了分布式锁就形同虚设。业界最成熟的解法是Redisson的看门狗机制。Redisson加锁成功后会启动一个后台定时任务每隔锁过期时间的三分之一去检查一次锁是否还在如果还在就自动把锁的过期时间续期。比如锁默认30秒过期看门狗每10秒续期一次把锁的生命周期延长到30秒。这样只要持有锁的进程还活着锁就永远不会提前释放进程一旦宕机看门狗线程也会跟着停掉锁到了过期时间自然释放不会死锁。PHP生态里虽然没有官方Redisson这么完整的客户端但你可以自己实现一个简化版看门狗用定时任务或者后台脚本定期执行EXPIRE命令给锁续期比如每10秒执行一次EXPIRE把锁的过期时间重新设置为30秒。不过自己做续期方案要小心最简单可靠的还是评估业务最大执行时间把过期时间设得足够长或者业务逻辑里手动保证长任务不要在线程里同步执行。面试的时候主动提到看门狗机制能说明你对分布式锁的长期可用性有思考这在候选人里算是加分项。5.3 Redlock和主从切换丢锁这个争议题聊到分布式锁Nine成以上面试官会追一个问题如果Redis是主从架构主节点宕机了怎么办锁存在主节点上主从切换后从节点没有同步到这把锁其他请求就能拿到锁了锁就失效了。这里牵扯出当前业界对Redis分布式锁最经典的质疑之一。Redlock算法的基本思路是不依赖单台Redis节点而是向多个相互独立的Redis节点依次尝试加锁只有超过半数节点N/21加锁成功才算真正拿到锁。这样即使某个节点发生故障只要大部分节点还活着锁就不会丢失。这个算法最早是Redis作者antirez提出来的但后来分布式领域的大佬Martin Kleppmann专门写文章质疑过它认为Redlock在遇到时钟跳跃、GC暂停这类情况时依然无法保证绝对安全。这个争论到今天都没有统一结论。面试时被问到这个问题正确的姿态不是表态Redlock就一定行或一定不行而是把两边的观点讲清楚如果业务是资金支付这类强一致场景需要考虑更可靠的分布式协调组件比如ZooKeeper或etcd如果只是普通的防重复提交、任务调度单机Redis加锁加上合理的过期时间和续期已经够用了。这个回答展现的是工程判断力比死记一个算法要值钱得多。6. 主从复制与哨兵以及Redis集群的基础认知6.1 为什么Redis要做主从复制全量同步和部分同步要分清单机Redis一旦宕机整个缓存都无法服务即使有RDB/AOF持久化重启恢复数据也需要时间这期间服务还是不可用。所以生产环境Redis必须做高可用第一步就是主从复制一个主节点负责写多个从节点负责读主节点数据变更后同步到从节点。PHP项目里应用层读多写少主从架构既能提高可用性又能分担读压力。主从复制的全量同步过程大概是从节点发送PSYNC命令主节点执行BGSAVE生成RDB快照把快照发给从节点从节点清空自己的旧数据并加载快照。这个过程中主节点产生的新写命令会存在复制缓冲区里快照同步完之后再发给从节点执行。这部分面试官非常爱问为什么从节点第一次连接主节点时主节点要执行BGSAVE答案就是因为从节点没有任何数据必须先把全量数据拷过去。除了全量同步还有部分同步。如果主从之间的网络断开重连Redis不会傻乎乎地重新全量复制而是通过复制积压缓冲区repl_backlog来决定能不能只把断线期间的增量命令同步过去。如果从节点的偏移量还在缓冲区里就做增量同步如果缓冲区已经覆盖不了就只能全量同步。这个机制平时不接触但面试聊到主从复制时说出来能明显体现你对同步机制的理解比一般人深。6.2 哨兵和Cluster高可用和水平扩展的正确答案有了主从复制如果主节点宕机了怎么办写操作就不可用了。哨兵Sentinel解决的就是这个问题哨兵节点会持续监控主节点和从节点的健康状态一旦检测到主节点主观下线并且经过多数哨兵节点确认后会执行故障转移——从从节点里选举一个新的主节点并通知其他从节点和客户端更新主节点地址。哨兵通常要部署奇数个节点至少3个因为需要超过半数确认才能判定主节点故障并执行自动切换。如果只有两个哨兵节点其中一个宕机剩下那个就没法形成多数派无法完成故障转移。这个设计逻辑和分布式系统中的多数派共识是一脉相承的。主从加哨兵解决的是可用性问题但解决不了容量问题单台Redis能承载的内存毕竟有限读写性能也有上限。真正要水平扩展需要Redis Cluster集群模式。Cluster采用数据分片的思路把整个键空间划分成16384个哈希槽每个节点负责一部分槽通过一致性哈希的方式把Key映射到具体节点。当一个节点宕机它负责的槽会被其他节点接管高可用和扩展性都得到了保证。面试的时候我建议你把主从、哨兵、Cluster的区别总结成一句话主从复制解决的是数据冗余和读写分离哨兵解决的是自动故障转移Cluster解决的是数据分片和水平扩容。有这个大的框架感后面不管怎么追问细节都不容易乱。7. 一份能落地的Redis面试复盘清单7.1 按考察层级梳理从会用升级到会设计准备Redis面试题最忌讳的就是零散地刷各种面经。我自己复盘过几十场技术面试Redis相关的问题通常会遵循一个由浅入深的层级结构你按这个结构去准备会高效得多。第一层是能说出是什么Redis是什么、为什么快、单线程模型、五种基础数据类型、常用命令、默认端口6379这些东西很基础但必须脱口而出。第二层是能说出用过什么你在项目里用Redis做过哪些事比如缓存热点商品、Session共享、排行榜、分布式锁、异步队列。每个场景你都能说出Key怎么设计、数据怎么存取、为什么选Redis不选其他方案。第三层是能说出原理为什么Redis快除了单线程还有IO多路复用和内存存储为什么ZSet用跳表RDB和AOF各自的工作流程缓存过期删除策略怎么工作。这些原理不要求背诵源码但核心机制要能讲清楚。第四层是能说出坑和解决方案缓存穿透、击穿、雪崩怎么防主从延迟怎么处理big key和热Key怎么发现怎么治理分布式锁的安全边界在哪里。这层最能拉开差距能把第四层讲清楚的人基本可以认定是真正有生产环境经验的。7.2 一组可以拿来练手的高频追问题最后分享一套我在模拟面试时常用的追问链建议你对着自测在脑子里组织语言最好能出声讲一遍看能不能每层都接得住问题一Redis有哪些数据类型你们项目里各用在哪里追问ZSet底层是什么数据结构为什么用跳表不用红黑树问题二你们用Redis做缓存怎么保证缓存和数据库的一致性追问为什么先更新数据库再删缓存如果删缓存失败怎么办问题三万一Redis缓存全挂了服务会不会雪崩追问主从切换的哨兵模式最少要几个节点为什么问题四怎么用Redis实现分布式锁追问锁过期时间怎么定如果业务还没执行完锁就过期了怎么办问题五Redis持久化是怎么做的追问AOF重写会不会阻塞主进程为什么这五条追问链基本覆盖了PHP中级开发到高级开发对Redis的要求。你能不看资料把每条链顺下来面试就稳了大半。我见过不少候选人前面基础题答得不错一到你们项目里怎么用的就露怯问题通常就出在没把技术点和自己的真实业务建立连接。所以最后再强调一句准备Redis面试题最好的方式不是背题而是打开自己的项目代码找到每一处Redis调用的位置问自己一句这里为什么用它、换成别的行不行、出问题了怎么办把这三个问题想明白比刷一百道面经都有用。
返回列表