欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Mysql中保證緩存與數(shù)據(jù)庫的雙寫一致性

 更新時間:2024年03月27日 10:42:45   作者:開心就好啦啦啦  
在一些高并發(fā)場景下,為了提升系統(tǒng)的性能,我們通常會將數(shù)據(jù)存儲在 Redis 緩存中,并通過 Redis 緩存來提高系統(tǒng)的讀取速度,這篇文章主要介紹了詳解Mysql中保證緩存與數(shù)據(jù)庫的雙寫一致性,需要的朋友可以參考下

概述

MySQL 和 Redis 都是常見的數(shù)據(jù)存儲方案,MySQL 用于存儲結構化數(shù)據(jù),Redis 用于存儲非結構化數(shù)據(jù)。在一些高并發(fā)場景下,為了提升系統(tǒng)的性能,我們通常會將數(shù)據(jù)存儲在 Redis 緩存中,并通過 Redis 緩存來提高系統(tǒng)的讀取速度。但是,Redis 緩存中的數(shù)據(jù)是不穩(wěn)定的,可能會隨時被刪除或者被更新,因此需要和 MySQL 中的數(shù)據(jù)進行同步,保證數(shù)據(jù)的一致性。

但是使用過緩存的人都應該知道,在實際應用場景中,要想實時刻保證緩存和數(shù)據(jù)庫中的數(shù)據(jù)一樣,很難做到。 基本上都是盡可能讓他們的數(shù)據(jù)在絕大部分時間內(nèi)保持一致,并保證最終是一致的。

同步策略

首先介紹一下雙寫一致性·當修改了數(shù)據(jù)庫的數(shù)據(jù)也要同時更新緩存的數(shù)據(jù),緩存和數(shù)據(jù)庫

四種同步策略:
想要保證緩存與數(shù)據(jù)庫的雙寫一致,一共有4種方式,即4種同步策略:
1. 先更新緩存,再更新數(shù)據(jù)庫;
2. 先更新數(shù)據(jù)庫,再更新緩存;
3. 先刪除緩存,再更新數(shù)據(jù)庫;
4. 先更新數(shù)據(jù)庫,再刪除緩存。

從這4種同步策略中,我們需要作出比較的是:

  • 更新緩存與刪除緩存哪種方式更合適?
  • 應該先操作數(shù)據(jù)庫還是先操作緩存?

更新緩存還是刪除緩存:

下面,我們來分析一下,應該采用更新緩存還是刪除緩存的方式。

1.更新緩存

  • 優(yōu)點:每次數(shù)據(jù)變化都及時更新緩存,所以查詢時不容易出現(xiàn)未命中的情況。
  • 缺點:更新緩存的消耗比較大。如果數(shù)據(jù)需要經(jīng)過復雜的計算再寫入緩存,那么頻繁的更新緩存,就會影響服務器的性能。如果是寫入數(shù)據(jù)頻繁的業(yè)務場景,那么可能頻繁的更新緩存時,卻沒有業(yè)務讀取該數(shù)據(jù)。

2.刪除緩存

  • 優(yōu)點:操作簡單,無論更新操作是否復雜,都是將緩存中的數(shù)據(jù)直接刪除。
  • 缺點:刪除緩存后,下一次查詢緩存會出現(xiàn)未命中,這時需要重新讀取一次數(shù)據(jù)庫。

從上面的比較來看,一般情況下,刪除緩存是更優(yōu)的方案。

先操作數(shù)據(jù)庫還是緩存:

下面,我們再來分析一下,應該先操作數(shù)據(jù)庫還是先操作緩存。

案例一、先刪除緩存,在更新數(shù)據(jù)庫

初始時,緩存和數(shù)據(jù)庫均為10。

如上圖,先刪除緩存,再更新數(shù)據(jù)庫,可能會出現(xiàn)的問題:

  • 線程1刪除緩存
  • 線程2查詢緩存未命中,查詢數(shù)據(jù)庫
  • 寫入緩存的值為10,
  • 線程1再進行更新數(shù)據(jù)庫,值為20

此時數(shù)據(jù)庫為更新過的值20,而緩存還是舊值10,此時出現(xiàn)了數(shù)據(jù)庫和緩存數(shù)據(jù)不一致情況。

案例二 先操作數(shù)據(jù)庫,再刪除緩存

如上圖,先刪除緩存,再更新數(shù)據(jù)庫,可能會出現(xiàn)的問題:

  • 線程1查詢緩存未命中,查詢數(shù)據(jù)庫
  • 線程2更新數(shù)據(jù)庫為20,
  • 線程2刪除緩存
  • 線程1寫入緩存值為10

此時數(shù)據(jù)庫為更新過的值20,而緩存還是舊值10,此時出現(xiàn)了數(shù)據(jù)庫和緩存數(shù)據(jù)不一致情況。

經(jīng)過案例一和案例二的比較,先刪除緩存和先更新數(shù)據(jù)庫都會出現(xiàn)問題。

延時雙刪策略(不推薦)

在寫庫前后都進行redis.del(key)操作,并且設定合理的超時時間。

偽代碼如下:

public void write( String key, Object data ){
    redis.delKey(key);
    db.updateData(data); 
    Thread.sleep(500);
    redis.delKey(key);
}

問題:這個500毫秒怎么確定的,具體該休眠多久時間呢?

需要評估自己的項目的讀數(shù)據(jù)業(yè)務邏輯的耗時。這么做的目的,就是確保讀請求結束,寫請求可以刪除讀請求造成的緩存臟數(shù)據(jù)。當然這種策略還要考慮redis和數(shù)據(jù)庫主從同步的耗時。另外這種策略也會可能會有臟數(shù)據(jù)的風險,而且還會消耗不必要的性能。

在實際場景中,并不推薦延時雙刪策略,一方面可能會有臟數(shù)據(jù)的風險,而且還會消耗不必要的性能。

雖然先更新數(shù)據(jù)庫,再刪除緩存也是會出現(xiàn)數(shù)據(jù)不一致性的問題,但是在實際中,這個問題出現(xiàn)的概率并不高。因為緩存的寫入通常要遠遠快于數(shù)據(jù)庫的寫入,所以在實際中很難出現(xiàn)請求 B 已經(jīng)更新了數(shù)據(jù)庫并且刪除了緩存,請求 A 才更新完緩存的情況。所以,「先更新數(shù)據(jù)庫 + 再刪除緩存」的方案,是可以保證數(shù)據(jù)一致性的。

但是,為了確保萬無一失,在更新完緩存時,給緩存加上較短的過期時間,這樣即時出現(xiàn)緩存不一致的情況,緩存的數(shù)據(jù)也會很快過期,對業(yè)務還是能接受的。另外在更新緩存中加入過期時間,這樣就算出現(xiàn)了緩存和數(shù)據(jù)庫不一致問題,但最終是一致的。

使用分布式鎖實現(xiàn)雙寫一致性

分別在寫數(shù)據(jù)和讀數(shù)據(jù)加分布式鎖,保證同一時間只運行一個請求更新緩存(保證讀寫串行化),就會不會產(chǎn)生并發(fā)問題了,這樣就能保證redis和mysql的數(shù)據(jù)強一致性。

但是這樣的話讀操作和寫操作都需要加鎖,效率就會大大降低。其實在真實場景中放入緩存中的數(shù)據(jù)一般是讀多寫少,如果是讀少寫多,那完全可以不用緩存,直接操作數(shù)據(jù)庫了。

使用讀寫鎖實現(xiàn)雙寫一致性

在讀多寫少的場景下,可以使用讀鎖和寫鎖的機制。

  • 共享鎖:讀鎖readLock,加鎖之后,其他線程可以共享讀操作,寫互斥
  • 排他鎖:獨占鎖writeLock也加寫鎖,加鎖之后,堵塞其他線程讀寫操作。

使用redisson中的讀寫鎖實現(xiàn)雙寫一致性

想要拿到共享鎖或者排他鎖,都需要先拿到讀寫鎖。通過固定代碼可以拿到讀寫鎖。

RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");

隨后分別拿到共享鎖和排他鎖。(注意兩個鎖需要是同一把讀寫鎖)

RLock readLock = readWriteLock.readLock();
RLock writeLock = readWriteLock.writeLock();

讀操作加入讀鎖(共享鎖)

public void getById(Integer id){
  RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");
  RLock readLock = readWriteLock.readLock();
  try{
    readLock.lock();
    System.out.println("readLock...");
    Item item = (Item) redisTemplate.opsForValue().get("item"+id);
    if(item != null){
      return item;
    }
    item = new Item(id, "手機", "手機", 60.00);
    redisTemplate.opsForValue().set("item"+id, item);
    return item;
  }finally{
    readLock.unlock();
  }
}

寫操作加入寫鎖(排他鎖)

public void updateById(Integer id){
  RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");
  RLock writeLock = readWriteLock.writeLock();
  try{
    writeLock.lock();
    System.out.println("writeLock...");
    Item item = new Item(id, "手機", "手機", 100.00);
    try{
      Thread.sleep(2000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    redisTemplate.delete("item"+id);
  }finally{
    writeLock.unlock();
  }
}

可以實現(xiàn)強一致性方案,雖然比分布式鎖好一點,但是在高并發(fā)場景下性能也比較低。

使用消息隊列異步通知

如果允許緩存中的數(shù)據(jù)在短時間內(nèi)可以跟數(shù)據(jù)庫數(shù)據(jù)不一致的情況下,可以使用異步通知的方案,可以保證最終一致性。

為了解決雙寫一致性的問題,我們可以引入消息隊列,比如RabbitMQ,來異步更新Redis。將操作同一資源的請求,打到同一個隊列中。

當有數(shù)據(jù)變動時,我們先操作數(shù)據(jù)庫,然后通過消息隊列發(fā)送消息到一個緩存更新的隊列中,異步更新緩存。這種方式能夠讓寫操作變得更加高效,并且避免了高并發(fā)下的緩存與數(shù)據(jù)庫數(shù)據(jù)不一致的問題。

訂閱Mysql的Binlog文件(可借助Canal來進行)

另一種更為可靠的方法是使用MySQL的binlog。我們可以使用Maxwell或者Canal等工具,實時解析binlog,然后更新Redis。

這種方案的好處是即使應用程序崩潰,也不會丟失binlog,因此能夠保證最終的數(shù)據(jù)一致性。但是,這種方案的實現(xiàn)比較復雜,需要對MySQL的內(nèi)部機制有深入的理解。

總結

允許延時一致的業(yè)務,采用異步通知

  • 使用MQ中間件,更新數(shù)據(jù)之后,通知緩存更新,將操作同一資源的請求,打到同一個隊列中。
  • 利用canal中間件,不需要修改業(yè)務代碼,偽裝為mysql的一個從節(jié)點,canal通過讀取binlog數(shù)據(jù)更新緩存

強一致性,采用Redisson提供的讀寫過

在讀多寫少的場景下,可以使用讀鎖和寫鎖的機制。

  • 共享鎖:讀鎖readLock,加鎖之后,其他線程可以共享讀操作,寫互斥
  • 排他鎖:獨占鎖writeLock也加寫鎖,加鎖之后,堵塞其他線程讀寫操作。

到此這篇關于Mysql中如何保證緩存與數(shù)據(jù)庫的雙寫一致性的文章就介紹到這了,更多相關保證緩存與數(shù)據(jù)庫的雙寫一致性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • MySQL中實現(xiàn)分頁操作的實戰(zhàn)指南

    MySQL中實現(xiàn)分頁操作的實戰(zhàn)指南

    MySQL的分頁似乎一直是個問題,下面這篇文章主要給大家介紹了關于MySQL中實現(xiàn)分頁操作的相關資料,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • ubuntu 15.04下mysql開放遠程3306端口

    ubuntu 15.04下mysql開放遠程3306端口

    這篇文章主要為大家詳細介紹了ubuntu 15.04開放mysql遠程3306端口的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • MySQL實現(xiàn)查詢某個字段含有字母數(shù)字的值

    MySQL實現(xiàn)查詢某個字段含有字母數(shù)字的值

    這篇文章主要介紹了MySQL實現(xiàn)查詢某個字段含有字母數(shù)字的值方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • mysql存儲過程 在動態(tài)SQL內(nèi)獲取返回值的方法詳解

    mysql存儲過程 在動態(tài)SQL內(nèi)獲取返回值的方法詳解

    本篇文章是對mysql存儲過程在動態(tài)SQL內(nèi)獲取返回值進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06
  • mysql8.0無法使用group by的問題及解決

    mysql8.0無法使用group by的問題及解決

    這篇文章主要介紹了mysql8.0無法使用group by的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • Mysql全文搜索match against的用法

    Mysql全文搜索match against的用法

    全文檢索在 MySQL 中就是一個 FULLTEXT 類型索引。FULLTEXT 索引用于 MyISAM 表,可以在 CREATE TABLE 時或之后使用 ALTER TABLE 或 CREATE INDEX 在 CHAR、 VARCHAR 或 TEXT 列上創(chuàng)建
    2011-10-10
  • MySQL?表分區(qū)步驟示例詳解

    MySQL?表分區(qū)步驟示例詳解

    MySQL表分區(qū)是一種數(shù)據(jù)庫管理技術,用于將大型表拆分成更小、更可管理的分區(qū)(子表,這篇文章主要介紹了MySQL?表分區(qū)簡介,需要的朋友可以參考下
    2023-09-09
  • macOS安裝Solr并索引MySQL

    macOS安裝Solr并索引MySQL

    這篇文章主要介紹了macOS安裝Solr并索引MySQL的相關資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下
    2016-11-11
  • 全面了解MySql中的事務

    全面了解MySql中的事務

    下面小編就為大家?guī)硪黄媪私釳ySql中的事務。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • mysql 8.0.18各版本安裝及安裝中出現(xiàn)的問題(精華總結)

    mysql 8.0.18各版本安裝及安裝中出現(xiàn)的問題(精華總結)

    這篇文章主要介紹了mysql 8.0.18各版本安裝及安裝中出現(xiàn)的問題,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-12-12

最新評論