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