1. 程式人生 > >聊聊db和快取一致性的5種實現方式

聊聊db和快取一致性的5種實現方式

資料儲存在資料庫中,為了加快業務訪問的速度,我們將資料庫中的一些資料放在快取中,那麼問題來了,如何確保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中當前資料的快取

存在的問題

  1. 上面u3成功,u4失敗,會導致刪除快取失敗,導致快取中資料和db資料會不一致。
  2. 如果同時有很多執行緒到達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事務

存在的問題

  1. 更新資料的執行緒執行u1成功之後,u2還未執行時,此時獲取快取的執行緒剛好執行了c1到c3的邏輯,此時會將舊的資料放入redis,導致redis和db資料不一致
  2. 同樣存在方案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中對應的快取清除。

存在的問題

  1. 更新db和清理redis中的快取之間存在一定的時間延遲,這段時間內,redis快取的資料是舊的,也就是說這段時間內db和快取資料是不一致的,但是最終會一致,這個不一致的時間可能比較小(這個需要看訊息消費的效率了)
  2. 同樣存在方案2中說到的問題:如果同時有很多執行緒到達c2發現快取不存在,同時請求c3訪問db,會對db造成很大的壓力

關於可靠訊息的,可以看

  • 聊聊mq的使用場景
  • 聊聊業務系統中投遞訊息到mq的幾種方式
  • 談談mq訊息消費的幾種方式
  • 如何確保訊息至少消費一次?

方式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事務

獲取快取邏輯

/*公眾號:路人甲Java
* 工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!
* 堅信用技術改變命運,讓家人過上更體面的生活。*/
public class CacheUtil {

    //根據key獲取快取中對應的value
    public static String getCache(String key) throws InterruptedException {
        String value = RedisUtils.get(key);
        if (value != null) {
            return value;
        }
        //過期時間為當前時間+5秒
        String expireTimeKey = key + "ExpireTime";
        long expireTimeValue = System.currentTimeMillis() + 5000;
        //setnx是原子操作,所以只有一個會成功
        int setnx = RedisUtils.setnx(expireTimeKey, expireTimeValue + "");
        if (setnx == 0) {
            expireTimeValue = Long.valueOf(RedisUtils.get(expireTimeKey));
            //如果expireTimeValue小於當前時間,說明expireTimeKey過期了,將其刪除
            if (System.currentTimeMillis() > expireTimeValue) {
                //將expireTimeKey對應的刪除
                RedisUtils.del(expireTimeKey);
            } else {
                //休眠1秒繼續獲取
                TimeUnit.SECONDS.sleep(1);
            }
            //重試
            return getCache(key);
        } else {
            //1. 開啟db事務
            start transaction;
            //2. 執行update  t set v = #v# where t.key = #key# for update; 將v的值賦值給value
            update  t set v = #v# where t.key = #key# for update;
            RedisUtils.set(key, value);
            //3.提交db事務
            commit transaction;
        }
        return value;
    }

    //redis工具類,內部方法為虛擬碼
    public static class RedisUtils {
        //根據key獲取value
        public static String get(String key) {
            return null;
        }

        //設定key對應的value
        public static void set(String key, String value) {
        }

        //刪除redis中一個key對應的值
        public static void del(String key) {
        }

        //setnx的含義就是SET if Not Exists,該方法是原子的,如果key不存在,
        //則設定當前key成功,返回1;如果當前key已經存在,則設定當前key失敗,返回0
        public static int setnx(String key, String value) {
            return 1;
        }
    }
}

這種方式可以確保db和redis中快取同一時間強一致。

expireTimeKey為了防止某些線執行緒執行RedisUtils.setnx(expireTimeKey, expireTimeValue + "");返回1,表示setnx成功了,然後執行下一行程式碼的時候系統後掛了,會導致將db資料載入到redis中失敗,程式碼:if (System.currentTimeMillis() > expireTimeValue)是給其他執行緒機會,可以獲取這個過期時間,發現過期之後直接刪掉,這樣其他執行緒才有機會將db資料load到redis中。

工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!喜歡的請關注公眾號:路人甲Java

相關推薦

聊聊db快取一致性5實現方式

資料儲存在資料庫中,為了加快業務訪問的速度,我們將資料庫中的一些資料放在快取中,那麼問題來了,如何確保db和快取中資料的一致性呢?我們列出了5種方法,大家都瞭解一下,然後根據業務自己選擇。 方案1 獲取快取邏輯 使用過定時器,定時重新整理redis中的快取。 db更新資料邏輯 更新資料不用考慮快取中的資料,直

單例模式的5實現方式

ber none jvm hid dem abs spl null uic 1.餓漢模式(線程安全,調用效率高,但是不能延時加載): package com.yanwu.www.demo; /* * 測試單例模式 * * 餓漢模式 * * @author

安卓專案實戰之設定Activity跳轉動畫的5實現方式

前言 在介紹activity的切換動畫之前我們先來說明一下實現切換activity的兩種方式: 1,呼叫startActivity方法啟動一個新的Activity並跳轉其頁面 2,呼叫finish方法銷燬當前的Activity返回上一個Activity介面 當呼叫startActiv

Android EditText限制輸入字元的5實現方式

         最近專案要求限制密碼輸入的字元型別, 例如不能輸入中文。   現在總結一下EditText的各種實現方式,  以比較各種方法的優劣。  第一種方式:  設定EditText的inputType屬性,可以通過xml或者java檔案來設定。假如我要設定為顯示

Java設計模式(二):單例模式的5實現方式,以及在多執行緒環境下5建立單例模式的效率

這段時間從頭溫習設計模式。記載下來,以便自己複習,也分享給大家。 package com.iter.devbox.singleton; /** * 餓漢式 * @author Shearer * */ public class SingletonDemo1 {

5.6-全棧Java筆記:內部類的四實現方式

java一般情況,我們把類定義成獨立的單元。有些情況下,我們把一個類放在另一個類的內部定義,稱為內部類(innerclasses)。內部類的作用1.內部類提供了更好的封裝。只能讓外部類直接訪問,不允許同一個包中的其他類直接訪問。2.內部類可以直接訪問外部類的私有屬性,內部類被當成其外部類的成員。 但外部類不能

【小家java】SessionCookie的區別聯絡、分散式session的幾實現方式

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

servlet的介紹 & xml中配置 以及 & 三實現方式(補充設定瀏覽器不快取的方法)

開始時間:2018年10月13日20:53:30 | 2018年10月14日16:10:56 結束時間:2018年10月13日21:53:30 | 2018年10月14日17:02:23 累計時間:2小時 備註:幾乎每一句話都很有收穫,複習的時候務必要仔細一點 Servlet

多執行緒同步之——兩個執行緒序列順序列印奇數偶數的兩實現

題目:一道經典的執行緒併發的問題,執行緒a列印1、3、5……,執行緒b列印2、4、6……,兩個執行緒交替執行輸出1、2、3、4、5、6…… 要點: package com.test; import java.util.concurrent.locks.

併發伺服器三實現方式之程序、執行緒select

  前言:剛開始學網路程式設計,都會先寫一個客戶端和服務端,不知道你們有沒有試一下:再開啟一下客戶端,是連不上服務端的。還有一個問題不知道你們發現沒:有時啟伺服器,會提示“Address already in use”,過一會就好了,想過為啥麼?在這篇部落格會解釋這個問題。   但現實的伺服器都會連很多客戶

斐波那契數列的兩實現方式(遞迴迴圈)

public class Fibonacci { public static long F(int N){ int f0 = 0; int f1 = 1; int fn = 0; i

Java多執行緒迴圈列印ABC的5實現方法

https://blog.csdn.net/weixin_39723337/article/details/80352783   題目:3個執行緒迴圈列印ABC,其中A列印3次,B列印2次,C列印1次,迴圈列印2輪一.Synchronized同步法思路:使用synchronized、wait、n

【併發程式設計】CPU cache結構快取一致性(MESI協議)

一、cache cpu cache已經發展到了三級快取結構,基本上現在買的個人電腦都是L3結構。 1. cache的意義 為什麼需要CPU cache?因為CPU的頻率太快了,快到主存跟不上,這樣在處理器時鐘週期內,CPU常常需要等待主存,浪費資源。所以cac

servlet的介紹 & xml中配置 以及 & 三實現方式(補充設定瀏覽器不快取的方法)

開始時間:2018年10月13日20:53:30 | 2018年10月14日16:10:56 結束時間:2018年10月13日21:53:30 | 2018年10月14日17:02:23 累計時間:3小時 動態資源: Servlet 簡單介紹:   Servlet

Redis叢集的兩實現方式之Redis ShardingRedis Cluster

  在當前網際網路的背景下,企業的業務需求越來越大,所以一般的業務+資料庫已經不能滿足需求了,所以大批的記憶體式資料庫應運而生,Redis是一個應用比較廣泛的資料庫。用它來實現分散式的操作得心應手。目前有兩種實現分散式的方式,基於Redisx2的Redis Sharding,

(十六)java併發程式設計--執行緒的死鎖解決方案(生產者消費者幾實現方式)

上一篇中,主要了解了什麼時候死鎖,並且提出死鎖的一個解決方案,多個鎖要按照一定的順序來。 本片主要是利用生產者消費者模式解決執行緒的死鎖。 多執行緒生產者和消費者一個典型的多執行緒程式。一個生產者生產提供消費的東西,但是生產速度和消費速度是不同的。這就需要讓

二、C++迭代器的兩實現方式 (Range forC#、Java中的foreach)

一、迭代器概述   這個標題其實有點“標題黨”的含義,因為C++在標準庫中的實現迭代器的方式只有一種,也就是為類定義begin()和end()函式,C++11增加了range for語句,可以用來遍歷迭代器中的元素。實現迭代器的第二種方式,就是用C++模擬C#和Java中的

淺談Activity之啟動方式(5啟動方式隱式啟動)

Activity在Android APP中的重要性不言而喻,那麼瞭解Activity的幾種不同的啟動方式對設計出好的App至關重要! 眾所周知Activity的啟動方式有如下幾種 1.在“Home”下點選圖示,啟動應用程式的首個Activity。我們稱之為主Activ

聚類分析(K-means 層次聚類基於密度DBSCAN演算法三實現方式)

之前也做過聚類,只不過是用經典資料集,這次是拿的實際資料跑的結果,效果還可以,記錄一下實驗過程。 首先: 確保自己資料集是否都完整,不能有空值,最好也不要出現為0的值,會影響聚類的效果。 其次: 想好要用什麼演算法去做,K-means,層次聚類還是基於密

js將number數值轉化成為貨幣格式,貨幣格式化,金錢過濾器,貨幣過濾器,vue貨幣過濾金錢過濾全域性區域性兩實現方式

js中使用 js程式碼 const digitsRE = /(\d{3})(?=\d)/g function currency (value, currency, decimals) { value = parseFloat(value) if (