如果讓你自己設(shè)計一個分布式架構(gòu)的中間件系統(tǒng),該怎么做?
?這篇文章,給大家來聊一個生產(chǎn)級的中間件系統(tǒng)的架構(gòu)設(shè)計實踐,希望給對中間件系統(tǒng)感興趣的同學(xué)一點啟發(fā)。
1、Master-Slave架構(gòu)
這個中間件系統(tǒng)的本質(zhì)是希望能夠用分布式的方式來處理一些數(shù)據(jù),但是具體的作用涉及到核心技術(shù),所以這里不能直接說明。
但是他的核心思想,就是把數(shù)據(jù)分發(fā)到很多臺機器上來處理,然后需要有一臺機器來控制N多臺機器的分布式處理,大概如下圖所示。
那么既然是分布式的處理,就肯定涉及到在Master中要維護這個集群的一些核心元數(shù)據(jù)。
比如說數(shù)據(jù)的分發(fā)處理是如何調(diào)度的,處理的具體過程現(xiàn)在什么進度了,還有就是對集群里存放數(shù)據(jù)進行描述的一些核心元數(shù)據(jù)。
這些核心元數(shù)據(jù)肯定會不斷的頻繁的修改,大家此時可以想,無論你是基于外部的文件還是數(shù)據(jù)庫,或者是zookeeper來存放這些元數(shù)據(jù)的話,其實都會導(dǎo)致他的元數(shù)據(jù)更新性能降低,因為要訪問外部依賴。
何況這種復(fù)雜的元數(shù)據(jù)其實還不一定能通過zk或者數(shù)據(jù)庫來存放,因為他可能是非格式化的。
所以這里一個核心的設(shè)計,就是將核心元數(shù)據(jù)直接存放在Master的內(nèi)存里,這樣可以保證高并發(fā)更新元數(shù)據(jù)的時候,他的性能是極高的,而且直接基于內(nèi)存來提供對外的更新服務(wù)。
如果Master部署在高配置物理機上,比如32核128GB的那種,每秒支持10萬+的請求都沒問題。
2、異步日志持久化機制
但是這里有一個問題,假如說Master進程重啟,或者是突然宕機了,那么內(nèi)存里的數(shù)據(jù)不就丟失了么?
對,所以針對這個問題,既然已經(jīng)否決掉了基于外部存儲來寫入元數(shù)據(jù),那么這里就可以采取異步持久化日志的機制,來通過異步化的方式把元數(shù)據(jù)的更新日志寫入磁盤文件。
每次Master收到一個請求,在內(nèi)存里更新元數(shù)據(jù)之后,就需要生成一條元數(shù)據(jù)的更新日志,把這個更新日志需要寫入到一個內(nèi)存緩沖里去。
然后等內(nèi)存緩沖滿了之后,由一個后臺線程把這里的數(shù)據(jù)刷新到磁盤上去,如下圖。
肯定會有人說,那如果一條更新日志剛寫入緩沖區(qū),結(jié)果Master宕機了,此時不是還是會丟失少量數(shù)據(jù)嗎?因為還沒來得及刷入磁盤。
沒錯啊,這個為了保證高并發(fā)請求都是由內(nèi)存來處理的,你必須得用異步持久化磁盤的模式,所以必然要容忍極端宕機情況下,可能丟失比如幾秒鐘的數(shù)據(jù)。
那么如果是正常的Master重啟呢?
那簡單,必須先把日志緩沖區(qū)清空刷入磁盤,然后才能正常重啟Master,保證數(shù)據(jù)都在磁盤上不會丟失。
接著重啟的時候,從磁盤上讀取更新日志,每一條都依次回訪到內(nèi)存里,恢復(fù)出來核心元數(shù)據(jù)即可。
3、檢查點機制:定時持久化全量數(shù)據(jù)
但是這里又有一個問題了,那個磁盤上的日志文件越來越大,因為元數(shù)據(jù)不斷的在更新,不斷在產(chǎn)生最新的變更日志寫入磁盤文件。
那么系統(tǒng)運行一段時間以后,每次重啟都需要從磁盤讀取歷史全部日志,一條一條回放到內(nèi)存來恢復(fù)核心元數(shù)據(jù)嗎?
不可能,所以這里一定要配合引入檢查點機制。
也就是說,每隔一段時間,就需要開啟一個后臺線程,把內(nèi)存里的全部核心元數(shù)據(jù)序列化后寫入磁盤上的元數(shù)據(jù)文件,作為這個時間的一個快照文件,同時清空掉日志文件,這個叫做檢查點操作。
下次重啟,只要把元數(shù)據(jù)文件讀取出來直接反序列化后方入內(nèi)存,然后把上次檢查點之后的變更日志從日志文件里讀出來回放到內(nèi)存里,就可以恢復(fù)出來完整的元數(shù)據(jù)了。
這種方式,可以讓Master重啟很快,因為大部分數(shù)據(jù)都是在檢查點寫入的那個元數(shù)據(jù)文件里。
整個過程,如下圖所示:
4、引入檢查點節(jié)點
但是這個時候又有一個問題了。
大家可以想一下,Master內(nèi)存里的元數(shù)據(jù)需要高并發(fā)的被人訪問和修改,同時每隔一段時間還要檢查點寫入磁盤。
那么在檢查點過程中,是不是需要把內(nèi)存數(shù)據(jù)全部加鎖,不允許別人修改?
在加鎖的時候,把不會變動的數(shù)據(jù)寫入磁盤文件中,但是這個過程是很慢的,意味著此時別人高并發(fā)的寫入操作都需要等待核心元數(shù)據(jù)的鎖。
因為此時別人鎖住了,你無法加鎖去寫數(shù)據(jù)進去,這會導(dǎo)致系統(tǒng)在幾秒內(nèi)出現(xiàn)卡頓無法響應(yīng)請求的問題。
所以此時需要在架構(gòu)設(shè)計里引入一個檢查點節(jié)點,專門負責同步Master的變更日志。
然后在自己內(nèi)存里維護一份一模一樣的核心元數(shù)據(jù),每隔一段時間由檢查點節(jié)點來負責將內(nèi)存數(shù)據(jù)寫入磁盤,接著上傳發(fā)送給Master。
這樣做,就不需要Master自己執(zhí)行檢查點的時候?qū)ψ约簝?nèi)存數(shù)據(jù)進行加鎖了,如下圖。
在這樣的一個架構(gòu)下,對Master來說,他只需要一個后臺線程負責接收Checkpoint進程定時傳送過來的元數(shù)據(jù)文件快照然后寫入本地磁盤就可以了,完全規(guī)避掉了對自己內(nèi)存元數(shù)據(jù)的鎖沖突的問題。
5、總結(jié) & 思考
總結(jié)一下這個架構(gòu)設(shè)計,其實就是Master基于內(nèi)存維護元數(shù)據(jù),這樣一臺物理機可以支撐每秒10萬+的高并發(fā)請求。
每次元數(shù)據(jù)出現(xiàn)更新,寫一條日志到內(nèi)存緩沖區(qū),然后后臺線程去刷新日志到日志文件里去,同時需要發(fā)送一條日志到Checkpoint節(jié)點去。
Checkpoint節(jié)點會在自己內(nèi)存里維護一份一模一樣的元數(shù)據(jù),然后每隔一段時間執(zhí)行checkpoint檢查點寫一份元數(shù)據(jù)文件快照。
接著上傳給Master節(jié)點后清空掉他的日志文件。然后Master節(jié)點每次重啟的時候直接讀取本地元數(shù)據(jù)文件快照,加上回放上次checkpoint之后的日志即可。
這里可能大家會提幾個問題,比如說Master節(jié)點突然宕機會如何?
那很簡單,直接影響就是他內(nèi)存緩沖里的那些日志丟了,導(dǎo)致少量數(shù)據(jù)丟失,這個在我們的場景下可以容忍。
如果Checkpoint節(jié)點宕機怎么辦?
那不要緊,因為他之前上傳過元數(shù)據(jù)文件的快照,所以對Master而言最多就是無法同步數(shù)據(jù)過去。
但是Master重啟,還是可以讀取最近一次的元數(shù)據(jù)快照,然后回放日志即可。
等Checkpoint節(jié)點恢復(fù)了,可以繼續(xù)接著上一次同步日志,然后繼續(xù)執(zhí)行checkpoint操作。?