一致性的含义
根据是否接受写请求,可以将缓存分成写缓存和只读缓存
对于读写缓存,修改数据时,对缓存和数据库都需要修改,因此需要指定写回策略
对于数据一致性要求较高时,应该采取同步直写策略,并且采用事务机制,保证缓存和数据库的更新具有原子性,即要不一起更新,要不都不更新,返回错误信息。
对数据一致性要求不是那么高时,例如缓存中存放电商商品的非关键属性或短视频的创建或修改时间等,使用异步写回策略
对于只读缓存来说,如果有数据新增,会直接写入数据库;而有数据删改时,就需要把只读缓存中的数据标记为无效。这样一来,应用后续再访问这些增删改的数据时,因为缓存中没有相应的数据,就会发生缓存缺失。此时,应用再从数据库中把数据读入缓存,这样后续再访问数据时,就能够直接从缓存中读取了。
新增数据,数据会直接写到数据库中,不对缓存进行操作。缓存中没有新增数据,数据库中是最新值,符合一致性的第二种情况
发生删改操作时,既需要更新数据库,也需要删除缓存中数据,如果这两个操作无法保证原子性,就会出现数据不一致的问题
先删除缓存,再更新数据库
删除缓存成功,更新数据库失败,此时有其他并发请求访问数据,则Redis中缓存缺失,访问数据库后读到旧值
先更新数据库,再删除缓存
更新数据库成功,删除Redis缓存失败,其他并发请求访问数据,Redis直接返回缓存旧值
将需要删改的缓存值暂存到消息队列中,如果应用没有成功删改缓存值或更新数据库时,从消息队列中重新读取值,重新进行对应的删改操作
若删改操作成功,应将这些值从消息队列中去除,避免重复操作,否则重试至消息一致为止。重试次数到达阈值后,需要向业务层发送报错信息。
消息队列只能处理小量并发情况,大量并发请求时,应用还是有可能读到不一致的数据。
线程A更新完数据库值以后,先让线程Asleep一小段时间,再进行一次缓存删除操作
sleep一段时间的目的是让线程B先从数据库读取数据,并将缺失的数据写入缓存中,线程A再删除缓存。
因此sleep时间应该以线程读取数据和写缓存的平均时间为基准进行估算
其他线程再读取数据时,会发现缓存缺失,从数据库中读取最新值
// 伪代码
redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)
缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增
缓存击穿是指,针对某个访问频繁的热点数据,无法在缓存中进行处理,对该热点数据的大量请求,都发送到后端数据库,导致数据库压力激增,影响数据库处理其他请求
对于访问特别频繁的热点数据,不设置过期时间,对热点数据的访问请求,都在缓存中进行处理
缓存穿透是指要访问的数据既不在Redis缓存中,也不在数据库中,请求数据时,访问缓存-缓存缺失-访问数据库-数据库无数据的流程,如果有大量这样的请求,会同时对缓存和数据库带来巨大压力
缓存穿透解决方法
尽量使用预防式方案
缓存穿透这个问题的本质是查询了Redis和数据库中没有的数据,而服务熔断、服务降级和请求限流的方法,本质上是为了解决Redis实例没有起到缓存层作用的问题,缓存雪崩和缓存击穿都属于这类问题。