使用缓存一些常见的套路问题。
缓存穿透
- 场景:大量请求访问某个不存在的KEY
在缓存设计中,查询缓存 -> key不存在 -> 回源DB -> 更新缓存,这是一个典型的方案。
缓存穿透是指查询一个一定不存在的Key,由于缓存层不存在,将导致这个不存在的数据每次请求都要到存储层去查询,直接对DB造成影响。在恶意攻击和失败回调中可能会出现这种情况。
- 解决方案
1.对空对象进行缓存。对查询结果为空的情况也进行缓存,如当此查询结果为空,设置Key对应对象为NULL,缓存时间设置短一点,存储层中有数据后及时更新。
2.对所有可能查询的参数Key以hash形式存储,在控制层先进行校验,不符合则丢弃。最常见的是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。比较适合命中不高,但是更新不频繁的数据。
缓存失效
- 场景:缓存中大量的Key集中在一段时间内失效,数据库的压力凸显
- 解决方案
1.可以分析用户行为,尽量让失效时间点均匀分布。针对失效时间相同的key,在设置失效时间时不设置固定的时间,而在原有基础上加上一个随机的值,比如1分钟-5分钟,这样就可以有效分散开缓存失效的时间。
2.考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
缓存并发
- 场景:在高并发场景下,某些业务有可能多个请求并发的去从数据库获取数据
有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大。
- 解决方案
1.添加分布式锁,在缓存更新或者过期的情况下,先尝试获取到锁,当更新或者从数据库获取完成后再释放锁,其他的请求只需要牺牲一定的等待时间,即可直接从缓存中继续获取数据。
2.定期从DB里查询数据,再刷到缓存里面,确保缓存里面的数据一直可以读到。
缓存雪崩
- 场景:当发生大量的缓存穿透,例如缓存挂掉,或者对某个失效的缓存的大并发访问
由于缓存扛了大量的请求,有效保护了数据库的安全。但是当缓存雪崩,所用请求就会瞬间全部打到DB上,可能会导致数据库崩溃。
- 解决方案
1.保证缓存服务的高可用性,当一个实例挂掉的时候,请求也可以转移到集群的其他实例上。缓存失效时的雪崩效应对底层系统的冲击非常大,这时候可以使用双缓存机制,在工作缓存之外另外维护一层灾备缓存。
2.使用降级策略,当缓存服务出现问题时,可以暂时对用户展示一份固定的数据,避免系统的崩溃,等待缓存服务的恢复。前端也应该有此机制,比如当后端接口返回非正常数据时,将之前保存的旧数据固定展示给用户,避免页面崩溃的问题。
缓存数据的淘汰
缓存淘汰的策略有两种:
1.定时去清理过期的缓存
2.当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂,具体用哪种方案,根据自己的应用场景来权衡。
更新缓存还是淘汰缓存
什么是更新缓存:数据不但写入数据库,还会写入缓存
什么是淘汰缓存:数据只会写入数据库,不会写入缓存,只会把数据淘汰掉
更新缓存的优点:缓存不会增加一次miss,命中率高
淘汰缓存的优点:简单
先操作数据库还是先操作缓存
假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致。
假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次Cache miss。
所以结论是:先淘汰缓存,再写数据库
参考: