redis分布式鎖解決緩存雙寫一致性
如何解決緩存雙寫問題
只要涉及到緩存,那么緩存雙寫的問題就避免不了,每一種情況下使用的方案也不相同,對于數(shù)據(jù)一致性要求不高的場景,我們可以使用延時雙刪等方案來實現(xiàn),而對于一致性要求很高的場景,在之前查找的資料都是基于隊列來實現(xiàn),也就是所有的請求都進入一個隊列,但是實現(xiàn)起來相對來說比較復雜。今天就使用分布式鎖來實現(xiàn)
業(yè)務背景-美食分享
1: 現(xiàn)在有一個很火的美食博主分享了一篇美食,此刻是很多人都會來查看,對于美食分享是典型的讀多寫少的場景,可以利用緩存
//根據(jù)id查詢美食信息
public GoodsVO loadGoodsInfoById(Long id) {
//從redis中拿用戶信息
Object obj = redisTemplate.opsForValue().get(GOODS_KEY + id);
if(obj == null) {
//如果redis中不存在,就從數(shù)據(jù)庫中獲取
GoodsVO goods = loadGoodsFromDb(id);
//將結(jié)果保存到redis中
redisTemplate.opsForValue().set(GOODS_KEY+id,JSONUtil.toJsonPrettyStr(goods));
return fileUser;
}
return JSONUtil.toBean(obj.toString(), GoodsVO.class);
}//編輯美食信息
public void modifyGoodsById(GoodsVO goodsVO) throws Exception{
//刪除緩存
redisTemplate.delete(GOODS_KEY + goodsVO.getId());
//更新用戶信息
goodsMapper.updateById(goodsVO);
//刪除緩存
Thread.sleep(1000);
redisTemplate.delete(GOODS_KEY + goodsVO.getId());
}而對于更新數(shù)據(jù)的時候是先更新緩存還是先更新數(shù)據(jù)庫呢?
1:先更新緩存再更新數(shù)據(jù)庫
- 如果更新緩存成功,更新數(shù)據(jù)庫失敗,那么緩存中的數(shù)據(jù)就是臟數(shù)據(jù),與數(shù)據(jù)庫數(shù)據(jù)不一致
2:先更新數(shù)據(jù)庫再更新緩存
- 更新數(shù)據(jù)庫成功,但是此時更新緩存失敗,那么緩存中的還是臟數(shù)據(jù),與數(shù)據(jù)庫數(shù)據(jù)不一致
3:延時雙刪 - 先刪除緩存,再更新數(shù)據(jù)庫,最后再刪除緩存
- 即使第一次刪除緩存失敗了,后續(xù)的更新數(shù)據(jù)庫操作也不會執(zhí)行,此時來讀取的線程發(fā)現(xiàn)緩存中沒有數(shù)據(jù),從數(shù)據(jù)庫讀取最新的。
- 第一次刪除緩存成功,更新數(shù)據(jù)庫失敗,此時來讀取的線程發(fā)現(xiàn)緩存中沒有數(shù)據(jù),從數(shù)據(jù)庫讀取最新的。
- 第一次刪除緩存成功,更新數(shù)據(jù)庫成功,第二次刪除緩存成功,那么來讀取的線程發(fā)現(xiàn)緩存中沒有數(shù)據(jù),從數(shù)據(jù)庫讀取最新的。
- 第一次刪除除緩存成功,更新數(shù)據(jù)庫成功,第二次刪除緩存失敗,這時候就會有問題了

問題一:Thread-A第一次刪除緩存成功,然后更新數(shù)據(jù),但是這時候不知道怎么了,可能是線程阻塞了或者其它原因,導致還沒有開始更新數(shù)據(jù)庫,這時候另外一個線程Thread-B來讀取數(shù)據(jù)了,讀取到數(shù)據(jù)之后將數(shù)據(jù)放到緩存中,這時候Thread-A才開始更新數(shù)據(jù)庫,但是Thread-A在第二次刪除緩存的時候失敗了,此時就導致緩存中的數(shù)據(jù)是之前的舊數(shù)據(jù),與數(shù)據(jù)庫的數(shù)據(jù)不一致
問題二:假設(shè)修改的時候都沒問題,第二次刪除的緩存的時候都正常,這時候讀取數(shù)據(jù)的時候緩存里面肯定就沒有了,這時候就要從數(shù)據(jù)庫讀取,如果這時候一下子并發(fā)量很高,那么這些線程都要從數(shù)據(jù)庫中查詢,這時候數(shù)據(jù)庫都有可能直接掛掉
分布式鎖
查詢
//根據(jù)id查詢美食信息
public GoodsVO loadGoodsInfoById(Long id) {
//從redis中拿用戶信息
Object obj = redisTemplate.opsForValue().get(GOODS_KEY + id);
if(obj == null) {
//如果redis中不存在,就從數(shù)據(jù)庫中獲取
GoodsVO goods = loadGoodsFromDb(id);
return fileUser;
}
return JSONUtil.toBean(obj.toString(), GoodsVO.class);
}
//查詢緩存中沒有,這時候就要去數(shù)據(jù)庫中查詢
public String getGoodInfoFromDb(Long id) {
//此時這里要加的鎖和 modifyGoodsById()方法加的應該是同一把鎖,這樣才能保證雙寫一致性
boolean lock = redisLock.tryLock(GOODS_KEY+id);
if(!lock) {
//獲取鎖失敗,嘗試從緩存中獲取
Object obj = redisTemplate.opsForValue().get(GOODS_KEY + id);
if(obj != null){
return JSONUtil.toBean(obj.toString(), GoodsVO.class);
}else{
//直接返回一個操作頻繁的信息給前端
throw new GloableException("訪問頻繁,請稍后再試");
}
}
try{
//獲取到鎖了
//嘗試從緩存中拿
Object obj = redisTemplate.opsForValue().get(GOODS_KEY + id);
if(obj != null) {
return JSONUtil.toBean(obj.toString(), GoodsVO.class);
}
//緩存中沒有,從MySql中拿
GoodsVO goods = goodsMapper.selectById(id);
if(goods == null) {
//為了解決緩存穿透的問題
redisTemplate.opsForValue().set(GOODS_NULL_KEY + id);
return null;
}
//將數(shù)據(jù)庫查到的數(shù)據(jù)放到緩存中
redisTemplate.opsForValue().set(GOODS_KEY+id,JSONUtil.toJsonPrettyStr(goods));
return goods;
}finally{
//釋放鎖
redisLock.unLock(GOODS_KEY+goodsVO.getId());
}
}修改
//編輯美食信息
public void modifyGoodsById(GoodsVO goodsVO) throws Exception{
//嘗試獲取鎖
boolean lock = redisLock.tryLock(GOODS_KEY+goodsVO.getId());
if(!lock) {
//直接返回一個操作頻繁的信息給前端
throw new GloableException("更新數(shù)據(jù)失敗,請稍后再試.....");
}
try{
//獲取到鎖了
//更新用戶信息
goodsMapper.updateById(goodsVO);
//更新完之后,將數(shù)據(jù)庫最新的數(shù)據(jù)更新到緩存中
redisTemplate.opsForValue().set(GOODS_KEY+id,JSONUtil.toJsonPrettyStr(goodsVO));
}finally{
//釋放鎖
redisLock.unLock(GOODS_KEY+goodsVO.getId());
}
}現(xiàn)在第一次來訪問數(shù)據(jù),返現(xiàn)Redis中沒有,這時候就會去MySql中查,但是只有獲取到鎖的線程才可以去數(shù)據(jù)庫中查,所以此時只有一個線程訪問數(shù)據(jù)庫,這時候即使有線程要去修改數(shù)據(jù),由于鎖已經(jīng)被拿走了,無法獲取到鎖,也就無法修改,保證了數(shù)據(jù)一致性。
假設(shè)現(xiàn)在修改的線程獲取到鎖了。由于之前Redis中已經(jīng)有數(shù)據(jù)了,此時所有讀取數(shù)據(jù)的線程都從Redis中拿,當修改完數(shù)據(jù)之后,重新設(shè)置緩存,此時緩存中的數(shù)據(jù)就是最新的
以上就是分布式鎖解決緩存雙寫一致性的詳細內(nèi)容,更多關(guān)于分布式鎖解決緩存雙寫一致性的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis優(yōu)化token校驗主動失效的實現(xiàn)方案
在普通的token頒發(fā)和校驗中 當用戶發(fā)現(xiàn)自己賬號和密碼被暴露了時修改了登錄密碼后舊的token仍然可以通過系統(tǒng)校驗直至token到達失效時間,所以系統(tǒng)需要token主動失效的一種能力,所以本文給大家介紹了Redis優(yōu)化token校驗主動失效的實現(xiàn)方案,需要的朋友可以參考下2024-03-03

