架構(gòu)設(shè)計備用方案
今天我們來聊聊講架構(gòu)設(shè)計之如何設(shè)計備用方案,同樣還會結(jié)合之前的微博場景,談?wù)勏㈥犃性O(shè)計備選方案的實戰(zhàn)。
設(shè)計備用方案
架構(gòu)師的工作并不神秘,成熟的架構(gòu)師需要對已經(jīng)存在的技術(shù)非常熟悉,對已經(jīng)經(jīng)過驗證的架構(gòu)模式爛熟于心,然后根據(jù)自己對業(yè)務(wù)的理解,挑選合適的架構(gòu)模式進行組合,再對組合后的方案進行修改和調(diào)整。
雖然軟件技術(shù)經(jīng)過幾十年的發(fā)展,新技術(shù)層出不窮,但是經(jīng)過時間考驗,已經(jīng)被各種場景驗證過的成熟技術(shù)其實更多。例如,高可用的主備方案、集群方案,高性能的負載均衡、多路復用,可擴展的分層、插件化等技術(shù),絕大部分時候我們有了明確的目標后,按圖索驥就能夠找到可選的解決方案。
只有當這種方式完全無法滿足需求的時候,才會考慮進行方案的創(chuàng)新,而事實上方案的創(chuàng)新絕大部分情況下也都是基于已有的成熟技術(shù)。
NoSQL:Key-Value的存儲和數(shù)據(jù)庫的索引其實是類似的,Memcache只是把數(shù)據(jù)庫的索引獨立出來做成了一個緩存系統(tǒng)。
Hadoop大文件存儲方案,基礎(chǔ)其實是集群方案+ 數(shù)據(jù)復制方案。
Docker虛擬化,基礎(chǔ)是LXC(Linux Containers)。
LevelDB的文件存儲結(jié)構(gòu)是Skip List。
在《技術(shù)的本質(zhì)》一書中,對技術(shù)的組合有清晰的闡述:
新技術(shù)都是在現(xiàn)有技術(shù)的基礎(chǔ)上發(fā)展起來的,現(xiàn)有技術(shù)又來源于先前的技術(shù)。將技術(shù)進行功能性分組,可以大大簡化設(shè)計過程,這是技術(shù)“模塊化”的首要原因。技術(shù)的“組合”和“遞歸”特征,將徹底改變我們對技術(shù)本質(zhì)的認識。
雖說基于已有的技術(shù)或者架構(gòu)模式進行組合,然后調(diào)整,大部分情況下就能夠得到我們需要的方案,但并不意味著架構(gòu)設(shè)計是一件很簡單的事情。因為可選的模式有很多,組合的方案更多,往往一個問題的解決方案有很多個;如果再在組合的方案上進行一些創(chuàng)新,解決方案會更多。因此,如何設(shè)計最終的方案,并不是一件容易的事情,這個階段也是很多架構(gòu)師容易犯錯的地方。
第一種常見的錯誤:設(shè)計最優(yōu)秀的方案。
很多架構(gòu)師在設(shè)計架構(gòu)方案時,心里會默認有一種技術(shù)情結(jié):我要設(shè)計一個優(yōu)秀的架構(gòu),才能體現(xiàn)我的技術(shù)能力!例如,高可用的方案中,集群方案明顯比主備方案要優(yōu)秀和強大;高性能的方案中,淘寶的XX方案是業(yè)界領(lǐng)先的方案……
根據(jù)架構(gòu)設(shè)計原則中“合適原則”和“簡單原則“的要求,挑選合適自己業(yè)務(wù)、團隊、技術(shù)能力的方案才是好方案;否則要么浪費大量資源開發(fā)了無用的系統(tǒng)(例如,之前提過的“億級用戶平臺”的案例,設(shè)計了TPS 50000的系統(tǒng),實際TPS只有500),要么根本無法實現(xiàn)(例如,10個人的團隊要開發(fā)現(xiàn)在的整個淘寶系統(tǒng))。
第二種常見的錯誤:只做一個方案。
很多架構(gòu)師在做方案設(shè)計時,可能心里會簡單地對幾個方案進行初步的設(shè)想,再簡單地判斷哪個最好,然后就基于這個判斷開始進行詳細的架構(gòu)設(shè)計了。
這樣做有很多弊端:
心里評估過于簡單,可能沒有想得全面,只是因為某一個缺點就把某個方案給否決了,而實際上沒有哪個方案是完美的,某個地方有缺點的方案可能是綜合來看最好的方案。
架構(gòu)師再怎么牛,經(jīng)驗知識和技能也有局限,有可能某個評估的標準或者經(jīng)驗是不正確的,或者是老的經(jīng)驗不適合新的情況,甚至有的評估標準是架構(gòu)師自己原來就理解錯了。
單一方案設(shè)計會出現(xiàn)過度辯護的情況,即架構(gòu)評審時,針對方案存在的問題和疑問,架構(gòu)師會竭盡全力去為自己的設(shè)計進行辯護,經(jīng)驗不足的設(shè)計人員可能會強詞奪理。
因此,架構(gòu)師需要設(shè)計多個備選方案,但方案的數(shù)量可以說是無窮無盡的,架構(gòu)師也不可能窮舉所有方案,那合理的做法應(yīng)該是什么樣的呢?
備選方案的數(shù)量以3 ~ 5個為最佳。少于3個方案可能是因為思維狹隘,考慮不周全;多于5個則需要耗費大量的精力和時間,并且方案之間的差別可能不明顯。
備選方案的差異要比較明顯。例如,主備方案和集群方案差異就很明顯,或者同樣是主備方案,用ZooKeeper做主備決策和用Keepalived做主備決策的差異也很明顯。但是都用ZooKeeper做主備決策,一個檢測周期是1分鐘,一個檢測周期是5分鐘,這就不是架構(gòu)上的差異,而是細節(jié)上的差異了,不適合做成兩個方案。
備選方案的技術(shù)不要只局限于已經(jīng)熟悉的技術(shù)。設(shè)計架構(gòu)時,架構(gòu)師需要將視野放寬,考慮更多可能性。很多架構(gòu)師或者設(shè)計師積累了一些成功的經(jīng)驗,出于快速完成任務(wù)和降低風險的目的,可能自覺或者不自覺地傾向于使用自己已經(jīng)熟悉的技術(shù),對于新的技術(shù)有一種不放心的感覺。就像那句俗語說的:“如果你手里有一把錘子,所有的問題在你看來都是釘子”。例如,架構(gòu)師對MySQL很熟悉,因此不管什么存儲都基于MySQL去設(shè)計方案,系統(tǒng)性能不夠了,首先考慮的就是MySQL分庫分表,而事實上也許引入一個Memcache緩存就能夠解決問題。
第三種常見的錯誤:備選方案過于詳細。
有的架構(gòu)師或者設(shè)計師在寫備選方案時,錯誤地將備選方案等同于最終的方案,每個備選方案都寫得很細。這樣做的弊端顯而易見:
耗費了大量的時間和精力。
將注意力集中到細節(jié)中,忽略了整體的技術(shù)設(shè)計,導致備選方案數(shù)量不夠或者差異不大。
評審的時候其他人會被很多細節(jié)給繞進去,評審效果很差。例如,評審的時候針對某個定時器應(yīng)該是1分鐘還是30秒,爭論得不可開交。
正確的做法是備選階段關(guān)注的是技術(shù)選型,而不是技術(shù)細節(jié),技術(shù)選型的差異要比較明顯。例如,采用ZooKeeper和Keepalived兩種不同的技術(shù)來實現(xiàn)主備,差異就很大;而同樣都采用ZooKeeper,一個方案的節(jié)點設(shè)計是/service/node/master,另一個方案的節(jié)點設(shè)計是/company/service/master,這兩個方案并無明顯差異,無須在備選方案設(shè)計階段作為兩個不同的備選方案,至于節(jié)點路徑究竟如何設(shè)計,只要在最終的方案中挑選一個進行細化即可。
設(shè)計備選方案實戰(zhàn)
還是回到微博的場景,上期我們通過“排查法”識別了消息隊列的復雜性主要體現(xiàn)在:高性能消息讀取、高可用消息寫入、高可用消息存儲、高可用消息讀取。接下來進行第2步,設(shè)計備選方案。
1.備選方案1:采用開源的Kafka
Kafka是成熟的開源消息隊列方案,功能強大,性能非常高,而且已經(jīng)比較成熟,很多大公司都在使用。
2.備選方案2:集群 + MySQL存儲
首先考慮單服務(wù)器高性能。高性能消息讀取屬于“計算高可用”的范疇,單服務(wù)器高性能備選方案有很多種??紤]到團隊的開發(fā)語言是Java,雖然有人覺得C/C++語言更加適合寫高性能的中間件系統(tǒng),但架構(gòu)師綜合來看,認為無須為了語言的性能優(yōu)勢而讓整個團隊切換語言,消息隊列系統(tǒng)繼續(xù)用Java開發(fā)。由于Netty是Java領(lǐng)域成熟的高性能網(wǎng)絡(luò)庫,因此架構(gòu)師選擇基于Netty開發(fā)消息隊列系統(tǒng)。
由于系統(tǒng)設(shè)計的QPS是13800,即使單機采用Netty來構(gòu)建高性能系統(tǒng),單臺服務(wù)器支撐這么高的QPS還是有很大風險的,因此架構(gòu)師選擇采取集群方式來滿足高性能消息讀取,集群的負載均衡算法采用簡單的輪詢即可。
同理,“高可用寫入”和“高性能讀取”一樣,可以采取集群的方式來滿足。因為消息只要寫入集群中一臺服務(wù)器就算成功寫入,因此“高可用寫入”的集群分配算法和“高性能讀取”也一樣采用輪詢,即正常情況下,客戶端將消息依次寫入不同的服務(wù)器;某臺服務(wù)器異常的情況下,客戶端直接將消息寫入下一臺正常的服務(wù)器即可。
整個系統(tǒng)中最復雜的是“高可用存儲”和“高可用讀取”,“高可用存儲”要求已經(jīng)寫入的消息在單臺服務(wù)器宕機的情況下不丟失;“高可用讀取”要求已經(jīng)寫入的消息在單臺服務(wù)器宕機的情況下可以繼續(xù)讀取。架構(gòu)師第一時間想到的就是可以利用MySQL的主備復制功能來達到“高可用存儲“的目的,通過服務(wù)器的主備方案來達到“高可用讀取”的目的。
具體方案:

簡單描述一下方案:
采用數(shù)據(jù)分散集群的架構(gòu),集群中的服務(wù)器進行分組,每個分組存儲一部分消息數(shù)據(jù)。
每個分組包含一臺主MySQL和一臺備MySQL,分組內(nèi)主備數(shù)據(jù)復制,分組間數(shù)據(jù)不同步。
正常情況下,分組內(nèi)的主服務(wù)器對外提供消息寫入和消息讀取服務(wù),備服務(wù)器不對外提供服務(wù);主服務(wù)器宕機的情況下,備服務(wù)器對外提供消息讀取的服務(wù)。
客戶端采取輪詢的策略寫入和讀取消息。
3.備選方案3:集群 + 自研存儲方案
在備選方案2的基礎(chǔ)上,將MySQL存儲替換為自研實現(xiàn)存儲方案,因為MySQL的關(guān)系型數(shù)據(jù)庫的特點并不是很契合消息隊列的數(shù)據(jù)特點,參考Kafka的做法,可以自己實現(xiàn)一套文件存儲和復制方案(此處省略具體的方案描述,實際設(shè)計時需要給出方案)。
可以看出,高性能消息讀取單機系統(tǒng)設(shè)計這部分時并沒有多個備選方案可選,備選方案2和備選方案3都采取基于Netty的網(wǎng)絡(luò)庫,用Java語言開發(fā),原因就在于團隊的Java背景約束了備選的范圍。通常情況下,成熟的團隊不會輕易改變技術(shù)棧,反而是新成立的技術(shù)團隊更加傾向于采用新技術(shù)。
上面簡單地給出了3個備選方案用來示范如何操作,實踐中要比上述方案復雜一些。架構(gòu)師的技術(shù)儲備越豐富、經(jīng)驗越多,備選方案也會更多,從而才能更好地設(shè)計備選方案。例如,開源方案選擇可能就包括Kafka、ActiveMQ、RabbitMQ;集群方案的存儲既可以考慮用MySQL,也可以考慮用HBase,還可以考慮用Redis與MySQL結(jié)合等;自研文件系統(tǒng)也可以有多個,可以參考Kafka,也可以參考LevelDB,還可以參考HBase等。限于篇幅,這里就不一一展開了。
小結(jié)
今天我向你講了架構(gòu)設(shè)計流程之設(shè)計備選方案,基于我們模擬的案例場景微博消息系統(tǒng),給出了備選方案的設(shè)計。
如果本文對你有幫助的話,歡迎點贊分享,這對我繼續(xù)分享&創(chuàng)作優(yōu)質(zhì)文章非常重要。感謝 !