亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java并發(fā)編程數(shù)據(jù)庫與緩存數(shù)據(jù)一致性方案解析

 更新時間:2022年04月02日 16:16:20   作者:Java知識圖譜  
這篇文章主要為大家介紹了Java并發(fā)編程中數(shù)據(jù)庫與緩存數(shù)據(jù)一致性解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪

一、序言

在分布式并發(fā)系統(tǒng)中,數(shù)據(jù)庫與緩存數(shù)據(jù)一致性是一項富有挑戰(zhàn)性的技術(shù)難點。本文將討論數(shù)據(jù)庫與緩存數(shù)據(jù)一致性問題,并提供通用的解決方案。

假設(shè)有完善的工業(yè)級分布式事務(wù)解決方案,那么數(shù)據(jù)庫與緩存數(shù)據(jù)一致性便迎刃而解,實際上,目前分布式事務(wù)不成熟。

二、不同的聲音

在數(shù)據(jù)庫與緩存數(shù)據(jù)一致解決方式中,有各種聲音。

  • 先操作數(shù)據(jù)庫后緩存還是先緩存后數(shù)據(jù)庫
  • 緩存是更新還是刪除

1、操作的先后順序

在并發(fā)系統(tǒng)中,數(shù)據(jù)庫與緩存雙寫場景下,為了追求更大的并發(fā)量,操作數(shù)據(jù)庫與緩存顯而易見不會同步進行。前者操作成功后者以異步的方式進行。

關(guān)系型數(shù)據(jù)庫作為成熟的工業(yè)級數(shù)據(jù)存儲方案,有完善的事務(wù)處理機制,數(shù)據(jù)一旦落盤,不考慮硬件故障,可以負責任的說數(shù)據(jù)不會丟失。

所謂緩存,無非是存儲在內(nèi)存中的數(shù)據(jù),服務(wù)一旦重啟,緩存數(shù)據(jù)全部丟失。既然稱之為緩存,那么時刻做好了緩存數(shù)據(jù)丟失的準備。盡管Redis有持久化機制,是否能夠保證百分之百持久化?Redis將數(shù)據(jù)異步持久化到磁盤有不可,緩存是緩存,數(shù)據(jù)庫是數(shù)據(jù)庫,兩個不同的東西。把緩存當數(shù)據(jù)庫使用是一件極其危險的事情。

從數(shù)據(jù)安全的角度來講,先操作數(shù)據(jù)庫,然后以異步的方式操作緩存,響應(yīng)用戶請求。

2、處理緩存的態(tài)度

緩存是更新還是刪除,對應(yīng)懶漢式和飽漢式,從處理線程安全實踐來講,刪除緩存操作相對難度低一些。如果在刪除緩存的前提下滿足了查詢性能,那么優(yōu)先選擇刪除緩存。

更新緩存盡管能夠提高查詢效率,然后帶來的線程并發(fā)臟數(shù)據(jù)處理起來較麻煩,序言引入MQ等其它消息中間件,因此非必要不推薦。

三、線程并發(fā)分析

理解線程并發(fā)所帶來問題的關(guān)鍵是先理解系統(tǒng)中斷,操作系統(tǒng)在任務(wù)調(diào)度時,中斷隨時都在發(fā)生,這是線程數(shù)據(jù)不一致產(chǎn)生的根源。以4和8線程CPU為例,同一時刻最多處理8個線程,然而操作系統(tǒng)管理的線程遠遠超過8個,因此線程們以一種看似并行的方式進行。

查詢數(shù)據(jù)

1、非并發(fā)環(huán)境

在非并發(fā)環(huán)境中,使用如下方式查詢數(shù)據(jù)并無不妥:先查詢緩存,如果緩存數(shù)據(jù)不存在,查詢數(shù)據(jù)庫,更新緩存,返回結(jié)果。

public BuOrder getOrder(Long orderId) {
    String key = ORDER_KEY_PREFIX + orderId;
    BuOrder buOrder = RedisUtils.getObject(key, BuOrder.class);
    if (buOrder != null) {
        return buOrder;
    }
    BuOrder order = getById(orderId);
    RedisUtils.setObject(key, order, 5, TimeUnit.MINUTES);
    return order;
}

如果在高并發(fā)環(huán)境中有一個嚴重缺陷:當緩存失效時,大量查詢請求涌入,瞬間全部打到DB上,輕則數(shù)據(jù)庫連接資源耗盡,用戶端響應(yīng)500錯誤,重則數(shù)據(jù)庫壓力過大服務(wù)宕機。

2、并發(fā)環(huán)境

因此在并發(fā)環(huán)境中,需要對上述代碼進行修改,使用分布式鎖。大量請求涌入時,獲得鎖的線程有機會訪問數(shù)據(jù)庫查詢數(shù)據(jù),其余線程阻塞。當查詢完數(shù)據(jù)并更新緩存,然后釋放鎖。等待的線程重新檢查緩存,發(fā)現(xiàn)能夠獲取到數(shù)據(jù),直接將緩存數(shù)據(jù)響應(yīng)。

這里提到分布式鎖,那么使用???????表鎖還是行鎖呢?使用分布式行鎖提高并發(fā)量;使用二次檢查機制,確保等待獲得鎖的線程能夠快速返回結(jié)果

@Override
public BuOrder getOrder(Long orderId) {
    /* 如果緩存不存在,則添加分布式鎖更新緩存 */
    String key = ORDER_KEY_PREFIX + orderId;
    BuOrder order = RedisUtils.getObject(key, BuOrder.class);
    if (order != null) {
        return order;
    }
    String orderLock = ORDER_LOCK + orderId;
    RLock lock = redissonClient.getLock(orderLock);
    if (lock.tryLock()) {
        order = RedisUtils.getObject(key, BuOrder.class);
        if (order != null) {
            LockOptional.ofNullable(lock).ifLocked(RLock::unlock);
            return order;
        }
        BuOrder buOrder = getById(orderId);
        RedisUtils.setObject(key, buOrder, 5, TimeUnit.MINUTES);
        LockOptional.ofNullable(lock).ifLocked(RLock::unlock);
    }
    return RedisUtils.getObject(key, BuOrder.class);
}

更新數(shù)據(jù)

1、非并發(fā)環(huán)境

非并發(fā)環(huán)境中,如下代碼盡管可能會產(chǎn)生數(shù)據(jù)不一致問題(數(shù)據(jù)被覆蓋)。盡管使用數(shù)據(jù)庫層面樂觀鎖能夠解決數(shù)據(jù)被覆蓋問題,然而無效更新流量依舊會流向數(shù)據(jù)庫。

public Boolean editOrder(BuOrder order) {
    /* 更新數(shù)據(jù)庫 */
    updateById(order);
    /* 刪除緩存 */
    RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId());
    return true;
}

2、并發(fā)環(huán)境

上面分析中使用數(shù)據(jù)庫樂觀鎖能夠解決并發(fā)更新中數(shù)據(jù)被覆蓋的問題,然而當同一行記錄被修改后,版本號發(fā)生改變,后續(xù)并發(fā)流向數(shù)據(jù)庫的請求為無效流量。減小數(shù)據(jù)庫壓力的首要策略是將無效流量攔截在數(shù)據(jù)庫之前。

使用分布式鎖能夠保證并發(fā)流量有序訪問數(shù)據(jù)庫,考慮到數(shù)據(jù)庫層面已經(jīng)使用了樂觀鎖,第二個及以后獲得鎖的線程操作數(shù)據(jù)庫為無效流量。

線程在獲得鎖時采用超時退出的策略,等待獲得鎖的線程超時快速退出,快速響應(yīng)用戶請求,重試更新數(shù)據(jù)操作。

public Boolean editOrder(BuOrder order) {
    String orderLock = ORDER_LOCK + order.getOrderId();
    RLock lock = redissonClient.getLock(orderLock);
    try {
        /* 超時未獲取到鎖,快速失敗,用戶端重試 */
        if (lock.tryLock(1, TimeUnit.SECONDS)) {
            /* 更新數(shù)據(jù)庫 */
            updateById(order);
            /* 刪除緩存 */
            RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId());
            /* 釋放鎖 */
            LockOptional.ofNullable(lock).ifLocked(RLock::unlock);
            return true;
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return false;
}

依賴環(huán)境

上述代碼使用了封裝鎖的工具類。

<dependency>
  <groupId>xin.altitude.cms</groupId>
  <artifactId>ucode-cms-common</artifactId>
  <version>1.4.3.2</version>
</dependency>

LockOptional根據(jù)鎖的狀態(tài)執(zhí)行后續(xù)操作。

四、先數(shù)據(jù)庫后緩存

數(shù)據(jù)一致性

1、問題描述

接下來討論先更新數(shù)據(jù)庫,后刪除緩存是否存在并發(fā)問題。

(1)緩存剛好失效
(2)請求A查詢數(shù)據(jù)庫,得一個舊值
(3)請求B將新值寫入數(shù)據(jù)庫
(4)請求B刪除緩存
(5)請求A將查到的舊值寫入緩存

上述并發(fā)問題出現(xiàn)的關(guān)鍵是第5步比第3、4步后發(fā)生,由操作系統(tǒng)中斷不確定因素可知,此種情況卻有發(fā)生的可能。

2、解決方式

從實際情況來看,將數(shù)據(jù)寫入Redis遠比將數(shù)據(jù)寫入數(shù)據(jù)庫耗時要短,盡管發(fā)生的概率較低,但仍會發(fā)生。

  • (1)增加緩存過期時間

增加緩存過期時間允許一定時間范圍內(nèi)臟數(shù)據(jù)存在,直到下一次并發(fā)更新出現(xiàn),可能會出現(xiàn)臟數(shù)據(jù)。臟數(shù)據(jù)會周期性存在。

  • (2)更新和查詢共用一把行鎖

更新和查詢共用一把行分布式鎖,上述問題不復(fù)存在。當讀請求獲取到鎖時,寫請求處于阻塞狀態(tài)(超時會快速失敗返回),能夠保證步驟5在步驟3之前進行。

  • (3)延遲刪除緩存

使用RabbitMQ延遲刪除緩存,去除步驟5的影響。使用異步的方式進行,幾乎不影響性能。

特殊情況

數(shù)據(jù)庫有事務(wù)機制保證操作成功與否;Redis單條指令具有原子性,然后組合起來卻不具備原子特征,具體來說是數(shù)據(jù)庫操作成功,然后應(yīng)用異常掛掉,導致Redis緩存未刪除。Redis服務(wù)網(wǎng)絡(luò)連接超時出現(xiàn)此問題。

如果設(shè)置有緩存過期時間,那么在緩存尚未過期前,臟數(shù)據(jù)一直存在。如果未設(shè)置過期時間,那么直到下一次修改數(shù)據(jù)前,臟數(shù)據(jù)一直存在。(數(shù)據(jù)庫數(shù)據(jù)已經(jīng)發(fā)生改變,緩存尚未更新)

解決方式

在操作數(shù)據(jù)庫前,向RabbitMQ寫入一條延遲刪除緩存的消息,然后執(zhí)行數(shù)據(jù)庫操作,執(zhí)行緩存刪除操作。不管代碼層面緩存是否刪除成功,MQ刪除緩存作為保底操作。

五、小結(jié)

上述方式提供的數(shù)據(jù)庫與緩存數(shù)據(jù)一致性解決方式,屬于耦合版,當然還有訂閱binlog日志的解耦版。解耦版由于增加了訂閱binlog組件,對系統(tǒng)穩(wěn)定性提出更高的要求。

數(shù)據(jù)庫與緩存一致性問題看似是解決數(shù)據(jù)問題,實質(zhì)上解決并發(fā)問題:在盡可能保證更多并發(fā)量的前提下,在保證數(shù)據(jù)庫安全的前提下,保證數(shù)據(jù)庫與緩存數(shù)據(jù)一致。

以上就是數(shù)據(jù)庫與緩存數(shù)據(jù)一致性方案解析的詳細內(nèi)容,更多關(guān)于數(shù)據(jù)庫緩存數(shù)據(jù)一致性的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Springboot-Shiro基本使用詳情介紹

    Springboot-Shiro基本使用詳情介紹

    這篇文章主要介紹了Springboot-Shiro基本使用詳情,文章根據(jù)官網(wǎng)依據(jù)官網(wǎng)快速搭建Quickstart,配置pom.xml依賴等操作,需要的小伙伴可以參考下面文章內(nèi)容
    2022-01-01
  • Java Optional<Foo>轉(zhuǎn)換成List<Bar>的實例方法

    Java Optional<Foo>轉(zhuǎn)換成List<Bar>的實例方法

    在本篇內(nèi)容里小編給大家整理的是一篇關(guān)于Java Optional<Foo>轉(zhuǎn)換成List<Bar>的實例方法,有需要的朋友們可以跟著學習下。
    2021-06-06
  • Java多線程中線程間的通信實例詳解

    Java多線程中線程間的通信實例詳解

    這篇文章主要介紹了Java多線程中線程間的通信實例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • 使用Java操作TensorFlow的方法

    使用Java操作TensorFlow的方法

    TensorFlow是一個功能強大且廣泛使用的框架,它不斷得到改進,并最近被引入新語言包括Java和JavaScript,這篇文章主要介紹了如何使用Java操作TensorFlow,需要的朋友可以參考下
    2023-05-05
  • Java如何優(yōu)雅替換if-else語句

    Java如何優(yōu)雅替換if-else語句

    當邏輯分支非常多的時候,if-else套了一層又一層,那么如何干掉過多的if-else,本文就詳細的介紹一下,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java中i++的一些問題總結(jié)

    Java中i++的一些問題總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java中i++的一些問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • Java swing實現(xiàn)支持錄音等功能的鋼琴程序

    Java swing實現(xiàn)支持錄音等功能的鋼琴程序

    這篇文章主要為大家詳細介紹了Java swing實現(xiàn)鋼琴程序,支持錄音等功能的Java鋼琴源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • 解決Spring boot 嵌入的tomcat不啟動問題

    解決Spring boot 嵌入的tomcat不啟動問題

    這篇文章主要介紹了解決Spring boot 嵌入的tomcat不啟動問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • 解決Spring boot整合mybatis,xml資源文件放置及路徑配置問題

    解決Spring boot整合mybatis,xml資源文件放置及路徑配置問題

    這篇文章主要介紹了解決Spring boot整合mybatis,xml資源文件放置及路徑配置問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • idea與eclipse項目相互導入的過程(圖文教程)

    idea與eclipse項目相互導入的過程(圖文教程)

    這篇文章主要介紹了idea與eclipse項目相互導入的過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03

最新評論