缓存
缓存用于存储那些频繁访问的数据,以减少重复计算或数据获取的时间。因为直接访问数据库需要读取磁盘,而磁盘的访问速度是很慢的。
应用程序处理一个请求时,都会先去缓存中获取数据,如果缓存中没有,再去数据库中获取。因此也就产生了缓存雪崩、击穿和穿透。
缓存雪崩
缓存雪崩(Cache Avalanche) 是指在短时间内,大量缓存同时失效,导致大量请求直接访问数据库,从而引发数据库过载、宕机的现象。
解决方法:
均匀分期:设置不同的过期时间,让缓存失效的时间尽量均匀,避免相同的过期时间导致缓存雪崩,造成大量数据库的访问。
- 随机过期时间:为缓存设置随机的过期时间,避免大量缓存同时失效,分散请求负载。
- 合理设置过期策略:综合考虑数据访问频率和业务需求,合理设置不同数据的过期时间,避免热点数据过期集中。
热点数据缓存用不过期。
多级缓存架构:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同。
缓存预热:在系统启动或缓存过期前,提前加载热点数据至缓存,确保缓存持续可用。
缓存集群高可用:使用高可用的缓存集群,避免单点故障导致所有缓存失效。通过主从复制、数据分片等方式提高缓存系统的稳定性。
降级策略:当缓存失效时,系统可以降级处理,例如返回默认值、进行有限频率的数据库访问,避免全部请求涌向数据库。
监控与报警:实时监控缓存和数据库的状态,设立报警机制,及时发现和处理缓存异常情况,防止雪崩进一步扩散。
缓存击穿
缓存击穿 (Cache Breakdown)是指在高并发情况下,某个热点数据的缓存同时失效,大量请求同时查询数据库,导致数据库瞬时压力增大。
解决方法:
互斥锁(Mutex Lock):当缓存失效时,使用分布式锁或本地锁控制只有一个请求去查询数据库,并设置新的缓存,其他请求等待或直接返回旧值。这种方式会阻塞其他的线程,导致系统的吞吐量下降。
热点数据缓存永远不过期:
永不过期实际包含两层意思:
- 物理不过期,针对热点key不设置过期时间
- 逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建
随机过期时间:为热点数据设置随机的过期时间,避免所有数据在同一时间点失效,分散请求负载。
双重检查锁:多层次的锁机制,确保只有必要的请求访问数据库,其他请求从缓存中获取数据。
本地缓存与远程缓存结合:通过在应用本地使用一级缓存(如本地内存缓存),减少对远程缓存的依赖,降低击穿风险。
缓存穿透
缓存穿透 (Cache Penetration)是指用户请求的数据在缓存中不存在(即:缓存没有命中),同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。
如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。
解决方法:
布隆过滤器(Bloom Filter): 使用布隆过滤器预先过滤掉一定规模的不存在的键,减少无效请求。
如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力。
缓存空对象:
当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value=“null”,并设置其过期时间极短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
参数校验: 对用户输入的参数进行严格校验,确保请求的合法性,防止恶意或无效请求。
限流措施: 对高频率的请求进行限流,防止恶意请求过多地打击系统。
缓存降级
缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。
缓存预热
缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。
缓存预热的方式:
- 数据量不大:工程启动的时候进行加载缓存动作;
- 数据量大:设置一个定时任务脚本,进行缓存的刷新;
- 数据量太大:优先保证热点数据进行提前加载到缓存。