人人都能看懂系列:《分散式系統改造方案——之資料篇》

tom發表於2021-08-09

打工人打工魂,打工仔hellohello-tom又上線了?

好久沒更新,前一段時間大家都知道鄭州又是經歷洪澇,又是經歷疫情的(澇疫結合啊),讓tom哥深深體會到生命的脆弱,人類的文明在災難面前不堪一擊,2021能活著真是萬幸,既然活著那我們就要繼續輸出,充分體現我們打工人的素養,今天tom哥分享的主題就是

人人都能看懂系列:《分散式系統改造方案——之資料篇》。

分散式系統涉及的內容知識面非常的廣泛,不是區區一篇文章就能講明白,但凡複雜的升級方案與枯燥無味的理論知識(CAP、TCC)這些而言,如何在不斷飆升的QPS面前從容不斷的解決問題才是關鍵,接下來tom哥就說說在實操過程中我們可能常見的幾種問題。

早期的架構基本都是這樣,我們的應用程式在請求資料時,會先從快取中獲取資料,如果快取中拿不到,再去資料庫拿一次,拿成功之後在寫入快取,這樣模式應該是很多公司前期開發模式。在這單體架構中會產生很多問題,tom哥從常見問題的幾個方面去幫大家踩坑,並提供解決方案。

資料庫

我們的系統一上線,靠著"老奸巨猾"的產品設計的套路,源源不斷的使用者開始在我們的平臺註冊,3個月內使用者註冊數量已經達到3000W,這時候單表3000W的user_info表在瑟瑟發抖,這時候該怎麼優化呢,很多同學都會說分庫分表,但是具體怎麼分呢?tom哥會採用常見hash方案,首先我們可以把使用者單獨提出來做一個資料庫(垂直拆分),這個資料庫裡我一次建立

user_info_0、user_info_1、....user_info_1023,

1024張使用者表,我們預估每張表500W資料,算下來500W * 1024 = 51億2千萬,這樣的使用者預估對於大部分網際網路公司絕對夠用了。但是隨之而來帶來很多新的問題,歷史資料遷移問題、使用者資訊查詢問題等等。先說說歷史資料遷移吧。

歷史資料遷移

我們的線上系統是一直再跑的,同步又發生線上下,這期間很難做到一致性,看過tom哥前面的文章應該知道歷史資料遷移的時候會採用資料雙寫方案,也就是2個階段。

第一階段上線我們會同時往老表和新表各寫一份,而遷移程式會線上下去跑,當然遷移程式和線上程式也會存在一個先後順序的問題,假如遷移程式在某個更新執行緒之後就很有可能會存在覆蓋的情況(請廣大同學自行腦補這個畫面,一定要想明白哦~),所以我們必須加上版本號或者時間戳等方案,判斷當前更新的資料是否等於資料庫記憶體在的版本,以保證原子性,尤其是使用者餘額這一類的欄位操作一定要謹慎。

//類似樂觀鎖的實現模式,如果能夠實現冪等更優
update userInfo set balance = balance + #{money} where userId = #{userId} and version = #{version}

第二階段線上下同步程式跑完之後,這時候老表和新表資料應該基本都一致了,這時候我們可以把老表的操作程式碼直接刪除,查詢相關的內容統一更換成新表的操作,然後再上線一次,就成功完成資料遷移的過程了。

使用者資訊查詢

這個也是一個常見的問題場景,假設我們分了1024張表,使用者資料均勻的落在了在1024表內,例如之前我們按照使用者手機號去查詢時,本來可以輕鬆的寫成

select * from user_info where mobile like '%#{mobile}%'

換成現在的查詢方式該怎麼寫呢

select * from user_info_0 where mobile like '%#{mobile}%'
select * from user_info_1 where mobile like '%#{mobile}%'
...
select * from user_info_1023 where mobile like '%#{mobile}%'

額...這不能把1024張表全部迴圈一遍吧,並且公司的運營人員可能會根據更多使用者資訊去檢索使用者,如果我們按照目前的分表方式確實需要過濾全部的表才能找到匹配電話號碼的使用者,這裡要怎麼做呢?

tom哥總結的分散式法則,要用合適的工具去做合適的事,這裡我們引入elasticsearch nosql(PS:引入不同的中介軟體會大大提升系統的複雜度),我們可以根據使用者需要搜尋的條件將這些條件全部作為索引存入es內,這裡tom哥是不建議資料庫和es表結構保持一致的,可能查詢時我們沒有那麼多需要過濾的內容,es作為nosql資料庫,我們應該把需要的熱資料存入es,,先從es內根據條件查到對應的使用者資訊,根據使用者id再去對應的表查詢詳情資料,我們在es插入doc時,索引一個欄位可以存入該條doc使用者資料對應的資料庫表名,這樣就可以快速定位到從DB中那個表中查詢到該使用者的詳細資訊了。

引入es中介軟體我們也要同時考慮新庫、老庫資料遷移工程(解決思路可以繼續使用資料雙寫方案),但是如何保證DB內資料與ES的一致性是新的問題,很多小夥伴應該都會遇到db與快取不一致,db與nosql不一致這類問題,我們該如何呢,cap理論:

CAP原則又稱CAP定理,指的是在一個分散式系統中,一致性(Consistency)、可用性(Availability)、分割槽容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。

概念還是要熟悉一下,採用CP原則,使用最終一致性去解決問題,引入訊息中介軟體去幫我們實現容錯性,訊息在消費不成功時,有一個重試機制,在重試達到閥值後可以人工處理或者轉入死信佇列,待服務恢復正常後重新消費保證最終結果的一致性。

說下實操,假設我們現在修改了使用者資訊,同時要修改使用者的快取,在DB更新成功之後我們可以往mq傳送一個事件(要保證傳送MQ和更新DB在一個事務內),MQ再接收到使用者基礎資訊,非同步去處理更新使用者的快取,如果這個過程消費失敗了,mq會幫我們實現重試機制,這裡不用擔心訊息丟失的問題,大部分廠商的佇列在訊息傳送成功時都已經落盤,可靠性還是能保證的(除非當機,停電,非同步刷盤這類)。

es一個索引的文件效能在幾億還是沒問題,但是太大的時候我們也要考慮繼續分,很多網際網路公司會按照時間range對索引進行二次分割槽,或者限定死了你只能查近3月、或近6月的資料,索引名字動態劃分一下,別名對應到近期資料,歷史資料就下沉,ES內也可以只對近期資料做預熱等操作,道理就是這樣。

大資料分析

系統越做越大,良好的藉助大資料分析我們可以給使用者提供更好的服務,大資料分析第一步肯定要有資料吧,需要把資料匯入hlive這類合適的中介軟體去跑,但是我們的程式碼不可能說在寫入es在同時同步到hlive吧,如果後續在增加類似mongodb、clickhouse這類儲存中介軟體時程式碼會越來越臃腫,所以tom哥這裡介紹基於MYSQL binlog擴充套件出來的資料同步方法canal。

MYSQL主從同步大家都知道吧,canal是模擬mysql 從庫向主庫傳送dump命令,拉下來binlog資料傳送到各個中介軟體進行,同步支援hlive,es,kafka等,這樣程式碼侵入性就降低了很多,我們只是針對資料進行後續操作,大部分網際網路公司也是這樣玩的。所以架構可能會變成這樣:

當然在同步的過程中可能存在的問題tom哥在圖中也都標記出來了,canal是非常適合做無侵入的架構重構的中介軟體,具體使用方法,可以去canal官網檢視教程。

還有系統做到後期,真正查詢對應到mysql的請求很少,請求都是會落到mongodb、es、tidb等這些高效中介軟體上,至於mysql我們就老老實實發揮他的本質,做個穩定的資料容器把。

這一期就簡單說到這,下次會繼續說,快取篇。

1、如何解決大key。

2、流量把redis也壓垮了?

我是hellohello-tom,一個二線城市的程式設計師

相關文章