在项目发展到后期,一些业务场景整体都处于高并发状态,大量QPS对整体业务的负载要求很高,为了避免很多时候脱离架构优化的初衷,还需要在项目中做到很多预先性的规避和细节把控。
2.1 优化防止缓存击穿
当请求发来的查询 Key 在 Cache 中存在,但某一时刻数据过期了,并且此时出现了大量并发请求,那么这里因为 Cache 中 Miss,就会统一去 DB 中搜索,直接造成在很短的时间内,DB 的 QPS 压力会陡增。
对于这种问题的预防和优化,往往从两方面入手:一是程序中加小粒度的锁/信号(去年有写过一篇关于商城系统里库存并发管控杂记,里面有具体话题的细节扩展,详见:https://www.cnblogs.com/bsfz/ );二是将 DB的读取延迟 和 Cache的写入时间尽可能拉到最低;三是对其中过于热点的数据采取一个较大的过期时间并做一定的随机性(这里非必要,可自行权衡)。其实还有一点,少数情况下,可根据场景是否限制,可以增加适当的到期自动刷新的策略,这里也可以考虑在程序中开启固定的线程通知维护。
2.2 预防大量缓存穿透
当请求发来的查询 Key 在 Cache 中 Miss,自然就会去 DB 里搜索,这里本身没问题,但是假如查询的 Key 在 DB 中也不存在,那么意味着每次请求实际上都是实打实落在了 DB 上。这种问题比较常见,并且即使并发不是很大的时候 DB 的连接数也轻松达到上限,而且本身也不符合我们设计为了提高QPS的初衷。
对于这种漏洞性问题的解决方式,同样可以从两方面入手:一是程序可以在第一次从DB搜索数据为 NULL 的时候,直接将 NULL 或者一个标识符 Sign 缓存起来,同时个人建议尽量设置一个小范围的随机过期时间,避免不必要的长期内存占用;二是程序里限制过滤一些不可能存在的数据KEY,如借鉴 Bloom filter 思想,特别是在前端请求到后端的这里,尽量进行一次中间判断处理(如有时对不合法KEY直接返回NULL)。
2.3 控制缓存雪崩
这里会有某些细节和上面类似,但不完全。当Cache出现不可用,再或者大量数据同一场景里同一时刻失效,批量请求直接访问DB,并且此刻也等同于没有任何Cache措施了。
为了规避这种偏极端的问题,主要可以考虑从三个方面入手:一是增加完善Cache 的高可用机制,并最好有单独的运维监控预警;二是类似上面针对Cache的时间再次作随机,特别是包含预热和批量的场景里。(ps:你看很多地方都有类似设计来降低一定概率,个人在设计时,即使是项目初期阶段的简化版本里也会包含进去。);三是,在部分场景增加多级Cache,但是在很多时候会增加其他的问题(如多级之前的同步问题),所以个人推荐优先增加到二级即可,然后稍微调整下时间尽量不高于一级Cache。