##.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:16px;overflow-x:hidden;color:#252933 } .markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin - bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:24px;line-height:38px;margin-bottom:5px}.markdown-body h2{font-size:22px;line-height : 34px;padding-bottom:12px;border-bottom:1pxsolid #ececec}.markdown-body h3{font-size:20px;line-height:28px}.markdown-body h4{font-size:18px;line-高度: 26px}.markdown-body h5{font-size:17px;line-height:24px}.markdown-body h6{font-size:16px;line-height:24px}.markdown-body p{line-height:繼承; margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1pxsolid #ddd;margin-top: 32px;margin -bottom:32px}.markdown-body程式碼{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size: .87em;填充:.065em .4em}.markdown-body程式碼,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative ;line- height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333 ;背景: #f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1pxsolid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{顏色: #275b8c}. markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1pxsolid #f6f6f6}.markdown-body thead {背景:#f6f6f6 ;顏色:#000;text-align:left}.markdown-body tr:nth-child(2n){背景顏色:#fcfcfc}.markdown-body td,.markdown-body th{填充:12px 7px;line- height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4pxsolid # cbcbcb; }.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul {padding-left:28px}. markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item {list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li. 任務清單項目ol,.markdown- body ul li .任務清單項目ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown -body ul ul{margin-top:3px}. markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list- item{list-style:none}@media (max -width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-大小:18px}}. markdown-body pre,.markdown-body pre>code.hljs{顏色:#333;背景:#f8f8f8}.hljs-comment,.hljs-quote{顏色:# 998;字體樣式:斜體}.hljs-關鍵字,.hljs-選擇器-標籤,.hljs-subst{顏色:#333;字體粗細:700}.hljs-文字,.hljs-數字,.hljs-標籤.hljs-attr,.hljs-模板變數,. hljs-變數{顏色:青色}.hljs-doctag,.hljs-string{顏色:#d14}.hljs-section,.hljs-selector-id,.hljs -標題{顏色:#900;字體權重:700} .hljs-subst{字型權重:400}.hljs-class .hljs-title,.hljs-type{顏色:#458;字型權重:700} .hljs-attribute,.hljs-name,.hljs-tag{color :navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name {顏色:#0086b3}.hljs-meta{顏色:#999;font-weight:700}.hljs-deletion{背景:#fdd}.hljs -addition{background:#dfd}.hljs-emphasis{font-style :italic}.hljs-strong{font-weight:700}前言
四月份的時候,有位去美團面試,他說被問到Redis與MySQL雙寫一致性如何保證?這題其實就是在問伺服器和資料庫在雙寫場景下,一致性是如何保證的?本文將跟大家一起來如何回答這個問題。
談談一致性
一致性就是資料保持一致,在多元化系統中,可以理解為多個節點中資料的值是一致的。
-
強一致性:這種一致性等級是最符合使用者直覺的,它要求系統寫入什麼,讀出來的也會是什麼,使用者體驗好,但實現起來往往對系統的效能影響大
-
弱一致性:這種一致性等級約束了系統在寫入成功後,不承諾立即可以讀到寫入的值,也不承諾多久之後資料能夠達到一致,但會盡可能地保證到某個時間等級(例如秒等級)後,資料能夠達到一致狀態
-
最終一致性:最終一致性是弱一致性的一個特例,系統會保證在一定時間內,能夠達到一個數據一致的狀態。這裡之所以將最終一致性單獨提出來,是因為它是弱一致性中非常推崇的一種一致性模型,也是業界在大型分散式系統的數據一致性上比較推崇的模型
#三個經典的快取模式
快取可以提升效能、緩解資料庫壓力,但使用快取也會導致資料不一致性的問題。一般我們是如何使用快取呢?有三種經典的快取模式:
- Cache-Aside Pattern
- Read-Through/Write through
- Write behind
#Cache -Aside Pattern
Cache-Aside Pattern,即旁路快取模式,它的提出是為了盡可能解決快取與資料庫的資料不一致問題。
Cache-Aside讀取流程
Cache-Aside Pattern#的讀取請求流程如下:
# #讀的時候,先讀緩存,緩存命中的話,直接返回數據- 緩存沒有命中的話,就去讀數據庫,從數據庫取出數據,放入緩存後,同時返回響應。
-
Cache-Aside 寫入流程
Cache-Aside Pattern的寫入請求流程如下:
更新的時候,先
更新資料庫,再刪除快取。
Read-Through/Write-Through(讀寫穿透)
#Read/Write Through模式中,服務端把快取作為主要資料儲存。應用程式跟資料庫快取交互,都是透過抽象快取層完成的。
Read-Through
Read-Through的簡要流程如下
##從快取讀取數據,讀到直接返回
- 如果讀取不到的話,從資料庫加載,寫入快取後,再回傳回應。
- 這個簡單流程是不是跟
Cache-Aside
很像呢?其實Read-Through就是多了一層Cache-Provider,流程如下:
Read-Through實際上只是在
Cache-Aside
之上進行了一層封裝,它會讓程式碼變得更簡潔,同時也減少資料來源上的負載。 Write-Through
Write-Through
模式下,當發生寫入請求時,也是由快取抽象層完成資料來源和快取數據的更新,流程如下:Write behind (非同步快取寫入)
Write behind
跟Read-Through/Write-Through有相似的地方,都是由Cache Provider來負責快取和資料庫的讀寫。它兩又有個很大的不同:Read/Write Through
是同步更新快取和資料的,Write Behind則是只更新緩存,不直接更新資料庫,透過批量異步的方式來更新資料庫。
這種方式下,快取和資料庫的一致性不強,
對一致性要求高的系統要謹慎使用
。但是它適合頻繁寫的場景,MySQL的InnoDB Buffer Pool機制就使用到這種模式。 操作快取的時候,刪除快取呢,還是更新快取?
一般業務場景,我們使用的就是
Cache-Aside
模式。
有些小夥伴可能會問, Cache-Aside在寫入請求的時候,為什麼是刪除快取而不是更新快取呢?
我們在操作快取的時候,到底該刪除快取還是更新快取呢?我們先來看個範例:
- 執行緒A先發起一個寫入操作,第一步先更新資料庫
- 執行緒B再發起一個寫入操作,第二步更新了資料庫
- 由於網路等原因,線程B先更新了快取
- #線程A更新快取。
這時候,快取保存的是A的資料(舊資料),資料庫保存的是B的資料(新資料),資料不一致了,髒資料出現啦。如果是刪除快取取代更新快取則不會出現這個髒資料問題。
更新快取相對於刪除快取,還有兩點劣勢:
- 如果你寫入的快取值,是經過複雜計算才得到的話。更新快取頻率高的話,就浪費效能啦。
- 在寫資料庫場景多,讀資料場景少的情況下,資料很多時候還沒被讀取到,又被更新了,這也浪費了效能呢(實際上,寫多的場景,用快取也不是很划算了)
雙寫的情況下,先操作資料庫還是先操作快取?
Cache-Aside
快取模式中,有些小夥伴還是有疑問,在寫入請求的時候,為什麼是先操作資料庫呢?為什麼不先操作快取?
假設有A、B兩個請求,請求A做更新操作,請B做查詢讀取操作。
- 線程A發起一個寫入操作,第一步del cache
- 此時線程B發起一個讀取操作,cache miss
- 線程B繼續讀DB,讀出來一個老資料
- 然後執行緒B把舊資料設定入cache
- #執行緒A寫入DB最新的資料
醬紫就有問題啦,快取和資料庫的資料不一致了。快取保存的是舊數據,資料庫保存的是新數據。因此,Cache-Aside
快取模式,選擇了先操作資料庫而不是先操作快取。
快取延時雙刪
有些小夥伴可能會說,不一定要先操作資料庫呀,採用快取延時雙刪策略就好啦?什麼是延時雙刪呢?
- 先刪除快取
- 再更新資料庫
- 休眠一會(例如1秒),再刪除快取。
這個休眠一會,通常多久呢?都是1秒?
這個休眠時間 = 讀業務邏輯資料的耗時 幾百毫秒。為了確保讀取請求結束,寫入請求可以刪除讀取請求可能帶來的快取髒資料。
刪除快取重試機制
不管是延時雙刪還是Cache-Aside的先操作資料庫再刪除快取,如果第二步驟的刪除快取失敗呢,刪除失敗會導致髒資料哦~
刪除失敗就多刪除幾次呀,保證刪除快取成功呀~ 所以可以引入刪除緩存重試機制
- 寫入請求更新資料庫
## 快取因為某些原因,刪除失敗- #把刪除失敗的key放到訊息佇列
- 消費訊息佇列的訊息,取得要刪除的key
- #重試刪除快取作業
- ##讀取biglog非同步刪除快取
重試刪除快取機制還可以,就是會造成好多業務程式碼入侵。其實,還可以透過
資料庫的binlog來非同步淘汰key。
以mysql為例可以使用阿里的canal將binlog日誌擷取傳送到MQ佇列裡面,然後透過ACK機制確認處理這條更新訊息,刪除緩存,保證數據快取一致性
推薦學習:《Redis影片教學
》