當(dāng)前沒有框架能夠保證redis的數(shù)據(jù)和數(shù)據(jù)庫的完全一致性,所以需要 我們自己在性能和一致性上作取舍。
使用到緩存的場景
這里講到的是緩存和數(shù)據(jù)庫的一致性問題。
當(dāng)查詢數(shù)據(jù)庫數(shù)據(jù)的時候,才涉及到緩存的利用上,所以緩存的引入是為了讓查詢數(shù)據(jù)的時候提高效率;
而當(dāng)發(fā)生增、刪、改數(shù)據(jù)的時候,對于緩存來說是要讓數(shù)據(jù)庫和緩存發(fā)生一致性的改變,進(jìn)而能讓緩存在數(shù)據(jù)查詢時候能繼續(xù)起作用。
下圖就是兩種在redis緩存數(shù)據(jù)庫內(nèi)容時的使用。
對于緩存的一致性操作的選擇
明確了緩存的使命------在查詢數(shù)據(jù)庫信息時,提前將數(shù)據(jù)進(jìn)行緩存,既減少了數(shù)據(jù)庫io,也提高了查詢效率。
那么如一個節(jié)點(diǎn)的兩個圖:
緩存的生成,是在首次查詢或者緩存過期時間到或者緩存被其他業(yè)務(wù)刪除,進(jìn)而需要在數(shù)據(jù)庫查詢完畢之后,將數(shù)據(jù)庫內(nèi)容同步到緩存中。
那當(dāng)數(shù)據(jù)發(fā)生增刪改時,涉及到兩個方向的選擇
1、是刪除還是更新redis緩存
2、是先刪除\更新緩存,再操作數(shù)據(jù)庫;還是先操作數(shù)據(jù)庫,再刪除\更新緩存。
下面一一討論
(1)數(shù)據(jù)發(fā)生變化,刪除還是更新redis緩存
更新:數(shù)據(jù)發(fā)生增刪改,把緩存的內(nèi)容 重新用redis的set操作,賦予新值。
刪除:在查詢時,發(fā)現(xiàn)緩存已經(jīng)不存在,去數(shù)據(jù)庫查詢之后,同步到redis緩存。
1、如何選擇,可以使用排除方法,首先討論更新。
(1)、redis更新是放在數(shù)據(jù)庫操作的前。
線程1: redis更新 ---------------------------數(shù)據(jù)庫更新
線程2:redis更新-------------------------數(shù)據(jù)庫更新
(2)、redis更新是放在數(shù)據(jù)庫操作的后。
線程1: 數(shù)據(jù)庫更新 ---------------------------redis更新
線程2:數(shù)據(jù)庫更新-------------------------redis更新
可以看到這樣的場景,當(dāng)線程1和2完成之后,此時緩存和數(shù)據(jù)庫是不一致的。
2、寫多讀少:上面一直討論緩存的目的是給讀操作使用的,那么每修改一次就寫入一次緩存,無疑大大的做無用功。
3、如果緩存是需要計(jì)算加工的數(shù)據(jù):即查詢到數(shù)據(jù)庫值之后,緩存的值需要再做計(jì)算,那么每一次修改將很損耗性能。
總結(jié):綜合上面三點(diǎn),刪除要比更新的效率和避免數(shù)據(jù)不一致的效果更好。
(1)數(shù)據(jù)發(fā)生變化,先刪除還是先操作數(shù)據(jù)庫
1、 先更新數(shù)據(jù)庫,再刪除緩存
2、先刪除緩存,再更新數(shù)據(jù)庫
下面一一討論:
(1)先更新數(shù)據(jù)庫,再刪除緩存
①如果更新數(shù)據(jù)庫失敗,那么程序捕獲異常,之后不進(jìn)行刪除緩存操作。
②如果更新數(shù)據(jù)庫成功,但是刪除緩存失敗。
針對②可以采用異步操作的辦法,把刪除失敗的key給到隊(duì)列當(dāng)中,由線程池來執(zhí)行失敗重試。
或者可以利用canal 監(jiān)聽數(shù)據(jù)庫修改 進(jìn)而發(fā)生刪除的操作。
(2)先刪除緩存,再更新數(shù)據(jù)庫
這種方式可能存在以下三種異常情況
1、刪除緩存失敗,這時可以通過程序捕獲異常,直接返回結(jié)果,不再繼續(xù)更新數(shù)據(jù)庫,所以不會出現(xiàn)數(shù)據(jù)不一致的問題
2、刪除緩存成功,更新數(shù)據(jù)庫失敗。此時不會發(fā)生數(shù)據(jù)不一致現(xiàn)象。
3、下圖的場景,兩個線程執(zhí)行完成之后,會導(dǎo)致redis和數(shù)據(jù)庫的不一致。
解決第三種情況可以使用 延時雙刪的策略,如下圖:
這里討論一下延時雙刪如何解決第三種異常情況,
1、線程2在查詢數(shù)據(jù)庫舊值之后,更新緩存,此時數(shù)據(jù)不一致,所以需要再次刪除。
2、休眠時間,是為了在線程2寫入緩存之后,線程1才發(fā)生刪除緩存,不延時,可能導(dǎo)致線程2還沒寫入緩存,線程1就完成了刪除緩存,那么最終的結(jié)果 還是數(shù)據(jù)不一致。
3、無論如何線程2并利用的是舊數(shù)據(jù),這點(diǎn)沒辦法更改這點(diǎn)容錯率還是支持的,畢竟要是發(fā)生在刪除緩存前一秒還會利用緩存,此時數(shù)據(jù)還是最新數(shù)據(jù),而刪除緩存之后的一秒,才會到數(shù)據(jù)庫去查最新數(shù)據(jù),這個時間差的代價無論如何一定存在。
偽代碼:
public void update(String key, Object data) {
// 首先刪除緩存
redisCache.delKey(key);
// 更新數(shù)據(jù)庫
db.updateData(data);
// 休眠一段時間,時間依據(jù)數(shù)據(jù)的讀取耗費(fèi)的時間而定
Thread.sleep(500);
// 再次刪除緩存
redisCache.delKey(key);
總結(jié)
1、選擇刪除緩存 而不是更新
2、如果先修改數(shù)據(jù)庫,再刪除緩存,緩存刪除成功的機(jī)制可以用異步的失敗重試機(jī)制。
3、如果先刪除緩存,再修改數(shù)據(jù)庫,可以使用延時雙刪的機(jī)制。文章來源:http://www.zghlxwxcb.cn/news/detail-400976.html
文章摘錄至https://blog.csdn.net/chanmufeng/article/details/122933214文章來源地址http://www.zghlxwxcb.cn/news/detail-400976.html
到了這里,關(guān)于redis和數(shù)據(jù)庫的一致性問題的解決方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!