一、使用Redis作为分布式缓存存在的问题
1、数据的一致性问题:即Redis缓存服务器与mysql之间存在数据不一致问题
一般情况下,新增数据没有一致性的问题,修改和删除数据会有一致性问题
因为市面上的解决方案都是不去更新缓存,而是删除缓存,再更新缓存,所以新增不存在一致性问题;
2、数据一致性产生的原理:
线程A执行删除缓存---》删除成功 -------》线程A执行更新数据库操作
线程B查询缓存 --- 》发现缓存为空 ---》线程B执行查询数据库,将查询结果更新到缓存
(高并发情况下,此时线程A还未完成数据库更新操作,线程B查询到的数据是旧数据,将旧数据更新到了缓存)
线程C查询缓存-----》取到线程B更新的旧的缓存数据,从而导致缓存数据和数据库数据不一致问题
3、解决方案
解决方案一:先操作缓存再操作数据库:
解决原理:
使用延迟双删,数据库A在更新数据库后,再次删除缓存;
问题又来了:为啥要延迟(让线程A sleep个500ms(具体时间根据项目定))?
因为:不延迟有可能线程B已经到查数据库那步,但已经查到旧数据,此时线程A第二次删除了,线程B又查询到了旧数据再更新缓存,则会导致不一致;
缺陷:
a、延迟双删能保证数据最终一致性,在线程B进行查询的时候还是会有一次查到旧数据
b、业务层进行线程休眠会影响高并发系统整体性能;
解决方案二:
异步延迟双删:在延迟双删的基础上,引入mq进行异步删除,不需要进行休眠,但引入MQ会增加系统复杂度;
解决方案三:
解决异步延迟删除:引入阿里的canal
canal原理:启一个进程,监听mysql的binlog日志文件,将有数据更新的数据进行写入mq去执行延迟删除
以上三个方案仅能保证数据最终一致性,始终会存在至少一次查询到旧数据;
想要保证数据强一致性,可以加锁,但是加锁,会影响整体性能,得不偿失;
方案二(推荐方案):
先更新数据库,再删除缓存
(能保证数据库最终一致性,但也会存在一次查询到旧数据)
存在问题:更新数据库成功,删除缓存失败怎么解决?使用mq发送到消息队列,进行重试,一样可以使用阿里的canal框架;
二、缓存在高并发情况下存在的问题及解决方案
4、缓存穿透:
a、当黑客用一个不存在的key一直请求,请求直接穿到mysql数据库,从而导致系统资源占用导致系统不可用;
b、解决方案:使用布隆过滤器,快速过滤黑名单信息
5、缓存击穿:
a、某些key在一个时间点全部失效,导致请求全部到mysql层,mysql系统不可用,从而引起整个系统奔溃;(比如秒杀结束,某些秒杀商品)
b、解决方案:
加时间节点判断秒杀是否结束
给秒杀商品设置随机过期时间,而非固定过期时间,使得所有的key不在同一时间过期