前言
Redis作为高性能的数据库,其主要用途之一是缓存。缓存是高并发场景下提高热点数据访问性能的一个有效手段。缓存的类型分为:本地缓存、分布式缓存和多级缓存。本地缓存就是在进程的内存中进行缓存。分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
然而缓存有时候会失灵,比如Redis挂掉、大量的keys在同一个时间失效。这些都会导致Mysql等数据库遇到大量的访问,影响用户体验。下面就根据不同的缓存问题进行分析。
缓存雪崩
雪崩这个词很好理解,就是缓存出现大面积的失效。缓存雪崩是指Redis中大量的key几乎同时过期,然后大量并发查询穿过redis击打到底层数据库上,此时数据库层的负载压力会骤增,我们称这种现象为"缓存雪崩"。事实上缓存雪崩相比于缓存击穿更容易发生,对于大多数公司来讲,同时超大并发量访问同一个过时key的场景的确太少见了,而大量key同时过期,大量用户访问这些key的几率相比缓存击穿来说明显更大。或者Redis突然挂掉了。相对应的解决方案有:
- 使用快速失败的熔断策略
- 使用主从模式和集群模式来尽量保证缓存服务的高可用。
- 在可接受的时间范围内随机设置key的过期时间,分散key的过期时间,以防止大量的key在同一时刻过期;
- 对于一定要在固定时间让key失效的场景(例如每日12点准时更新所有最新排名),可以在固定的失效时间时在接口服务端设置随机延时,将请求的时间打散,让一部分查询先将数据缓存起来;
- 延长热点key的过期时间或者设置永不过期,这一点和缓存击穿中的方案一样;
缓存穿透
缓存穿透当查询Redis中没有的数据时,该查询会下沉到数据库层,同时数据库层也没有该数据。它产生这个问题的原因可能是遭遇外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。
解决方案:
- 在接口访问层对用户做校验,如接口传参、登陆状态、n秒内访问接口的次数;
- 利用布隆过滤器,将数据库层有的数据key存储在位数组中,以判断访问的key在底层数据库中是否存在;
缓存击穿
缓存击穿就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。它跟缓存穿透有些区别:穿透表示底层数据库没有数据且缓存内也没有数据,击穿表示底层数据库有数据而缓存内没有数据。当热点数据key从缓存内失效时,大量访问同时请求这个数据,就会将查询下沉到数据库层,此时数据库层的负载压力会骤增,我们称这种现象为"缓存击穿"。例如在这么一个应用场景:大量的key将在零点失效,而零点开始进行秒杀活动。那么对应的解决方案就比较简单:
- 在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值,这样可以保证数据不会在同一时间大面积失效
- 用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。
缓存预热
预热这个词很好理解,就是对redis进行初始化,类似深度学习的warming up。缓存预热的实现方式有很多,比较通用的方式是写个批任务,在启动项目时或定时去触发将底层数据库内的热点数据加载到缓存内。
缓存更新
缓存服务(Redis)和数据服务(底层数据库)是相互独立且异构的系统,在更新缓存或更新数据的时候无法做到原子性的同时更新两边的数据,因此在并发读写或第二步操作异常时会遇到各种数据不一致的问题。如何解决并发场景下更新操作的双写一致是缓存系统的一个重要知识点。
第二步操作异常:缓存和数据的操作顺序中,第二个动作报错。如数据库被更新, 此时失效缓存的时候出错,缓存内数据仍是旧版本;
缓存更新的设计模式有四种:
-
Cache aside:查询:先查缓存,缓存没有就查数据库,然后加载至缓存内;更新:先更新数据库,然后让缓存失效;或者先失效缓存然后更新数据库;
-
Read through:在查询操作中更新缓存,即当缓存失效时,Cache Aside 模式是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载;
-
Write through:在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后由缓存自己更新数据库;
-
Write behind caching:俗称write back,在更新数据的时候,只更新缓存,不更新数据库,缓存会异步地定时批量更新数据库;
四种缓存更新模式的优缺点:
- Cache Aside:实现起来较简单,但需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository);
- Read/Write Through:只需要维护一个数据存储(缓存),但是实现起来要复杂一些;
- Write Behind Caching:与Read/Write Through 类似,区别是Write Behind Caching的数据持久化操作是异步的,但是Read/Write Through 更新模式的数据持久化操作是同步的。优点是直接操作内存速度快,多次操作可以合并持久化到数据库。缺点是数据可能会丢失,例如系统断电等。
缓存降级
缓存降级是指当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,即使是有损部分其他服务,仍然需要保证主服务可用。可以将其他次要服务的数据进行缓存降级,从而提升主服务的稳定性。降级可以根据实时的监控数据进行自动降级也可以配置开关人工降级。是否需要降级,哪些服务需要降级,在什么情况下再降级,取决于大家对于系统功能的取舍。