01
案发回顾
Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-172.16.4.1:20051, Pool Size: 2100 (active: 2100, core: 0, max: 2100, largest: 2100), Task: 874734 (completed: 872634), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false).
... 24 more
导致请求链路阻塞的根本原因是大量并发请求到达数据库,导致其连接被耗尽:
java.lang.RuntimeException: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 45003, active 50
... 77 more
02
常见的缓存使用模型(选读)
常见缓存使用模型:双读双写、异步更新和串联模式。
双读双写

对于读操作:先读缓存,如果缓存不存在,则再读数据库,读取数据库后再回写到缓存;
异步更新
异步更新的示意图如下:

串联模式
串联模式示意图如下:

03
缓存击穿(缓存并发)

应用缓存设计上采用双读双写的模型; 大量请求并发访问缓存中的某个key(一般为热点数据),发现该key失效,故多个请求同时访问数据库,并回写缓存。
解决方案:
本地锁:与分布式锁类似的思路,解决单个节点的缓存并发问题,分布式场景下仍然可能发生;
04
缓存穿透

应用缓存设计上采用双读双写的模型; 大量请求并发访问,系统(缓存和数据库等)中不存在的key;
缓存无法命中,请求涌进数据库,耗尽数据库服务器资源。
解决方案:
入参校验:对入参的ID进行格式分析,如果不符合ID的生成规则,就直接拒绝,快速失败。甚至可以在ID生成上设计一些时间戳信息和校验串信息,这样在进行校验时候可以过滤掉一定数量的无效请求;
布隆过滤器:将缓存数据也映射到布隆过滤器中,当请求进来时,首先判断是否在布隆过滤器中存在,若不存在直接快速失败即可。即使布隆过滤器存在一定的误判率,也可以有效抵挡大部分的无效请求。
05
缓存雪崩

应用缓存设计上采用双读双写的模型; 缓存服务器重启、耗时卡顿和假死,或者大量缓存集中在某个较短的时间段内失效;
解决方案:
缓存预热与降级:必要时候做好缓存预热、缓存降级;
多级缓存方案:本地缓存 + Redis缓存 + 服务降级熔断策略;当Redis缓存失效时候,本地缓存兜底应急;
针对时间无关性:给缓存中不同的key设置随机的过期时间;
06
三者的共同点与不同点
大量请求,高并发场景;
缓存失效后需要访问数据库; 导致数据库和调用链路资源紧张,甚至导致应用程序停止服务。
不同点:
不同的触发条件和产生原因。缓存击穿发生在热点key被高并发访问的时候、缓存穿透指key在系统(缓存和数据库)中不存在,缓存雪崩指大量缓存在某一时间段失效;
不同的解决方案和策略。
07
总结
合理使用缓存。在分布式系统的缓存设计中,不仅要考虑业务的缓存设计,更要看到缓存实践后所潜在的风险。及时避免,未雨绸缪。
08
引用
[1] https://juejin.cn/post/6844903807797690376
[2] https://xie.infoq.cn/article/a035f12e5590385ac578778b0
[3] https://developpaper.com/cache-penetration-cache-breakdown-cache-avalanche-solution-analysis/
[4] 李艳鹏等,可伸缩服务架构:框架与中间件,电子工业出版社

点个在看你最好看




