缓存是Redis的主场但很多人连锁都拿不稳做后端开发的这几年Redis基本是每个项目里的标配要么当缓存扛读流量要么当分布式锁保互斥。但说实话见过太多人把Redis当成“高级一点的HashMap”缓存写进去就完事分布式锁就一个SETNX完事。结果线上出问题的时候连缓存穿透和缓存击穿都分不清锁失效导致超卖的惨案也不是没见过。这个系列第四篇我把缓存和分布式锁这两块放在一起聊原因是它们在实际业务里经常纠缠在一起——缓存扛流量锁扛并发组合得好系统很稳组合不好就是一个接一个的事故。这篇不讲Redis基础命令直接从缓存更新的标准姿势讲起然后逐个拆穿透、击穿、雪崩这三个高频故障再花大篇幅把分布式锁从SETNX到Redisson的完整进化路径捋一遍最后把我这些年踩过的坑和排查经验一次交代清楚。不管你是刚接触Redis的初级开发还是已经写了两年业务代码但没系统整理过缓存方案的这篇都能让你少走弯路。1. 缓存更新策略先动数据库还是先动缓存顺序错一步就是脏数据1.1 四种主流缓存模式先花一分钟说清在动手写代码之前得先知道缓存和数据库之间有哪些协作方式。业界常见的四种模式Cache Aside旁路缓存、Read Through读穿透、Write Through写穿透、Write Behind异步写回。后三种对应用层来说基本透明由缓存组件自己负责和数据库同步很多中间件就是这么实现的。比如Read Through模式下应用读缓存查不到时由缓存组件去加载数据库并回填缓存应用完全不感知。Write Behind更极端写操作先落到缓存就直接返回成功后台异步批量刷入数据库性能最高但数据丢失风险也最大。实际业务里99%的团队用的都是Cache Aside。原因很简单实现成本最低不依赖任何中间件代码里自己控制读写顺序就行。而且可控性最强出问题能明确知道是哪一步代码写得不对。所以这篇主要讲Cache Aside也是面试里问得最细的模式。1.2 Cache Aside的更新顺序困境先删缓存还是先更新数据库Cache Aside的逻辑分两条线读请求先读缓存命中直接返回未命中读数据库回填缓存。写请求更新数据库然后删除缓存注意不是更新缓存。为什么写请求是“删缓存”而不是“更新缓存”我见过不少新手写代码时把缓存当成数据库的影子更新数据库之后立刻把新值SET进去。表面看没问题但这里有坑如果缓存不是单纯存数据库的原始值而是经过计算的聚合数据那“更新缓存”意味着业务逻辑要重复算一遍更麻烦的是并发写场景A请求先更新DBB请求后更新DB但B更新缓存可能比A早最终就会缓存里是旧值、数据库是新值。删缓存就简单了下次读请求自然会拉最新的。删缓存也讲究顺序。常见方案是两个先更新数据库再删除缓存。先删除缓存再更新数据库。方案二有隐患。你看这个时序线程A删掉缓存还没来得及更新数据库线程B这时候读缓存查不到于是去读数据库——读到的还是旧数据然后B把旧数据回填到缓存。等A把数据库更新完缓存里躺着的依然是旧值。这种脏数据能存活到下一次缓存过期问题很严重。方案一同样有极端时序线程A更新数据库还没删缓存线程B此时读缓存命中拿到旧值。但这有个前提——缓存里本来就存着旧数据、且没有被删过。也就是请求B在读缓存的那一刻恰好卡在A更新完DB但还没删缓存的窗口期。这个窗口非常短而且最终A会删掉旧缓存后续请求读到的是新值。结论很清楚先更新数据库再删除缓存是相对更安全的选择。这也是业界最主流的落地方案。1.3 删缓存失败怎么办重试机制是必须的“先更新DB再删缓存”方案有个现实问题——删缓存这个动作可能失败。缓存删不掉旧数据就会一直躺在那里。我见过最草率的处理方式是调一下del方法不判断返回值也不做任何兜底。后来数据不一致了排查半天。正确做法是给删缓存加重试。最简单的方案是引入一个本地消息表或者用现成的MQ更新数据库成功后写一条“删缓存”的消息由消费者去删删失败就按照MQ的重试机制反复消费直到成功。如果公司没有MQ也可以用Spring Retry之类的组件做本地重试几毫秒后重试三五次大多数场景够用。另外还有一个通用兜底给缓存设置合理的过期时间。所有缓存数据都应该有TTL哪怕业务上希望长期保存也要设个很长的过期时间比如7天或30天。过期时间的存在意味着哪怕删缓存彻底失败了最坏情况也只是数据在TTL内不一致不会永久错下去。这是我做缓存设计的第一原则。2. 缓存三大经典故障穿透、击穿、雪崩一次性说透2.1 缓存穿透请求打在空数据上缓存根本帮不上忙缓存穿透描述的是请求查询的数据在数据库里根本不存在。缓存没存数据库也没有每次请求都穿过缓存直捣数据库。如果有人恶意构造大量不存在的ID来请求数据库会被瞬间打爆。我做过一个真实案例。某活动页面有个查询用户优惠券明细的接口前端会传一个userId参数。正常流程是先查缓存再查数据库但如果攻击者把userId换成随机的UUID数据库里永远查不到缓存里也永远没有每次请求都会穿透到数据库。某天夜里这个接口的数据库慢查询突然飙升排查后发现就是被人扫接口了。解决穿透有两大主流方案方案一空值缓存。查询结果为空时也往缓存里写一个空值比如特殊标识NULLTTL设置短一点比如60秒。同一ID在60秒内的重复请求直接就命中空值缓存了。代价是每个不存在的ID都会占用一份缓存空间攻击者如果持续用不同的随机ID空值缓存会越积越多。所以空值缓存不适合恶意性强的场景只适合误触发场景。方案二布隆过滤器。用一个小巧的位数组把所有合法ID存进去请求来了先过布隆过滤器ID不在里面就直接返回不存在绝不给缓存和数据库增加压力。布隆过滤器有个要命的特性——有误判率。它只会说不存在的一定不存在但说存在的未必真的存在。就是过滤器里的位可能被其他元素的哈希碰巧置1导致一个不存在的ID被误判为存在漏到数据库。不过没关系漏过几个不存在的请求到数据库数据库能扛住总比所有请求都穿透过来强。参数选择上布隆过滤器要关注两个数预估数据量n和误判率p。公式比较复杂实操中一般直接用Guava的BloomFilter把n和p传进去它会自动算出位数组大小和哈希次数。我惯用误判率0.011%数据量按业务高峰期的两倍预估这样既不会太占内存误判率也基本无感。2.2 缓存击穿热点Key过期的瞬间并发全部涌入数据库击穿和穿透一字之差本质完全不同。击穿针对的是单个热点Key——比如某商品的详情页、某个明星的热搜数据。平时这个Key存在缓存里扛着每秒几千次的并发。突然它过期了那一刻所有请求都发现缓存为空然后集体涌向数据库。击穿的破坏力有多大这么说吧一个数据库可能平时每秒处理几百个查询热点Key过期的那一秒压力可能直接飙到每秒几千数据库CPU和连接数瞬间打满整个服务跟着雪崩。解决击穿有两个主流方案方案一互斥锁重建缓存。当缓存未命中时不是所有请求都去查数据库而是让请求先尝试获取一个分布式锁。拿到锁的请求去查数据库、重建缓存拿不到锁的请求先短暂等待比如自旋10毫秒再回来读取缓存。这样数据库在同一时刻只有一个请求真正进来。这个方案效果好但缺点是会让部分请求的延迟升高因为拿不到锁的请求需要多等一会。方案二逻辑过期。给缓存里的value额外存一个业务过期时间字段。读请求发现逻辑过期后先返回旧数据页面展示的还是老数据同时异步发起一个线程去查数据库、重建缓存。这种方案用户体验最好因为用户永远能立刻拿到响应只是数据可能旧个几百毫秒。代价是逻辑稍复杂而且所有的读请求都要做逻辑过期判断。我在项目里两种都用过。对实时性要求高的数据用互斥锁方案对展示类数据详情页、列表页用逻辑过期方案更多。2.3 缓存雪崩大面积Key同时失效缓存层直接瘫痪雪崩比击穿更猛。击穿是单个热点Key失效雪崩是大量Key在同一时间段内集体失效——典型的场景就是把过期时间都设置成同一个值比如全表缓存24小时凌晨零点集体过期零点一过所有请求都砸向数据库。雪崩往往不是设计出来的“巧合”而是代码里的坏习惯。很多开发图省事写set(key, value, 86400)所有数据过期时间写死成一天结果固定零点那个时间点全量失效。应对雪崩有三板斧第一板斧过期时间加随机值。某个商品的缓存本应设置10分钟过期实际设置成10分钟 随机0到5分钟。这样一个商品集合里的所有Key不会同时过期过期时间被均匀打散。这是代价最低的解法代码里几行就能搞定。第二板斧多级缓存。进程内再用一个本地缓存比如Caffeine或Guava Cache扛住第一层Redis扛第二层数据库扛第三层。就算Redis的Key全挂了本地缓存还能顶一段时间。这个方案还顺便把Redis的网络开销也省了。第三板斧限流降级。在数据库前面加一层限流保护比如Sentinel或Hystrix当流量超过某个阈值时直接返回兜底数据或者失败提示确保数据库不被干趴。这套方案属于最后的防线不解决雪崩本身但能保证雪崩时服务不至于全挂。2.4 一次压测暴露的真实雪崩排查过程上个月给某内部项目做压测200并发跑一个列表页接口持续10分钟。前8分钟接口TP99稳定在80毫秒到了第9分钟突然飙到2秒以上数据库连接池打满了。当时第一反应是看Redis监控结果发现缓存命中率从99%掉到了40%。再看Key的过期时间果然所有列表缓存全部写死成1800秒压测从同一时刻开始跑1800秒后又同一时刻集体过期直接雪崩。修复方式很简单过期时间改成1800 ThreadLocalRandom.current().nextInt(300)也就是半小时加0到5分钟的随机偏移。改完重新压测TP99稳定在100毫秒以内雪崩消失。这个案例提示两点一是给每个Key设置过期时间时都要问一句“这个值会不会和别的Key撞车”二是压测的时候多跑一会儿很多并发问题不会在刚启动时就暴露而是卡在某个关键时间点突然爆出来。3. 缓存一致性删除策略的坑延迟双删与旁路更新3.1 延迟双删给缓存和数据库的一致性加层保险前面讲了“先更新DB再删缓存”是推荐的方案但严格来说它也不是100%一致。看这个时序线程A更新了数据库商品价格变为100。线程A还没来得及删缓存。线程B发起读请求缓存命中返回旧价格50。线程A删除缓存。这个窗口期虽然极短但理论上存在而且在高并发下窗口期可能被放大。因为从更新DB到执行del之间涉及网络、线程调度完全可能出现一个读请求恰好卡在中间。延迟双删就是针对这个窗口的修补更新数据库后先删一次缓存隔一段时间比如500毫秒再删一次。第一次删除清掉主线缓存第二次删除清掉窗口期内可能被并发线程回填的旧数据。延迟双删的时间参数怎么定核心原则是第二次删除的时间要大于一次完整的读写时间。一般经验值是500毫秒到1秒。太长会拖慢写请求因为写流程要阻塞等待太短又可能在并发线程还在执行时就把第二次删除处理完没起到兜底作用。我自己一般取800毫秒作为默认值但也会根据业务实际的读耗时调整——如果读链路很慢比如涉及大字段反序列化就适当放大。延迟双删不是完美方案它只是让脏数据存活时间缩短到几秒钟以内做不到严格一致。如果要强一致得看后面的binlog方案。3.2 Binlog订阅把删缓存从代码里彻底剥离比延迟双删更省心的方案是监听数据库Binlog。利用现成的中间件比如Canal把自己伪装成一个MySQL“从库”消费Binlog变更事件在事件里解析出变更的表的ID然后去删对应缓存。业务代码里完全不用写删缓存的逻辑只管更新数据库就行。这个方案最大的好处是解耦还顺便解决了写流程里删缓存失败的重试问题——Binlog消费端天然有重试机制消费失败会一直重试直到成功。代价是架构上多了一套组件运维成本上去了。适合对缓存一致性要求高、团队又有运维能力的项目。我经历过一个项目用了Canal之后之前所有“删缓存失败导致数据不一致”的工单直接清零。写业务代码的同学再也不用操心缓存了。3.3 版本号方案用ID加版本号做到缓存和数据严格对齐上面所有防脏数据的方案都是“尽力而为”但有业务场景严格要求缓存里的数据不能是旧的——比如库存、余额这类的强一致数据。版本号方案的思路数据库里每个数据加一个version字段每次更新时version 1。缓存存的是value version。更新流程变为更新数据库version增加后直接更新缓存里的value和version读请求读取缓存后如果发现缓存里的version比数据库的最新版本小就说明缓存是旧数据需要重新拉取。这个方案的严格性取决于更新缓存的时机是否和数据变更在同一个事务或连接里实操时往往有两种变体。一种是在DB更新事务里同时发消息通知缓存更新因为消息和事务捆绑可以做到不丢消息另一种是干脆不做缓存预更新而是每次读时校验版本。前者对写请求延迟有影响后者对读请求延迟有影响。我见过的大多数场景用延迟双删或者binlog订阅就够了版本号只有库存扣减等强一致场景才用得上。4. 分布式锁从SETNX到Redisson的完整进化路线4.1 为什么单机的synchronized不够用了单机时代多个线程同时操作同一个资源用synchronized或者ReentrantLock就能搞定因为JVM内部的锁只有自己进程能看到。但系统一拆成多实例比如部署了三个实例做负载均衡同一个用户请求可能被分发到不同的实例上每个实例各自持有自己的JVM锁三个线程分别在三个进程里同时进入临界区互斥完全失效。分布式锁的思路是找所有进程都认的同一个地方来协调互斥。Redis因为高性能、高可用成了实现分布式锁的首选存储。4.2 第一版锁一条命令搞定基础互斥最基础的实现一条命令SET lock:order:1001 ownerId 20 NX EX 30参数解读NX只有键不存在时才能设置成功这是锁的互斥保证。EX 30锁的自动过期时间防止持有锁的线程挂了之后锁永远不释放。value一个标记所有者的唯一ID一般用UUID或者请求ID。加锁成功了就返回OK别的线程再来加锁就会返回失败可以自旋重试等待。上面这条命令最大的进步是解决了加锁和过期时间的原子性问题。老手可能见过旧写法SETNX key value和EXPIRE key 30分开执行如果SETNX成功之后、EXPIRE执行之前应用挂了锁就永远不会过期其他线程就被永久阻塞。把NX和EX放进同一条命令里这条命令本身就是原子性的这个坑直接不需要踩了。4.3 释放锁为什么不能直接DEL释放锁这个动作新手最容易写错。看这段伪代码if redis.get(lock:order:1001) ownerId: redis.delete(lock:order:1001)先判断锁是不是自己的再删除。逻辑上没毛病但存在一个竞态窗口判断完成之后、删除操作执行之前锁超时到期了被另一个线程B抢到此时A去执行删除把B的锁给删了。这就造成了锁失效B和C同时进入临界区。正确做法是把判断和删除合成一个原子的Lua脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end一次Lua脚本执行Redis保证整个脚本原子执行中间不会插入其他命令。这样就彻底杜绝了误删别人锁的问题。Redis官方文档里推荐的就是这个方案。4.4 实战中的得力干将Redisson框架用原生Redis命令实现分布式锁到Lua脚本这一步已经能用了。但再往下抠细节会发现还很麻烦锁的自动续期业务执行超过了锁的过期时间怎么办锁自动释放并发进来了业务还没跑完。锁的可重入业务里递归调用或者一个锁套另一个锁怎么支持同线程重复加锁等待锁的人如何高效排队是不是只能循环轮询自旋锁在Redis主从切换场景下如何保证不丢失这些恰恰是Redisson这个框架已经解决好的问题。Redisson的分布式锁RLock基于Lua脚本实现底层做了大量封装。它的看门狗机制值得单独拿出来讲获取锁时如果没指定leaseTimeRedisson会启动一个后台定时任务每10秒执行一次把锁的有效期重置回30秒直到业务执行完毕主动释放。这样就不用担心业务执行超过锁的过期时间锁被随手放开。如果持有锁的服务宕机看门狗也随之中止了锁会在30秒后自动过期依然不会死锁。代码层面用Redisson加锁大概是这样的RLock lock redissonClient.getLock(lock:order: orderId); try { if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 业务逻辑 } else { throw new RuntimeException(获取订单锁失败); } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }注意tryLock的三个参数等待时间3秒拿不到锁就等3秒租约时间10秒业务执行超过10秒锁自动释放单位秒。如果业务可能跑得很久可以不传租约时间让看门狗来接管自动续期。4.5 主从切换与RedLock的争议Redisson解决了锁续期问题但依然有一个理论上无法回避的缺陷——主从复制带来的锁丢失。Redis的高可用架构是“一主多从”主节点写入后异步复制到从节点。线程A在主节点上成功设置了锁主节点还没来得及把这条写命令复制给从节点主节点就挂了。哨兵把某个从节点提升为主节点这个新主节点上根本没有A的锁数据。此时线程B去加锁成功获得锁A和B同时进入了临界区分布式锁形同虚设。所谓的RedLock方案试图解决这个问题部署多个彼此独立的Redis节点通常是5个加锁时向所有节点依次加锁超过半数节点比如3个成功才算加锁成功。RedLock的作者认为这样即使在个别节点故障的情况下锁依然能保证互斥。但RedLock在业界争议很大。很多人指出它的正确性依赖太多假设比如各节点间时钟必须是同步的、网络延迟要可控等而且锁的有效期也会因多个节点的耗时而被压缩。连最初提出分布式系统里一致性问题的某位大神都公开diss过RedLock表示它并不是一个安全的选择。我的个人观点是对于绝大多数公司的业务而言单实例Redis加Redisson锁已经完全够用。你是做一个订单服务、秒杀活动还不是做跨机房级别的强一致系统。RedLock引入的复杂度远超收益而且数据库的行锁本身就能做兜底——即使Redis锁出了问题数据库层面也能保证最终一致。如果真到了必须使用RedLock的地步只能说明你的业务场景已经发生在“分布式锁失效会引发灾难性事故且不能依赖数据库兜底”的极端场景那这种场景应该优先考虑更可靠的选型。5. 分布式锁实战避坑清单这些都是拿事故换来的经验5.1 锁的过期时间设多长合适过期时间设短了业务还没跑完锁就没了并发就进来了设长了如果持有锁的实例挂了其他线程会一直拿不到锁等到锁过期才能恢复。我的习惯是先估算业务本身的最坏执行时间再乘以2或者加一个缓冲值。比如订单支付的预扣库存逻辑一般50毫秒内能完成锁过期时间就设1秒。如果用了Redisson并开启看门狗这个值可以设得保守一点因为看门狗会自动续期过期的风险交给框架处理。但注意看门狗不是万能的。如果业务里做了长时间阻塞比如调外部接口设置了5秒超时而锁的租约时间只有3秒看门狗还没来得及续期锁就放了。所以业务代码要把锁持有时长控制在合理范围把外部调用移到锁外锁里面只放最核心的互斥逻辑。5.2 锁的粒度同一个锁锁住所有商品是灾难一个比较隐蔽的坑。做库存操作时很多人图省事用一个全局的lock:inventory锁住所有商品的库存操作。结果就是A商品和B商品本来毫无关系但当A商品被某个请求锁住时B商品的库存操作也只能排队等待。并发上来之后锁变成性能瓶颈。推荐的做法是锁的粒度尽量细化。用业务主键来设计锁名称比如lock:inventory:{skuId}、lock:order:{orderId}、lock:user:{userId}。这样每个SKU有独立的锁互不干扰并发能力能提升好几个量级。5.3 重入问题同一个线程加同一个锁两次会死锁可重入锁的意思是同一个线程可以多次获取同一把锁持有锁的线程再次进入锁代码时不会被自己阻塞。Java里的ReentrantLock天然支持可重入synchronized也支持。但用Redis实现锁时如果只靠SET NX完全无法区分“同一个线程再次加锁”和“不同线程加锁”。比如一个递归方法里外层持锁后调内层方法内层也加同一把锁就会把自己阻塞住形成死锁。Redisson的RLock实现了可重入机制它内部用Hash结构记录加锁次数同线程重复加锁只增加计数不新建锁。如果项目没有用Redisson而是自己造了锁一定要把重入这个特性设计进来否则很容易在复杂的业务调用链里埋雷。5.4 释放锁的finally不是可选项是必须项见过的另一个高频BUG拿到锁之后业务代码在中间抛了异常直接返回unlock()永远没执行。锁只能等着超时自动释放期间所有线程排队等待接口延迟飙升。正确写法永远是这样的结构if (lock.tryLock(...)) { try { // 业务逻辑 } finally { lock.unlock(); } }finally里释放锁是不需要思考的固定写法。另外在释放锁之前最好判断一下isHeldByCurrentThread()防止自己持有的锁已经被看门狗误释放极端情况再去释放别人的锁。6. 缓存与锁的运维排查监控指标、大Key和慢查询6.1 上线前必须关心的三个监控指标一说到Redis监控很多团队就是看下内存占用和命中率就完事我觉得不够。缓存系统和分布式锁的线上健康度至少要盯三个指标缓存命中率正常业务能到95%以上如果跌破85%就要警惕。要么缓存设置不合理要么大量Key在集中失效雪崩前兆。慢查询日志Redis的SLOWLOG命令能查慢命令长度超过10毫秒的命令值得关注。最常见导致慢查询的就是KEYS *扫描大Key以及HGETALL拉取大Hash。锁等待时间如果分布式锁的等待时间平均值持续上升说明临界区并发冲突变严重可能需要优化锁粒度或者业务逻辑。6.2 大Key排查一个Hash装一万条数据是隐患大Key是Redis性能杀手。一个Hash或ZSet装了上万条数据当某个请求对这个Key做全量读取时一次命令就可能阻塞Redis事件循环几十毫秒期间所有其他Key的读写都排队等着等于整个Redis被一个大Key卡了喉咙。Redis 4.0之后可以用MEMORY USAGE key查看单个Key的内存占用。建议在测试环境跑一个定期脚本扫描并报告内存占用超过1MB的Key然后逐个人工确认为什么会这么大、能不能拆分。我做过一次大Key治理把某个列表缓存从一个大Hash拆成多个Key分页存储接口延迟直接降了一个数量级。6.3 热Key识别同一个Key被高频访问不是好事热Key指的是某一段时间的访问量特别集中比如秒杀商品、热点新闻。单个Key的QPS如果到了几千甚至上万就算Redis本身扛得住也会导致单实例网络带宽打满、CPU飙升。最简单有效的处理是加本地缓存。在应用进程里加一层Caffeine缓存将热点Key缓存到本地访问本地内存肯定比访问网络快。加完本地缓存之后Redis侧的压力自然降下来。另一个思路是给热Key做副本同一个数据存多个Key比如sku:1001:0、sku:1001:1请求随机路由到不同Key上把压力分摊到多个Redis分片。6.4 锁风暴锁竞争导致Redis QPS暴涨的实证有人把Redis当成锁服务器每个业务方法都在加锁解锁Redis的QPS被锁操作刷得很高。某次我优化一个服务看监控发现Redis总QPS是2万其中1.6万都是锁操作——SET NX和DEL占了80%。优化办法是分析哪些锁可以去掉读多写少场景根本不需要锁直接读缓存就行。可以接受最终一致的场景用一个异步队列串行处理写操作天然免锁。真正需要锁的只有“必须实时互斥的写操作”这种场景才保留Redis锁。最后锁操作降到了3000 QPSRedis负载直接腰斩。7. 最后的经验总结与扩展思考写了这么多最后分享几个我个人的体会。第一缓存方案的复杂度永远适配业务场景就好。小团队刚起步用Cache Aside加过期时间就能跑得很稳到了并发量大了再考虑逻辑过期、多级缓存、binlog订阅。不要一上来就全副武装过度设计会让你花大量时间维护一套复杂系统而不是迭代业务。第二延迟双删这类方案本质是“用时间换一致”。它永远不可能做到强一致只能缩短脏数据窗口。真要做到强一致请回到数据库层面去加行锁或者用事务别指望Redis帮你扛所有事。第三分布式锁是最后的手段不是第一选择。能用数据库唯一索引解决的用数据库能用消息队列串行化的用MQ能用乐观锁版本号的用版本号。只有这些方案都表达不了业务约束时才轮到Redis分布式锁出场。锁的维护成本很高——它涉及续期、可重入、误删、主从切换每一个都是事故点。第四监控和演练是长期投入。我见过太多团队上线前不压测、不跑缓存雪崩演练等到线上真出了事才手忙脚乱。强烈建议每个季度做一次缓存故障演练人为把Redis停了、把所有缓存Key提前过期、模拟热点Key秒杀看看系统会不会倒。花一天时间演练可能帮你省下来一个周末的紧急修复时间。Redis的缓存和分布式锁看着都是基础功但一旦用不好就是线上事故的温床。这个系列的下一篇我打算继续聊聊Redis在高并发场景下的其他实践比如延迟队列、限流、或者更深入的主从复制原理。如果你在实际项目里也遇到过缓存穿透、锁失效之类的奇葩问题欢迎在评论区交流我看到了会回复。 SEO 优化官网定制响应式建站教育培训建站