來源公眾號路人甲Java
作者路人甲Java
數據存儲在資料庫中,為了加快業務訪問的速度,我們將資料庫中的一些數據放在緩存中,那麼問題來了,如何確保db和緩存中數據的一致性呢?我們列出了5種方法,大家都了解一下,然後根據業務自己選擇。
方案1
獲取緩存邏輯
使用過定時器,定時刷新redis中的緩存。
db更新數據邏輯
更新數據不用考慮緩存中的數據,直接更新數據就可以了
存在的問題
緩存中數據和db中數據一致性可能沒有那麼及時,不過最終在某個時間點,數據是一致的。
方案2
獲取緩存邏輯
c1:根據key在redis中獲取對應的value
c2:如果value存在,直接返回value;若value不存在,繼續下面步驟
c3:從資料庫獲取值,賦值給value,然後將key->value放入redis,返回value
更新db邏輯
u1:開始db事務
u2:更新數據
u3:提交db事務
u4:刪除redis中當前數據的緩存
存在的問題
- 上面u3成功,u4失敗,會導致刪除緩存失敗,導致緩存中數據和db數據會不一致。
- 如果同時有很多線程到達c2發現緩存不存在,同時請求c3訪問db,會對db造成很大的壓力
方案3
獲取緩存邏輯
c1:根據key在redis中獲取對應的value
c2:如果value存在,直接返回value;若value不存在,繼續下面步驟
c3:從資料庫獲取值,賦值給value,然後將key->value放入redis,返回value
更新db邏輯
u1:刪除redis中當前數據的緩存
u2:開始db事務
u3:更新數據
u4:提交db事務
存在的問題
- 更新數據的線程執行u1成功之後,u2還未執行時,此時獲取緩存的線程剛好執行了c1到c3的邏輯,此時會將舊的數據放入redis,導致redis和db數據不一致
- 同樣存在方案2中說到的問題:如果同時有很多線程到達c2發現緩存不存在,同時請求c3訪問db,會對db造成很大的壓力
方案4
對方案2做改進,確保db更新成功之後,刪除緩存操作一定會執行,我們可以通過可靠消息來實現,可靠消息可以確保更新db操作和刪除redis中緩存最終要麼都成功要麼都失敗,依靠的是最終一致性來實現的。
改進之後過程如下。
獲取緩存邏輯
c1:根據key在redis中獲取對應的value
c2:如果value存在,直接返回value;若value不存在,繼續下面步驟
c3:從資料庫獲取值,賦值給value,然後將key->value放入redis,返回value
更新db邏輯
u1:開始db事務
u2:更新數據
u3:投遞刪除redis緩存的消息
u4:提交db事務
消息消費者-清理redis緩存的消費者
接受到清理redis緩存的消息之後,將redis中對應的緩存清除。
存在的問題
- 更新db和清理redis中的緩存之間存在一定的時間延遲,這段時間內,redis緩存的數據是舊的,也就是說這段時間內db和緩存數據是不一致的,但是最終會一致,這個不一致的時間可能比較小(這個需要看消息消費的效率了)
- 同樣存在方案2中說到的問題:如果同時有很多線程到達c2發現緩存不存在,同時請求c3訪問db,會對db造成很大的壓力
方式5
我們先了解一些知識。
redis中幾個方法
get(key)
獲取key的值,如果存在,則返回;如果不存在,則返回nil
setnx(key,value)
setnx的含義就是SET if Not Exists,該方法是原子的,如果key不存在,則設置當前key成功,返回1;如果當前key已經存在,則設置當前key失敗,返回0
del(key)
將key對應的值從redis中刪除
資料庫相關知識
select v from t where t.key = #key# for update;
update t set v = #v# where t.key = #key#;
上面兩個sql會相互阻塞,直到其中一個提交之後,另外一個才可以繼續執行。
下面我們就通過上面的知識來實現db和緩存強一致性。
更新數據邏輯
1.打開db事務
2.update t set v = #v# where t.key = #key#;
3.根據key刪除redis中的緩存:RedisUti.del(key);
4.提交db事務
獲取緩存邏輯
這種方式可以確保db和redis中緩存同一時間強一致,有問題的可以留言交流!