1、缓存雪崩
缓存雪崩就是 Redis 的大量热点数据在短时间内同时过期(失效),因为设置了相同的过期时间,刚好这个时候 Redis 请求的并发量又很大,就会导致所有的请求到数据库。
缓存雪崩解决方案:
1. 加互斥锁或者使用队列,针对同一个key只允许一个线程到数据库查询;
2.缓存定时预先更新,避免同时失效;
3.通过加随机数,使 key 在不同的时间过期,错开过期时间;
4.缓存永不过期。
2、缓存穿透
缓存穿透是说请求查询的key在Redis缓存和数据库都不存在,这可能是一次错误的查询,也可能用户恶意攻击。在这种情况下,因为数据库值不存在,所以肯定不会写入 Redis,那么下一次查询相同的key 的时候,肯定还是会再到数据库查一次。这种频繁查询数据库中不存在的值,导致Redis缓存失效,我们称之为缓存穿透。
避免缓存穿透解决方案:
1.根据key缓存空数据,同时必须设置一个过期时间;
2.根据key缓存特殊字符串;
3.根据IP做限流。
每次查询的key如果我们知道在数据库不存在,我们直接返回就不需要到数据库查询。我们可以考虑布隆过滤器,我们把key放入到bitmap中,根据布隆过滤器特性,判断key不存在则key在数据库中一定不存在,这样我们就可以有效的过滤不合法的查询请求了。
3、缓存与数据库一致性
3.1、查询请求,redis中不存在,则先到数据库查询然后放入缓存,redis存在则直接返回。
3.2、更新请求,先删除缓存还是先更新数据库?
如果先删除了缓存,但更新数据库还没有完成,这是另一个查询请求查询相同key,发现缓存没有,就会从数据库查询,然后将redis中放一份。这样删除就失效的。所以要先更新数据库。
3.3、是删除缓存还是更新缓存的值呢?
更新缓存之前,是不是要经过其他表的查询、接口调用、计算才能得到最新的数据,而不是直接从数据库拿到的值。如果是的话,建议直接删除缓存,这种方案更加简单,而且避免了数据库的数据和缓存不一致的情况。在一般情况下,我们也推荐使用删除的方案。另外根据懒加载思想,我们可以等到查询时在重新放一份到redis中,而不是更新缓存。
Redis的数据与数据库不可能通过事务达到统一,我们只能通过一些措施降低出现不一致的概率。
(1)如果更新数据库失败,删除缓存成功与否,下次查询获取的数据都是旧值。当更新数据库失败时捕获异常,不操作缓存,所以这时数据依然一致。
(2)如果更新数据库成功,删除缓存失败,则缓存与数据库不一致。出现这种情况我们如何解决呢?
第一种:删除缓存失败后增加重试机制,将要删除的key放入消息队列,通过线程消费队列再去删除这个key。
第二种:采用异步更新缓存,因为更新数据库时会往 binlog 写入日志,所以我们可以通过一个服务来监听 binlog的变化(比如阿里的 canal),然后在客户端完成删除 key 的操作。如果删除失败的话,再发送到消息队列。总之,对于后删除缓存失败的情况,我们的做法是不断地重试删除,直到成功。无论是重试还是异步删除,都是最终一致性的思想。
(3)如果二者均成功则完美。
3.4 延时双删
前面我们说到先删缓存再更新数据库可能存在删除了又被另一个请求从数据库拿出来放入缓存。那么我们又想到改进的方案:延时双删。步骤如下:
1.先删除缓存,2.更新数据库,3.线程休眠一会儿,4,再删除一遍。