人人都能看懂系列:《分佈式系統改造方案——老舊系統改造篇》

打工人打工魂,打工仔hellohello-tom上線啦🤣

tom哥真是越來越懶了,懶得動筆,有很多粉絲一直在催我更新,所以tom哥整理了一下,今天打算來場硬核輸出,繼續更新人人能看懂系列,文字較多,建議多讀幾遍

人人都能看懂系列:《分佈式系統改造方案——老舊系統改造篇》。

很多同學到一家公司相信說的最多的一句話都是,窩xxxx,這什麼垃圾代碼,我可沒辦法維護,讓我改的話只能推翻重寫,畢竟每個人都只熟悉自己的代碼,但是老闆可不會讓你這麼輕而易舉答應你的要求,尤其是項目已經正在運營,正在產生收益,很多同學只能心裏MMB接受,繼續在混亂不堪的代碼上迭代功能,迭代不動了,就離開這家公司,把坑留給下個人,下個程序員繼續重複如上的循環,是不是很真實🤣

今天tom哥就把自己的親身經歷講給大家,不用怕老舊系統,技術債是早晚都要還的,而公司也願意花大價錢把能填坑的人給招來,tom哥希望每位同學都能成爲那個填坑的人(畢竟能得到不菲的收入),如果想成爲那個填坑的人來看看tom哥怎麼處理的。

數據庫

大部分程序的業務都圍繞在model上,數據庫肯定跑不了,舉個用戶表的例子

字段名 備註
user_id 用戶id
nick_name 暱稱
... ...

tom哥剛來公司的時候,一看用戶表,媽呀將近100個字段,各種各樣緯度的字段,暱稱基本信息不說了,還有什麼點贊數量、收入量,是不是vip等等亂起八遭的數據全部都冗餘在了用戶表,完全不符合數據庫設計三範式的原則,並且緩存設計也是經典的把一行數據對應的model直接存到redis內,導致一個用戶數據在緩存佔用將近300多kb,可真不小。並且現有的用戶表的數據早已經過了千萬,按照mysql的單表500W的原則,也是時候做橫向拆分了。

大家在面試的時候都應該會被問到分庫分表相關問題,我們可能會做水平分表,縱向分表,但是往往初期業務沒有那麼複雜,像tom哥公司這樣的情況應該會存在好多公司內,尤其是項目已經上線,如何做好平滑遷移實數不易,所以很多同學應該都是隻有理論知識,沒有實戰經驗。既然要拆,並且還是用戶無感知的情況下拆分,tom哥在實踐中總結的影響最小的三步法則。

1、數據同步,老表同步新表

2、查詢替換

3、更換寫入,幹掉老表

看tom哥具體怎麼實踐這三個步驟

1、數據同步,老表同步新表

我們的線上系統是一直在跑的,每分鐘都產生訂單都在給公司創造利潤,一刀切肯定不可能,再加上老表設計的不合理,已經沒辦法做水平切分,我們只能整理業務,把新表重新梳理

比如tom哥把原來的100多個字段的用戶大表按照具體的職責進行了垂直劃分,這裏劃分的原則需要結合多維度去考慮,例如業務場景、字段更新頻率等等,需要充分了解業務後才能做出正確的劃分原則。

再劃分表後與公司產品總監(老闆)商討後具體就要實施數據遷移了,本來tom哥想着是在現有老代碼的基礎上用戶註冊成功之後發送一個異步事件,由異步事件去把新註冊的數據給寫入,但是創建用戶不僅僅在註冊產生,並且在老代碼上修改也很不安全,要是加個這,導致服務崩了,tom哥可就要背個P0事故了,並且更新用戶表單個字段的地方非常多,不可能在每個地方都要加個事件異步去刷新吧,所以直接pass。

綜合考慮之下,tom哥使用canal進行了mysql binlog訂閱的模式,在確定數據入庫成功後,由canal訂閱老庫的binlog,發送到rocketmq內,再由消費端把數據寫入到新庫新表內。

PS:canal的使用教程看這裏

這樣新產生的數據就能用戶無感知的處理了,有的同學會問那老的用戶數據怎麼辦呢,別急接下來tom哥會說老的數據怎麼辦,既然都統一通過binlog訂閱處理這種模式來進行數據入庫,那能不能我寫個客戶端程序,把歷史數據模擬成binlog的插入模式,發送到mq,由mq訂閱消費處理呢,邏輯圖參考如下:

這裏有個坑,生產productor不能和消費Binlog同時開始處理,因爲會有這種情況,生產的數據程序在查出來那一刻其實已經可以稱爲歷史數據,你是沒辦法判定你當前的數據到底在老庫內是不是最新的(老的表內壓根不存在什麼版本號和更新時間戳啥的),所以tom哥的方案是,先開啓binlog,但是不消費,我們可以創建兩個topic來進行控制,同時我們也要保證一個用戶數據路由到一個queue進行處理,離散處理的話搞不好數據就又亂了,時序沒法保證,擠壓的話問題就更多了。

topicName 備註 簡稱
user_oldmysql_binlog_topic 老庫產生的binlog日誌 A
user_productor_binlog_topic 生產者模擬產生的binlog日誌 B

我們開始先把老庫內canal產生的binlog讓它擠壓到A內,不進行消費,然後我們開啓我們的生產者模擬insert binlog模式,生產消息併發送到B(A、B消費順序很重要),我們的consumer就正常開始消費B topic內的消息,當我們的生產者把老庫的用戶數據取完之後(早晚都會取完的,或者我們定一個時間點在開啓Atopic之後任意時間點都可以),這時候戶數據基本都已經入到新庫了。接下來開啓Atopic,我們的consumer開始消費A的消息,這裏面我們可以做個容錯機制,就是針對Atopic插入如果發生userId的主鍵衝突我們可以直接丟棄。然後慢慢就像看動畫書一樣,慢慢把Atopic 內binlog數據一點一點演變到新庫的表字段上,直到只消費老庫產生的實時binlog日誌就完成了

這裏很重要。建議沒看明白的同學自己多思考幾遍,想清楚這個順序。上面tom哥說的這個容錯處理,其實就是一個時間交叉的問題,有可能你productor取到的數據已經入庫了,在消費Atopic真正的產生的binlog時,會重複插入,所以我們不用重複插入,捕獲主鍵衝突異常直接丟棄就可以了。至於更新操作,前一步productor生產的消息有可能是最新的也有可能是老的,所以我們結合Atopic實時binlog按照順序慢慢把數據還原到最終的那個快照,沒想明白多想想哈。很重要!!!

2、查詢替換

解決了第一步那個難題,這第二步就很簡單了,基礎數據我們都有了,那不是隨心所欲了,tom哥我直接給用戶分庫分表了,按照我們日活100萬的原則,一口氣分他個2個庫,每個庫16個表,用戶id取模均勻落庫,考慮數據庫讀寫瓶頸,以及日後動態擴容用戶表,tom哥這裏引入了shardingjdbc,再來個讀寫分離,perfect,一次搞定(PS:sharding真是方便)。

user_master_0(0~5000萬的用戶在這裏放) user_0,user_1,...
user_master_1(5001萬~1億的用戶在這裏放) user_0,user_1,...
user_master_.. user_0,user_1,...

接下來然後我們直接和APP商量吧,把在老程序內的查詢操作慢慢替換爲新寫的程序的接口,慢慢迭代唄,這快不了。中間還可能會有很多問題呢。

說到查詢替換了,tom哥這裏簡單說說ES的問題,用戶索引設計可能很多同學也沒思路,我們公司基本這樣來佈局:

1、用戶索引考慮把接口用的和管理後臺用的索引分開設計,

2、假設現在有4臺ES集羣,3臺高配(A、B、C),1臺低配(D)
接口用戶索引定義爲hot節點,控制索引文檔生成到hot node (A、B、C)上,按照模板創建user_index_0(1、2、3)接口索引按照40G大小爲一個單位,控制用戶每5000萬在一個,後期把日活用戶。例如100萬或者200萬、300等,再維護到一個索引內(hot_user_index)。

3、管理後臺索引定義爲cold節點,控制索引生成到cold node(D)上,manage_user_index

4、管理後臺索引直接扔一個索引了(manage_user_index)
核心思路就是(冷熱隔離),避免管理後臺和接口共用時,多維度查詢頂替掉熱數據

這樣基本也都夠用了,很多大型系統裏面看你註冊用戶有個幾千萬,可能日活的用戶連100萬用戶都不到,這時候考慮好冷熱隔離就行,別冷不丁的什麼查詢把es內的熱數據頂掉,全部都是冷數據導致把es拖垮可就GG了。

3、更換寫入,幹掉老表

最後一步最簡單了,前期的查詢基本都替換完了,接下來就是更改數據寫入了,原來我們的新庫都是基於老庫的binlog做數據同步的,那現在目標很明確,把所有數據寫入用戶相關的全部替換到我們新的程序內,也是慢慢迭代吧,中間肯定還是要有一段時間新老共存的階段,全部替換完,停掉canal訂閱,整個老舊系統就全部改造完成了。

看完tom哥說的是不是很簡單,實操起來還是有很多複雜點和意料不到的情況的,好了今天分享就結束了,下期內容tom即興發揮,哈哈

我是hellohello-tom,一個二線城市的程序員

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章