日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

认识 MySQL 和 Redis 的数据一致性问题

發(fā)布時間:2024/2/28 数据库 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 认识 MySQL 和 Redis 的数据一致性问题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者:sinxu,騰訊 CSIG 后臺開發(fā)工程師

1. 什么是數(shù)據(jù)的一致性

“數(shù)據(jù)一致”一般指的是:緩存中有數(shù)據(jù),緩存的數(shù)據(jù)值 = 數(shù)據(jù)庫中的值。

但根據(jù)緩存中是有數(shù)據(jù)為依據(jù),則”一致“可以包含兩種情況:

  • 緩存中有數(shù)據(jù),緩存的數(shù)據(jù)值 = 數(shù)據(jù)庫中的值(需均為最新值,本文將“舊值的一致”歸類為“不一致狀態(tài)”)

  • 緩存中本沒有數(shù)據(jù),數(shù)據(jù)庫中的值 = 最新值(有請求查詢數(shù)據(jù)庫時,會將數(shù)據(jù)寫入緩存,則變?yōu)樯厦娴摹耙恢隆睜顟B(tài))

”數(shù)據(jù)不一致“:緩存的數(shù)據(jù)值 ≠ 數(shù)據(jù)庫中的值;緩存或者數(shù)據(jù)庫中存在舊值,導(dǎo)致其他線程讀到舊數(shù)據(jù)

2. 數(shù)據(jù)不一致情況及應(yīng)對策略

根據(jù)是否接收寫請求,可以把緩存分成讀寫緩存和只讀緩存。

只讀緩存:只在緩存進(jìn)行數(shù)據(jù)查找,即使用 “更新數(shù)據(jù)庫+刪除緩存” 策略;

讀寫緩存:需要在緩存中對數(shù)據(jù)進(jìn)行增刪改查,即使用 “更新數(shù)據(jù)庫+更新緩存”策略。

2.1 針對只讀緩存(更新數(shù)據(jù)庫+刪除緩存)

只讀緩存:新增數(shù)據(jù)時,直接寫入數(shù)據(jù)庫;更新(修改/刪除)數(shù)據(jù)時,先刪除緩存。后續(xù),訪問這些增刪改的數(shù)據(jù)時,會發(fā)生緩存缺失,進(jìn)而查詢數(shù)據(jù)庫,更新緩存。

  • 新增數(shù)據(jù)時 ,寫入數(shù)據(jù)庫;訪問數(shù)據(jù)時,緩存缺失,查數(shù)據(jù)庫,更新緩存(始終是處于”數(shù)據(jù)一致“的狀態(tài),不會發(fā)生數(shù)據(jù)不一致性問題)

  • 更新(修改/刪除)數(shù)據(jù)時 ,會有個時序問題:更新數(shù)據(jù)庫與刪除緩存的順序(這個過程會發(fā)生數(shù)據(jù)不一致性問題)

在更新數(shù)據(jù)的過程中,可能會有如下問題:

  • 無并發(fā)請求下,其中一個操作失敗的情況

  • 并發(fā)請求下,其他線程可能會讀到舊值

因此,要想達(dá)到數(shù)據(jù)一致性,需要保證兩點:

  • 無并發(fā)請求下,保證 A 和 B 步驟都能成功執(zhí)行

  • 并發(fā)請求下,在 A 和 B 步驟的間隔中,避免或消除其他線程的影響

接下來,我們針對有/無并發(fā)場景,進(jìn)行分析并使用不同的策略。

A. 無并發(fā)情況

無并發(fā)請求下,在更新數(shù)據(jù)庫和刪除緩存值的過程中,因為操作被拆分成兩步,那么就很有可能存在“步驟 1 成功,步驟 2 失敗” 的情況發(fā)生(由于單線程中步驟 1 和步驟 2 是串行執(zhí)行的,不太可能會發(fā)生 “步驟 2 成功,步驟 1 失敗” 的情況)。

(1) 先刪除緩存,再更新數(shù)據(jù)庫

(2) 先更新數(shù)據(jù)庫,再刪除緩存

解決策略:

a.消息隊列+異步重試

無論使用哪一種執(zhí)行時序,可以在執(zhí)行步驟 1 時,將步驟 2 的請求寫入消息隊列,當(dāng)步驟 2 失敗時,就可以使用重試策略,對失敗操作進(jìn)行 “補(bǔ)償”。

具體步驟如下:

  • 把要刪除緩存值或者是要更新數(shù)據(jù)庫值操作生成消息,暫存到消息隊列中(例如使用 Kafka 消息隊列);

  • 當(dāng)刪除緩存值或者是更新數(shù)據(jù)庫值操作成功時,把這些消息從消息隊列中去除(丟棄),以免重復(fù)操作;

  • 當(dāng)刪除緩存值或者是更新數(shù)據(jù)庫值操作失敗時,執(zhí)行失敗策略,重試服務(wù)從消息隊列中重新讀取(消費)這些消息,然后再次進(jìn)行刪除或更新;

  • 刪除或者更新失敗時,需要再次進(jìn)行重試,重試超過的一定次數(shù),向業(yè)務(wù)層發(fā)送報錯信息。

  • b.訂閱 Binlog 變更日志

    • 創(chuàng)建更新緩存服務(wù),接收數(shù)據(jù)變更的 MQ 消息,然后消費消息,更新/刪除 Redis 中的緩存數(shù)據(jù);

    • 使用 Binlog 實時更新/刪除 Redis 緩存。利用 Canal,即將負(fù)責(zé)更新緩存的服務(wù)偽裝成一個 MySQL 的從節(jié)點,從 MySQL 接收 Binlog,解析 Binlog 之后,得到實時的數(shù)據(jù)變更信息,然后根據(jù)變更信息去更新/刪除 Redis 緩存;

    • MQ+Canal 策略,將 Canal Server 接收到的 Binlog 數(shù)據(jù)直接投遞到 MQ 進(jìn)行解耦,使用 MQ 異步消費 Binlog 日志,以此進(jìn)行數(shù)據(jù)同步;

    不管用 MQ/Canal 或者 MQ+Canal 的策略來異步更新緩存,對整個更新服務(wù)的數(shù)據(jù)可靠性和實時性要求都比較高,如果產(chǎn)生數(shù)據(jù)丟失或者更新延時情況,會造成 MySQL 和 Redis 中的數(shù)據(jù)不一致。因此,使用這種策略時,需要考慮出現(xiàn)不同步問題時的降級或補(bǔ)償方案。

    B. 高并發(fā)情況

    使用以上策略后,可以保證在單線程/無并發(fā)場景下的數(shù)據(jù)一致性。但是,在高并發(fā)場景下,由于數(shù)據(jù)庫層面的讀寫并發(fā),會引發(fā)的數(shù)據(jù)庫與緩存數(shù)據(jù)不一致的問題(本質(zhì)是后發(fā)生的讀請求先返回了)

    (1) 先刪除緩存,再更新數(shù)據(jù)庫

    假設(shè)線程 A 刪除緩存值后,由于網(wǎng)絡(luò)延遲等原因?qū)е挛醇案聰?shù)據(jù)庫,而此時,線程 B 開始讀取數(shù)據(jù)時會發(fā)現(xiàn)緩存缺失,進(jìn)而去查詢數(shù)據(jù)庫。而當(dāng)線程 B 從數(shù)據(jù)庫讀取完數(shù)據(jù)、更新了緩存后,線程 A 才開始更新數(shù)據(jù)庫,此時,會導(dǎo)致緩存中的數(shù)據(jù)是舊值,而數(shù)據(jù)庫中的是最新值,產(chǎn)生“數(shù)據(jù)不一致”。其本質(zhì)就是,本應(yīng)后發(fā)生的“B 線程-讀請求” 先于 “A 線程-寫請求” 執(zhí)行并返回了。

    或者

    解決策略:

    a.設(shè)置緩存過期時間 + 延時雙刪

    通過設(shè)置緩存過期時間,若發(fā)生上述淘汰緩存失敗的情況,則在緩存過期后,讀請求仍然可以從 DB 中讀取最新數(shù)據(jù)并更新緩存,可減小數(shù)據(jù)不一致的影響范圍。雖然在一定時間范圍內(nèi)數(shù)據(jù)有差異,但可以保證數(shù)據(jù)的最終一致性。

    此外,還可以通過延時雙刪進(jìn)行保障:在線程 A 更新完數(shù)據(jù)庫值以后,讓它先 sleep 一小段時間,確保線程 B 能夠先從數(shù)據(jù)庫讀取數(shù)據(jù),再把缺失的數(shù)據(jù)寫入緩存,然后,線程 A 再進(jìn)行刪除。后續(xù),其它線程讀取數(shù)據(jù)時,發(fā)現(xiàn)緩存缺失,會從數(shù)據(jù)庫中讀取最新值。

    redis.delKey(X) db.update(X) Thread.sleep(N) redis.delKey(X)

    sleep 時間:在業(yè)務(wù)程序運行的時候,統(tǒng)計下線程讀數(shù)據(jù)和寫緩存的操作時間,以此為基礎(chǔ)來進(jìn)行估算:

    注意:如果難以接受 sleep 這種寫法,可以使用延時隊列進(jìn)行替代。

    先刪除緩存值再更新數(shù)據(jù)庫,有可能導(dǎo)致請求因緩存缺失而訪問數(shù)據(jù)庫,給數(shù)據(jù)庫帶來壓力,也就是緩存穿透的問題。針對緩存穿透問題,可以用緩存空結(jié)果、布隆過濾器進(jìn)行解決。

    (2) 先更新數(shù)據(jù)庫,再刪除緩存

    如果線程 A 更新了數(shù)據(jù)庫中的值,但還沒來得及刪除緩存值,線程 B 就開始讀取數(shù)據(jù)了,那么此時,線程 B 查詢緩存時,發(fā)現(xiàn)緩存命中,就會直接從緩存中讀取舊值。其本質(zhì)也是,本應(yīng)后發(fā)生的“B 線程-讀請求” 先于 “A 線程-刪除緩存” 執(zhí)行并返回了。

    或者,在”先更新數(shù)據(jù)庫,再刪除緩存”方案下,“讀寫分離 + 主從庫延遲”也會導(dǎo)致不一致:

    解決方案:

    a.延遲消息

    憑借經(jīng)驗發(fā)送「延遲消息」到隊列中,延遲刪除緩存,同時也要控制主從庫延遲,盡可能降低不一致發(fā)生的概率

    b.訂閱 binlog,異步刪除

    通過數(shù)據(jù)庫的 binlog 來異步淘汰 key,利用工具(canal)將 binlog 日志采集發(fā)送到 MQ 中,然后通過 ACK 機(jī)制確認(rèn)處理刪除緩存。

    c.刪除消息寫入數(shù)據(jù)庫

    通過比對數(shù)據(jù)庫中的數(shù)據(jù),進(jìn)行刪除確認(rèn) 先更新數(shù)據(jù)庫再刪除緩存,有可能導(dǎo)致請求因緩存缺失而訪問數(shù)據(jù)庫,給數(shù)據(jù)庫帶來壓力,也就是緩存穿透的問題。針對緩存穿透問題,可以用緩存空結(jié)果、布隆過濾器進(jìn)行解決。

    d.加鎖

    更新數(shù)據(jù)時,加寫鎖;查詢數(shù)據(jù)時,加讀鎖 保證兩步操作的“原子性”,使得操作可以串行執(zhí)行。“原子性”的本質(zhì)是什么?不可分割只是外在表現(xiàn),其本質(zhì)是多個資源間有一致性的要求,操作的中間狀態(tài)對外不可見。

    建議:

    優(yōu)先使用“先更新數(shù)據(jù)庫再刪除緩存”的執(zhí)行時序,原因主要有兩個:

  • 先刪除緩存值再更新數(shù)據(jù)庫,有可能導(dǎo)致請求因緩存缺失而訪問數(shù)據(jù)庫,給數(shù)據(jù)庫帶來壓力;

  • 業(yè)務(wù)應(yīng)用中讀取數(shù)據(jù)庫和寫緩存的時間有時不好估算,進(jìn)而導(dǎo)致延遲雙刪中的 sleep 時間不好設(shè)置。

  • 2.2 針對讀寫緩存(更新數(shù)據(jù)庫+更新緩存)

    讀寫緩存:增刪改在緩存中進(jìn)行,并采取相應(yīng)的回寫策略,同步數(shù)據(jù)到數(shù)據(jù)庫中

    • 同步直寫:使用事務(wù),保證緩存和數(shù)據(jù)更新的原子性,并進(jìn)行失敗重試(如果 Redis 本身出現(xiàn)故障,會降低服務(wù)的性能和可用性)

    • 異步回寫:寫緩存時不同步寫數(shù)據(jù)庫,等到數(shù)據(jù)從緩存中淘汰時,再寫回數(shù)據(jù)庫(沒寫回數(shù)據(jù)庫前,緩存發(fā)生故障,會造成數(shù)據(jù)丟失) 該策略在秒殺場中有見到過,業(yè)務(wù)層直接對緩存中的秒殺商品庫存信息進(jìn)行操作,一段時間后再回寫數(shù)據(jù)庫。

    一致性:同步直寫 > 異步回寫 因此,對于讀寫緩存,要保持?jǐn)?shù)據(jù)強(qiáng)一致性的主要思路是:利用同步直寫 同步直寫也存在兩個操作的時序問題:更新數(shù)據(jù)庫和更新緩存

    A. 無并發(fā)情況
    B. 高并發(fā)情況

    有四種場景會造成數(shù)據(jù)不一致:

    針對場景 1 和 2 的解決方案是:保存請求對緩存的讀取記錄,延時消息比較,發(fā)現(xiàn)不一致后,做業(yè)務(wù)補(bǔ)償 針對場景 3 和 4 的解決方案是:對于寫請求,需要配合分布式鎖使用。寫請求進(jìn)來時,針對同一個資源的修改操作,先加分布式鎖,保證同一時間只有一個線程去更新數(shù)據(jù)庫和緩存;沒有拿到鎖的線程把操作放入到隊列中,延時處理。用這種方式保證多個線程操作同一資源的順序性,以此保證一致性。

    其中,分布式鎖的實現(xiàn)可以使用以下策略:

    2.3 強(qiáng)一致性策略

    上述策略只能保證數(shù)據(jù)的最終一致性。要想做到強(qiáng)一致,最常見的方案是 2PC、3PC、Paxos、Raft 這類一致性協(xié)議,但它們的性能往往比較差,而且這些方案也比較復(fù)雜,還要考慮各種容錯問題。如果業(yè)務(wù)層要求必須讀取數(shù)據(jù)的強(qiáng)一致性,可以采取以下策略:

    (1)暫存并發(fā)讀請求

    在更新數(shù)據(jù)庫時,先在 Redis 緩存客戶端暫存并發(fā)讀請求,等數(shù)據(jù)庫更新完、緩存值刪除后,再讀取數(shù)據(jù),從而保證數(shù)據(jù)一致性。

    (2)串行化

    讀寫請求入隊列,工作線程從隊列中取任務(wù)來依次執(zhí)行

  • 修改服務(wù) Service 連接池,id 取模選取服務(wù)連接,能夠保證同一個數(shù)據(jù)的讀寫都落在同一個后端服務(wù)上

  • 修改數(shù)據(jù)庫 DB 連接池,id 取模選取 DB 連接,能夠保證同一個數(shù)據(jù)的讀寫在數(shù)據(jù)庫層面是串行的

  • (3)使用 Redis 分布式讀寫鎖

    將淘汰緩存與更新庫表放入同一把寫鎖中,與其它讀請求互斥,防止其間產(chǎn)生舊數(shù)據(jù)。讀寫互斥、寫寫互斥、讀讀共享,可滿足讀多寫少的場景數(shù)據(jù)一致,也保證了并發(fā)性。并根據(jù)邏輯平均運行時間、響應(yīng)超時時間來確定過期時間。

    public?void?write()?{Lock?writeLock?=?redis.getWriteLock(lockKey);writeLock.lock();try?{redis.delete(key);db.update(record);}?finally?{writeLock.unlock();} }public?void?read()?{if?(caching)?{return;}//?no?cacheLock?readLock?=?redis.getReadLock(lockKey);readLock.lock();try?{record?=?db.get();}?finally?{readLock.unlock();}redis.set(key,?record); }

    2.4 小結(jié)

    針對讀寫緩存時:同步直寫,更新數(shù)據(jù)庫+更新緩存:

    針對只讀緩存時:更新數(shù)據(jù)庫+刪除緩存:

    較為通用的一致性策略擬定:

    在并發(fā)場景下,使用 “更新數(shù)據(jù)庫 + 更新緩存” 需要用分布式鎖保證緩存和數(shù)據(jù)一致性,且可能存在”緩存資源浪費“和”機(jī)器性能浪費“的情況;一般推薦使用 “更新數(shù)據(jù)庫 + 刪除緩存” 的方案。如果根據(jù)需要,熱點數(shù)據(jù)較多,可以使用 “更新數(shù)據(jù)庫 + 更新緩存” 策略。

    在 “更新數(shù)據(jù)庫 + 刪除緩存” 的方案中,推薦使用推薦用 “先更新數(shù)據(jù)庫,再刪除緩存” 策略,因為先刪除緩存可能會導(dǎo)致大量請求落到數(shù)據(jù)庫,而且延遲雙刪的時間很難評估。在 “先更新數(shù)據(jù)庫,再刪除緩存” 策略中,可以使用“消息隊列+重試機(jī)制” 的方案保證緩存的刪除。并通過 “訂閱 binlog” 進(jìn)行緩存比對,加上一層保障。

    此外,需要通過初始化緩存預(yù)熱、多數(shù)據(jù)源觸發(fā)、延遲消息比對等策略進(jìn)行輔助和補(bǔ)償。【多種數(shù)據(jù)更新觸發(fā)源:定時任務(wù)掃描,業(yè)務(wù)系統(tǒng) MQ、binlog 變更 MQ,相互之間作為互補(bǔ)來保證數(shù)據(jù)不會漏更新】

    3. 數(shù)據(jù)一致性中需要注意的其他問題有哪些?

    (1) k-v 大小的合理設(shè)置

    Redis key 大小設(shè)計:由于網(wǎng)絡(luò)的一次傳輸 MTU 最大為 1500 字節(jié),所以為了保證高效的性能,建議單個 k-v 大小不超過 1KB,一次網(wǎng)絡(luò)傳輸就能完成,避免多次網(wǎng)絡(luò)交互;k-v 是越小性能越好Redis 熱 key:(1) 當(dāng)業(yè)務(wù)遇到單個讀熱 key,通過增加副本來提高讀能力或是用 hashtag 把 key 存多份在多個分片中;(2)當(dāng)業(yè)務(wù)遇到單個寫熱 key,需業(yè)務(wù)拆分這個 key 的功能,屬于設(shè)計不合理- 當(dāng)業(yè)務(wù)遇到熱分片,即多個熱 key 在同一個分片上導(dǎo)致單分片 cpu 高,可通過 hashtag 方式打散——[引自騰訊云技術(shù)分享]

    (2 )避免其他問題導(dǎo)致緩存服務(wù)器崩潰,進(jìn)而簡直導(dǎo)致數(shù)據(jù)一致性策略失效

    緩存穿透、緩存擊穿、緩存雪崩、機(jī)器故障等問題:

    (3)方案選定的思路

  • 確定緩存類型(讀寫/只讀)

  • 確定一致性級別

  • 確定同步/異步方式

  • 選定緩存流程

  • 補(bǔ)充細(xì)節(jié)

  • 參考

    • https://xie.infoq.cn/article/1322475e05c11bd2aacd8bc73

    • https://www.infoq.cn/article/Hh4IOuIiJHWB4X46vxeO

    • https://time.geekbang.org/column/article/217593

    • https://xie.infoq.cn/article/ab2599366009928a17fe498fb

    • 緩存與數(shù)據(jù)庫一致性保證

    • https://time.geekbang.org/column/article/295812

    • https://blog.csdn.net/chengh1993/article/details/112685774

    • https://juejin.cn/post/6850418120201666568

    最近熱文:

    開發(fā)常用的縮寫 你能看懂幾個?

    TencentOCR 斬獲 ICDAR 2021 三項冠軍

    微信終端自研 C++協(xié)程框架的設(shè)計與實現(xiàn)

    總結(jié)

    以上是生活随笔為你收集整理的认识 MySQL 和 Redis 的数据一致性问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。