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

打工人打工魂,打工仔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。

canal是模擬mysql 從庫向主庫發送dump命令,拉下來binlog數據發送到各個中間件進行,同步支持hlive,es,kafka等,這樣代碼侵入性就降低了很多,我們只是針對數據進行後續操作,大部分互聯網公司也是這樣玩的。所以架構可能會變成這樣:

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

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

1、如何解決大key。

2、流量把redis也壓垮了?

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

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