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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

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

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

作者:sinxu,騰訊 CSIG 后臺開發工程師

1. 什么是數據的一致性

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

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

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

  • 緩存中本沒有數據,數據庫中的值 = 最新值(有請求查詢數據庫時,會將數據寫入緩存,則變為上面的“一致”狀態)

”數據不一致“:緩存的數據值 ≠ 數據庫中的值;緩存或者數據庫中存在舊值,導致其他線程讀到舊數據

2. 數據不一致情況及應對策略

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

只讀緩存:只在緩存進行數據查找,即使用 “更新數據庫+刪除緩存” 策略;

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

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

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

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

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

在更新數據的過程中,可能會有如下問題:

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

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

因此,要想達到數據一致性,需要保證兩點:

  • 無并發請求下,保證 A 和 B 步驟都能成功執行

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

接下來,我們針對有/無并發場景,進行分析并使用不同的策略。

A. 無并發情況

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

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

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

解決策略:

a.消息隊列+異步重試

無論使用哪一種執行時序,可以在執行步驟 1 時,將步驟 2 的請求寫入消息隊列,當步驟 2 失敗時,就可以使用重試策略,對失敗操作進行 “補償”。

具體步驟如下:

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

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

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

  • 刪除或者更新失敗時,需要再次進行重試,重試超過的一定次數,向業務層發送報錯信息。

  • b.訂閱 Binlog 變更日志

    • 創建更新緩存服務,接收數據變更的 MQ 消息,然后消費消息,更新/刪除 Redis 中的緩存數據;

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

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

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

    B. 高并發情況

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

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

    假設線程 A 刪除緩存值后,由于網絡延遲等原因導致未及更新數據庫,而此時,線程 B 開始讀取數據時會發現緩存缺失,進而去查詢數據庫。而當線程 B 從數據庫讀取完數據、更新了緩存后,線程 A 才開始更新數據庫,此時,會導致緩存中的數據是舊值,而數據庫中的是最新值,產生“數據不一致”。其本質就是,本應后發生的“B 線程-讀請求” 先于 “A 線程-寫請求” 執行并返回了。

    或者

    解決策略:

    a.設置緩存過期時間 + 延時雙刪

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

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

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

    sleep 時間:在業務程序運行的時候,統計下線程讀數據和寫緩存的操作時間,以此為基礎來進行估算:

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

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

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

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

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

    解決方案:

    a.延遲消息

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

    b.訂閱 binlog,異步刪除

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

    c.刪除消息寫入數據庫

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

    d.加鎖

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

    建議:

    優先使用“先更新數據庫再刪除緩存”的執行時序,原因主要有兩個:

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

  • 業務應用中讀取數據庫和寫緩存的時間有時不好估算,進而導致延遲雙刪中的 sleep 時間不好設置。

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

    讀寫緩存:增刪改在緩存中進行,并采取相應的回寫策略,同步數據到數據庫中

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

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

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

    A. 無并發情況
    B. 高并發情況

    有四種場景會造成數據不一致:

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

    其中,分布式鎖的實現可以使用以下策略:

    2.3 強一致性策略

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

    (1)暫存并發讀請求

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

    (2)串行化

    讀寫請求入隊列,工作線程從隊列中取任務來依次執行

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

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

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

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

    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 小結

    針對讀寫緩存時:同步直寫,更新數據庫+更新緩存:

    針對只讀緩存時:更新數據庫+刪除緩存:

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

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

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

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

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

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

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

    (2 )避免其他問題導致緩存服務器崩潰,進而簡直導致數據一致性策略失效

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

    (3)方案選定的思路

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

  • 確定一致性級別

  • 確定同步/異步方式

  • 選定緩存流程

  • 補充細節

  • 參考

    • 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

    • 緩存與數據庫一致性保證

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

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

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

    最近熱文:

    開發常用的縮寫 你能看懂幾個?

    TencentOCR 斬獲 ICDAR 2021 三項冠軍

    微信終端自研 C++協程框架的設計與實現

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。