缓存读写策略
Redis 中常用的缓存读写策略主要有以下几种,各有其适用场景和优缺点。
旁路缓存(Cache Aside)
服务端需同时维护数据库和缓存,读取时优先从缓存获取数据,缓存未命中则从数据库读取并回写缓存;写入时直接更新数据库并删除缓存。
读策略:
- 从缓存中读取数据,读取到就直接返回。
- 缓存中读取不到数据,就从数据库中读取数据并返回。
- 把读到的数据写入到缓存中。
写策略:
- 先更新数据库。
- 再直接删除缓存。
优点:
- 简单易实现,适用于小规模系统。
- 缓存数据不会因过期导致雪崩问题。
- 通过缓存加速读取性能,减轻数据库压力。
缺点:
- 首次请求数据一定不在缓存中,可通过预热热点数据缓解。
- 数据一致性依赖业务逻辑维护,存在短暂不一致风险。
- 写操作频繁时可能频繁删除缓存,影响性能。
读写穿透(Read/Write Through)
单独提供一个缓存服务接管所有读写操作,服务端把缓存视为主要数据存储,从中读取数据并将数据写入其中,不直接与数据库交互。读取时若缓存未命中则由缓存层同步从数据库加载;写入时缓存层同时更新缓存和数据库。
读写穿透策略实际只是在旁路缓存策略之上进行了封装,旁路策略的读写操作是由客户端来完成的,而读写穿透策略则是由缓存服务来完成的,后者对客户端是透明的,从而减轻了客户端的职责,简化了客户端的数据操作。
- 在旁路缓存下,发生读请求的时候,如果缓存中不存在对应的数据,则由客户端自己负责把数据写入缓存,而读写穿透策略则是缓存服务自己来写入缓存的,这对客户端是不可见的。
- 和旁路缓存一样,读写穿透也有首次请求数据一定不在缓存中的问题,对于热点数据可以提前放入缓存中。
异步写入(Write Behind)
仅在缓存层进行写操作,后续以异步方式更新数据库,降低实时写入压力。
异步写入和读写穿透策略很相似,两者都是由缓存服务来负责缓存和数据库的读写。不同的是,读写穿透是同步更新缓存和数据库,而异步写入则是只更新缓存,不直接更新数据库,而是改为异步批量的方式来更新数据库。
缓存异常问题
引入了缓存层,就会有缓存异常的三个问题,分别是缓存雪崩、缓存击穿、缓存穿透。
缓存雪崩
当大量缓存在同一时间过期或 Redis 故障宕机时,如果此时有大量的请求无法从 Redis 中获取缓存数据,全部去直接访问数据库,会导致数据库压力激增,严重的会导致数据库宕机,从而导致一系列的连锁反应造成整个系统崩溃,这就是『缓存雪崩』问题。
发生缓存雪崩有以下两个原因:
- 大量数据同时过期
- Redis 故障宕机
针对大量数据同时过期而引发的缓存雪崩问题,常见的应对方法有下面这几种:
-
均匀设置过期时间
- 如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
-
互斥锁保证同一时间内只有一个请求构建缓存
- 当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存,当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
-
后台更新缓存
- 业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存『永久有效』,并将更新缓存的工作交由后台线程定时更新。
针对 Redis 故障宕机引发的缓存雪崩问题,常见的应对方法有下面这几种:
-
服务熔断或请求限流机制
- Redis 故障宕机而导致缓存雪崩问题时,可以启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。
- 为了减少对业务的影响,也可以启用请求限流机制,只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。
-
构建 Redis 缓存高可靠集群
- 如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。
缓存击穿
业务中被频繁地访问的数据被称为热点数据,如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是『缓存击穿』的问题。
缓存击穿与缓存雪崩很相似,可以认为缓存击穿是缓存雪崩的一个子集。应对缓存击穿也可采取前面的两种方案:
- 保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么返回空值或默认值。
- 不给热点数据设置过期时间,由后台异步更新缓存。
缓存穿透
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据来服务后续的请求。当有大量这样的请求到来时,数据库的压力骤增,这就是『缓存穿透』的问题。
缓存穿透的发生一般有这两种情况:
- 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据。
- 恶意非法请求,故意大量访问某些读取不存在数据的业务。
应对缓存穿透的方案,常见的方案有三种:
-
非法请求的限制
- 当有大量请求访问不存在的数据的时候,也会发生缓存穿透。
- 可以在入口处判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
-
缓存空值或者默认值
- 当发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,而不用继续查询数据库。
-
使用布隆过滤器快速判断数据是否存在
- 在写入数据库数据时,使用布隆过滤器做标记,在用户请求时,业务线程查询到缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不再通过查询数据库来判断数据是否存在。
- 即使发生了缓存穿透,大量请求只会查询 Redis 缓存和布隆过滤器,而不会查询数据库,保证了数据库能正常运行。
相关问题
Redis 旁路缓存模式中延迟双删和 CDC 方案对比?
延迟双删策略和 CDC 策略都是用于解决 Redis 旁路缓存模式中缓存与数据库一致性问题的重要方案,但实现方式和适用场景有显著区别。
延迟双删(Delayed Double Delete)
在标准旁路策略中,若数据库采用主从架构,更新操作可能因主从同步延迟导致缓存删除后,后续请求仍读取到旧数据并重新写入缓存。
延迟双删是在标准的旁路缓存模式的写流程(先更新数据库,再删缓存)基础上,增加一次延迟的缓存删除操作来降低此类风险,覆盖主从延迟的时间窗口。
工作流程如下:
- 第一次删除:写操作开始时,先删除 Redis 中的旧缓存数据。
- 更新数据库:执行数据库更新操作。
- 延迟等待:主动等待一段时间。
- 第二次删除:延迟时间过后,再次删除 Redis 中的缓存数据。
CDC 方案
当系统规模变大、对一致性和应用解耦要求很高时,CDC 方案是一个强有力的选择,尽管引入了一定的复杂性。
CDC(Change Data Capture)是一种用于实时捕获和跟踪数据库数据变更(如插入、更新、删除)的技术方案。它的核心思想是监听数据库的变更事件而非轮询查询,并将这些变更事件以有序、可靠的方式传递给下游系统,用于实现数据同步、缓存更新、实时分析等场景。
不再依赖应用代码主动维护缓存一致性,通过独立的基础设施,实时监听数据库的变更日志,解析出数据变更事件来自动触发缓存失效或更新操作。
在 MySQL + Redis 的系统架构下,可以采用订阅 MySQL Binlog 的方式来实现该方案。