先放总结图:
异常一:缓存与数据库数据要保持实时一致性
- 如果Redis为只读模式,更新数据时直接更新MySQL里的数据,同时删除Redis里的旧数据;
- 如果Redis为读写模式,更新数据时Redis和MySQL中都要更新,存在两个问题。
1. Redis与数据库中各自操作成功/失败的原子性 ------ 成功/失败问题
解决方案: 消息队列,更新失败后可以再次从消息队列读取更新信息,再次更新;
2. 高并发时,Redis与数据库更新操作间隔时间内有客户端请求,此时得到的数据是旧数据(旧数据可能会被更新到Redis,如果之后不清除Redis,那么往后的请求都将读到Redis中的旧数据)----- 谁先谁后问题
解决方案:延迟双删,更新数据前先删除Redis旧数据 — 更新数据库 — 线程sleep一段时间 —再检查删除Redis中的旧数据(难以计算需要sleep多久,不知道其他线程什么时候将旧数据更新到Redis,sleep时间短了的话,Redis中更新的新数据之后还是会被其他线程改为他们读到的旧数据)
针对问题一二的最终解决方案(强一致性)
- 使用分布式事务锁,对问题一保证原子性,对问题二保证串行性。
异常二:缓存雪崩(大量数据失效)的原因,Redis中大量数据同时过期 / Redis宕机需要恢复
解决方案
雪崩前预防:
- A) 设置不同过期时间
雪崩后补救:
- A) 服务降级:拒绝非核心数据访问数据库,允许核心数据继续访问数据库获取数据(在数据库能承受的压力内)
- B) 服务限流:不管是访问核心还是非核心数据,直接限制单位时间内能处理的请求数量
- C) 服务熔断:直接返回异常给客户端,直到Redis恢复后再提供访问请求
异常三:缓存击穿(单个热点数据失效),设置热点数据不过期
异常四:缓存穿透,数据库Redis中均不存在数据,给两者都造成很大压力(业务误删数据或者恶意访问)
解决方案:
A) 发生穿透后在Redis中缓存一个约定好的数值,业务层得到该约定的数值后会得知发生了缓存穿透;
B) 布隆过滤器(bit数组,数组每个元素取0/1)快速确认数据库中是否存在该值:
MySQL插入数据时到布隆过滤器中进行一个映射;
- 数据X存入数据库时,用N个hash函数计算该数据得到N个数值, i = hash_i(X)
- 这N个值对过滤器数组长度取模,找到模值对应的数组下标,index_i = i % nums.length
- 将这几个下标位置的值置1,nums[index_i] = 1’
查询数据库前,数据X先根据布隆算法求解bit数组,与布隆过滤器中的数组对比,bit数组中有一个元素0/1取值不对就代表数据库中不存在该数据,放弃继续访问数据库。
C) 请求拦截:在拦截器中拦截非法访问
参考文章:
https://time.geekbang.org/column/article/296586