首頁  >  文章  >  Java  >  Java中怎麼保證快取一致性

Java中怎麼保證快取一致性

王林
王林轉載
2023-05-02 13:13:161077瀏覽

方案一:更新快取,更新資料庫

這種方式可輕易排除,因為如果先更新快取成功,但是資料庫更新失敗,肯定會造成資料不一致。

方案二:更新資料庫,更新快取

這種快取更新策略俗稱雙寫,存在問題是:並發更新資料庫場景下,會將髒資料刷到快取

updateDB();
updateRedis();

舉例:如果在兩個操作之間資料庫和快取又被後面請求修改,此時再去更新快取已經是過期資料了。

Java中怎麼保證快取一致性

方案三:刪除緩存,更新資料庫

#有問題:更新資料庫之前,若有查詢要求,會將髒資料刷到快取

deleteRedis();
updateDB();

範例:如果在兩個操作之間發生了資料查詢,那麼會有舊資料放入快取。

Java中怎麼保證快取一致性

該方案會導致請求資料不一致

如果同時有一個請求A進行更新操作,另一個請求B進行查詢操作。那麼會出現如下情形:

  • 請求A進行寫入操作,刪除快取

  • 請求B查詢發現快取不存在

  • 請求B去資料庫查詢得到舊值

  • 請求B將舊值寫入快取

  • 請求A將新值寫入資料庫

上述情況就會導致不一致的情形出現。而且,如果不採用給快取設定過期時間策略,該資料永遠都是髒資料。

方案四:更新資料庫,刪除快取

有問題:在更新資料庫之前有查詢要求,並且快取失效了,會查詢資料庫,然後更新快取。如果在查詢資料庫和更新快取之間進行了資料庫更新的操作,那麼就會把髒資料刷到快取

updateDB();
deleteRedis();

範例:如果在查詢資料庫和放入快取這兩個操作中間發生了資料更新並且刪除緩存,那麼會有舊資料放入快取。

Java中怎麼保證快取一致性

假設有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那麼會有如下情形產生

  • #快取剛好失效

  • 請求A查詢資料庫,得一個舊值

  • 請B將新值寫入資料庫

  • 請求B刪除快取

  • 請求A將查到的舊值寫入快取

如果發生上述情況,確實是會發生髒數據。但是發生上述情況有一個先天性條件,就是寫資料庫操作比讀取資料庫操作耗時更短

不過資料庫的讀取操作的速度遠快於寫入操作的

#因此這情況很難出現。

方案比較

方案1與方案2的共同缺點:

並發更新資料庫場景下,會將髒資料刷到緩存,但一般並發寫的場景機率都相對小一些;

線程安全角度,會產生髒數據,例如:

  • 線程A更新了資料庫

  • 線程B更新了資料庫

  • #線程B更新了快取

  • 線程A更新了快取

#方案3與方案4的共同缺點:

不管採用哪一種順序,2種方式都是存在一些問題的:

  • 主從延時問題:不管是先刪除或後刪除,資料庫主從延時可能導致髒資料的產生。

  • 快取刪除失敗:如果快取刪除失敗,則會產生髒資料。

問題解決想法:延遲雙刪,新增重試機制,以下介紹!

更新快取還是刪除快取?

  • 1.更新快取快取需要有一定的維護成本,而且會存在並發更新的問題

  • 2.寫多在讀少的情況下,讀取請求還沒來,快取以及被更新很多次,沒有起到快取的作用

  • 3.放入快取的值可能是經過複雜計算的,如果每次更新,都計算寫入快取的值,浪費效能的

#刪除快取優點:簡單、成本低,容易開發;缺點:會造成一次cache miss

如果更新快取開銷較小且讀多寫少,基本上不會有寫並發的時候可以才用更新緩存,否則通用做法還是刪除快取。

总结

方案 问题 问题出现概率 推荐程度
更新缓存 -> 更新数据库 为了保证数据准确性,数据必须以数据库更新结果为准,所以该方案绝不可行 不推荐
更新数据库 -> 更新缓存 并发更新数据库场景下,会将脏数据刷到缓存 并发写场景,概率一般 写请求较多时会出现不一致问题,不推荐使用。
删除缓存 -> 更新数据库 更新数据库之前,若有查询请求,会将脏数据刷到缓存 并发读场景,概率较大 读请求较多时会出现不一致问题,不推荐使用
更新数据库 -> 删除缓存 在更新数据库之前有查询请求,并且缓存失效了,会查询数据库,然后更新缓存。如果在查询数据库和更新缓存之间进行了数据库更新的操作,那么就会把脏数据刷到缓存 并发读场景&读操作慢于写操作,概率最小 读操作比写操作更慢的情况较少,相比于其他方式出错的概率小一些。勉强推荐。

推荐方案

延迟双删

采用更新前后双删除缓存策略

public void write(String key,Object data){
  redis.del(key);
     db.update(data);
     Thread.sleep(1000);
     redis.del(key);
 }
  • 先淘汰缓存

  • 再写数据库

  • 休眠1秒,再次淘汰缓存

大家应该评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上即可。

这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

问题及解法:

1、同步删除,吞吐量降低如何处理

将第二次删除作为异步的,提交一个延迟的执行任务

2、解决删除失败的方式:

添加重试机制,例如:将删除失败的key,写入消息队列;但对业务耦合有些严重;

Java中怎麼保證快取一致性

延时工具可以选择:

最普通的阻塞Thread.currentThread().sleep(1000);

Jdk调度线程池,quartz定时任务,利用jdk自带的delayQueue,netty的HashWheelTimer,Rabbitmq的延时队列,等等

实际场景

我们有个商品中心的场景,是读多写少的服务,并且写数据会发送MQ通知下游拿数据,这样就需要严格保证缓存和数据库的一致性,需要提供高可靠的系统服务能力。

写缓存策略

  • 缓存key设置失效时间

  • 先DB操作,再缓存失效

  • 写操作都标记key(美团中间件)强制走主库

  • 接入美团中间件监听binlog(美团中间件)变化的数据在进行兜底,再删除缓存

Java中怎麼保證快取一致性

读缓存策略

  • 先判断是否走主库

  • 如果走主库,则使用标记(美团中间件)查主库

  • 如果不是,则查看缓存中是否有数据

  • 缓存中有数据,则使用缓存数据作为结果

  • 如果没有,则查DB数据,再写数据到缓存

Java中怎麼保證快取一致性

注意

关于缓存过期时间的问题

如果缓存设置了过期时间,那么上述的所有不一致情况都只是暂时的。

但是如果没有设置过期时间,那么不一致问题就只能等到下次更新数据时解决。

所以一定要设置缓存过期时间

以上是Java中怎麼保證快取一致性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除