現象
應用系統中的關鍵服務絕大部分都會是對資料庫的依賴。
當多個程式同時操作同一個資料,會產生資源爭搶,資料一致性的問題。
如果只有一個資料庫伺服器,資料一致性問題也就不存在了。
可是,隨著系統訪問量、資料量的不斷增長,資料庫出現多個伺服器,又出現快取服務,又要拆分資料庫,還要分拆到不同的子應用等等。
這樣一來,資料一致性問題就會變得越來越突出。
舉個栗子
我們來看這樣一個資料流程。
使用者提交一個訂單(2個不同商家各一件商品)——資料來源頭
應用伺服器驗證使用者資訊、訂單資訊、庫存資訊等等,然後將這個訂單傳送到訂單訊息佇列——訊息佇列
訂單處理伺服器從訊息佇列中拿到新訂單,接下來的處理,可能做的資料操作有:
生成一個訂單/也可能會分拆為兩個訂單
更新兩個商品庫存數量
更新商家的銷售資料
生成訂單對應的支付資訊
生成使用者訂單成功的狀態資訊
思路
上面的資料處理中,涉及到的資料有:訂單資料、商品資料、商家資料、支付資料、使用者資料。
涉及到的應用和服務有:前端應用系統,訊息佇列,後端應用系統,資料庫,快取,甚至訂單、商品、商家、支付、使用者可能都是獨立的子應用。
可能大部分系統不會像上面這麼龐大。
如果前後端都是一起的,也就沒有訊息佇列。
如果也沒有這些子系統,資料庫是集中的,那可能資料一致性問題會稍微小些。
這時候,只需要注意資料庫更新的一致性就好了,比較容易想到的應對方法,就是用資料庫事務來保證。
如果這些資料不只是一份資料庫,還有快取中一份,又要考慮快取資料的更新,所以問題還是複雜了。
資料庫更新,怎麼保證快取也能正常更新呢?
程式中處理,資料庫更新後,就要馬上更新快取資料
如果快取更新失敗或者程式出現異常,要有異常處理方法
異常處理方法可以是程式中實時的糾正或者重試
異常處理方法也可以是針對資料庫的更新,二次檢查快取資料的更新
這裡還只是一個資料庫和一個快取的情況,已經要做出這麼多事情。
那這些工作帶來的影響有哪些呢?
程式開發更加複雜,不能有些許的遺漏
資料驗證和重試帶來的效能下降
資料庫事務帶來的資料庫瓶頸明顯
二次檢查再次增加複雜度和額外開銷
本來一個訂單處理,如果不考慮資料一致性問題,資料庫寫入/更新510次,快取寫入/更新510次,整個過程應該在10ms內完成。
但是加上資料庫事務之後,會把這些操作中涉及到的幾個表都加鎖,意味著資料的讀、寫都序列化了,整個應用系統的併發能力急劇下降。
當然,因為這裡引入快取,對資料庫的依賴會減少很多,而且還有從庫可以提供讀的服務,應用系統的訪問併發能力不至於下降太多。
但這些代價在交易處理中是難以避免的,為了解決資料一致性問題,犧牲的是訂單處理的併發能力。
對於大部分商城、網站,訂單併發量也不高,這類問題不太常發生,所以也就這麼過去了。
但是在一些促銷活動的時候,肯定還是會遇到下單等待太久的問題。
瓶頸
為了具備更大併發的訂單處理能力,單資料庫、快取肯定是行不通了。
那麼要在這麼多的子應用、大量的資料庫、快取服務中保持資料一致性又要怎麼做呢?
每個子應用都要支援分散式事務,共同保證資料庫全部成功更新
每個子應用各自要保證自己的資料更新一致性(異常處理、重試、二次檢查等方法同上)
上面看上去只有兩條,但是要做的事情和困難會比上面要多十倍,難百倍。
看到這裡,是不是對於資料一致性的問題都有點絕望了。
真相
正因如此,大部分的分散式系統,大部分應用,是沒有做到資料一致性,哪怕是弱一致性。
比如:論壇裡面發帖,要更新10份左右的資料,出現髒資料是常有的,這就是沒有做到資料一致性。
比如:商城裡面庫存超賣,訂單狀態不一致等,也是因為沒有做到資料一致性。
之所以會這樣,因為投入產出嚴重不成比例,是很無奈的選擇。
資料不一致的情況畢竟比例極低,但是投入的代價卻極大。
資料不一致引發的後果,可以忍受和容忍,哪怕是發現後再修正。
那麼,還有什麼辦法可以避免或減少出現資料一致性問題呢?
下面有幾個方法可以考慮:
- 將系統規模和容量降低,保證系統的穩定性和高效;
一個每秒鐘上百萬請求的應用系統能不能分拆為1000個每秒鐘1000請求的獨立叢集呢?
一個上百萬的商家、商品、訂單庫,能不能分拆為1000個只有1000個商家、商品、訂單的子庫呢?
- 將資料關聯降低,減少更新次數,減少不一致問題的出現概率;
上面的訂單、庫存、商家、支付、使用者幾個資料,核心資料只有訂單,其他的幾個資料完全可以從訂單資料推匯出來,減少訂單處理中的一致性要求。
- 將應用分拆,對效能和一致性要求高的應用獨立實現;
減少業務耦合,集中資源重點投入。