Redis⑥ —— 缓存设计
1. 缓存雪崩
- Redis会设置过期时间来保证缓存中的数据与数据库的数据一致性,当大量缓存数据在同一时间过期或Redis故障宕机,如果此时有大量用户请求都无法在Redis中处理,会直接全部请求访问数据库,导致压力骤增,从而形成一系列连锁反应造成整个系统崩溃
1.1 原因一:大量数据同时过期
- 均匀设置过期时间
- 可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
- 互斥锁
- 当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存,当缓存构建完成后,再释放锁。
- 后台更新缓存
- 业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。
- 缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,而在缓存被“淘汰”到下一次后台定时更新缓存的这段时间内,业务线程读取缓存失败就返回空值,业务的视角就以为是数据丢失了。
- 解决方法一:后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效
- 解决方法二:在业务线程发现缓存数据失效后(缓存数据被淘汰),通过消息队列发送一条消息通知后台线程更新缓存
- 在业务刚上线的时候,我们最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热,后台更新缓存的机制刚好也适合干这个事情
1.2 原因二:Redis故障宕机
- 服务熔断或请求限流机制
- 启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力
- 启用请求限流机制,只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。
- 构建Redis缓存高可靠集群
2. 缓存击穿
- 缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮
- 缓存击穿是缓存雪崩的一个子集
- 解决方法:
- 互斥锁
- 不给热点数据设置过期时间,由后台异步更新缓存 或 在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间
3. 缓存穿透
- 当用户访问的数据,既不在缓存中,也不在数据库中,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增。造成缓存穿透
- 原因:
- 业务误操作
- 黑客恶意攻击
3.1 非法请求的限制
- 当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在
3.2 缓存空值或默认值
- 可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
3.3 使用布隆过滤器快速判断数据是否存在
- 即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。
- 布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据
4. 动态缓存热点数据的策略
- 通过数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据。
- 以电商平台场景中的例子,现在要求只缓存用户经常访问的 Top 1000 的商品
- 先通过缓存系统做一个排序队列(比如存放 1000 个商品),系统会根据商品的访问时间,更新队列信息,越是最近访问的商品排名越靠前;
- 同时系统会定期过滤掉队列中排名最后的 200 个商品,然后再从数据库中随机读取出 200 个商品加入队列中;
- 这样当请求每次到达的时候,会先从队列中获取商品 ID,如果命中,就根据 ID 再从另一个缓存数据结构中读取实际的商品信息,并返回。
5. 缓存更新策略
5.1 Cache Aside(旁路缓存)策略
- 写策略:
- 先更新数据库中的数据,再删除缓存中的数据
- 读策略:
- 如果读取的数据命中了缓存,则直接返回数据;
- 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户
- Cache Aside 策略适合读多写少的场景,不适合写多的场景,因为当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。
5.2 Read/Write Through(读穿 / 写穿)策略
- 应用程序只和缓存交互,不再和数据库交互,而是由缓存和数据库交互,相当于更新数据库的操作由缓存自己代理了
- read through策略:
- 先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库查询数据,并将结果写入到缓存组件,最后缓存组件将数据返回给应用
- write through策略:
- 当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在:
- 如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成。
- 如果缓存中数据不存在,直接更新数据库,然后返回;
- 特点是由缓存节点而非应用程序来和数据库打交道
5.3 Write Back(写回)策略(Redis不适用,不能异步更新数据库)
- 在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。
- Write Back 策略特别适合写多的场景,因为发生写操作的时候, 只需要更新缓存,就立马返回了。
- 问题是数据不是强一致性的,而且会有数据丢失的风险
(图片来源网络,侵删)
- 当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在:
- 写策略:
- 可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
- 当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在
- 服务熔断或请求限流机制
- 在业务刚上线的时候,我们最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热,后台更新缓存的机制刚好也适合干这个事情
- 均匀设置过期时间
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。