Redis缓存场景
介绍旁路缓存和缓存异常的场景
旁路缓存(Cache-Aside)
Cache Aside Pattern 中服务端需要同时维系 db 和 cache,并且是以 db 的结果为准
- 读策略: 从缓存中读取数据;如果缓存命中,则直接返回数据;如果缓存不命中,则从数据库中查询数据;查询到数据后,将数据写入到缓存中,并且返回给用户
- 写策略: 更新数据库Mysql中的记录,然后删除Redis中的缓存记录
场景:
举个例子,假设某个用户的年龄是 20,请求 A 要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个请求 B 要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为 20,并且写入到缓存中,然后请求 A 继续更改数据库,将用户的年龄更新为 21。最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库的数据不一致
为什么「先更新数据库再删除缓存」不会有数据不一致的问题?
- 假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存中
- 最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。 从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高
- 因为缓存的写入通常要远远快于数据库的写入
- Cache Aside 策略适合读多写少的场景,不适合写多的场景
缓存穿透
用户访问的数据既不在缓存中,也不在数据库中
解决:
- 回写特殊值:缓存未命中且数据库也没有,在Redis缓存中设置一个特殊值表示数据不存在。会消耗内存
- 布隆过滤器:由初始值都为0的位图数组和N个哈希函数组成
流程:- 采用N个哈希函数运算得到N个哈希值
- 将这N个哈希值对位图数组长度取模,得到每个哈希值在数组中的位置
- 在N个对应位置设置成1
- 查询的时候,只要对于位置的值全是1就表示存在。因此过滤器查询到这个数据,数据库不一定存在。如果查询不到数据库一定不存在
- 限流策略:采取令牌桶算法或者漏桶算法,对这些数据进行限流
缓存击穿
缓存中的某个热点数据过期
解决:
- 设置热点数据的热度时间窗口,在这个时间内如果数据被频繁访问,将缓存时间延长
- 使用互斥锁或分布式锁,只允许一个线程去查询数据,避免多个线程同时查询数据库压力过大
- 热点数据缓存永不过期
- 异步更新缓存,减少对数据库的直接访问,并且不会阻塞请求的响应
缓存雪崩
大量缓存数据在同一时间过期或者Redis宕机
大量缓存数据在同一时间过期的解决:
- 设置缓存过期的随机过期时间
- 使用互斥锁或分布式锁,只允许一个线程去查询数据,避免多个线程同时查询数据库压力过大
- 数据预热,提前将热点数据加载到缓存
- 后台更新缓存,业务不再复制更新缓存,也不设置有效期,而让缓存“永久有效”,将更新缓存的工作交给后台线程定时更新
- 数据库优化,提升数据库的性能,增加数据库的容量,以应对大量请求导致的数据库压力
Redis宕机的解决:
- 服务熔断或者限流策略:暂停业务对缓存的访问,如果Redis宕机则直接返回错误不访问数据库。但是这样做会导致业务无法正常工作。也可以启动限流策略,只接受少量部分的请求
- 提供缓存本身的可用性:通过主从节点的构建Redis缓存高可靠集群
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 cloud_fly blog!