文章摘要: 高效能訊息讀取 高效能訊息寫入 高可用訊息儲存 高可用訊息讀取 設計備選方案 架構師需要對已存在的技術非常熟悉其複雜度為 高效能訊息讀取 高效能訊息寫入 高可用訊息儲存 高可用訊息讀取 現在就設計備選方案
結合複雜度來源和架構設計原則,我們一起來看看實踐中如何進行架構設計。
識別複雜度
由於架構設計的目的是爲了解決軟件系統的複雜性,因此我們在設計架構之前就要先分析系統的複雜度。
如果一個系統的複雜度本來是業務邏輯太複雜,功能耦合嚴重,架構師卻設計了一個TPS達到50000的高效能架構,那麼這個設計沒有任何意義,沒有解決當前的複雜性問題。
高效能、高可用、可擴充套件等幾個方面,一般情況下複雜度只是其中的一個,而如果當前架構需要解決三個或者三個以上的複雜度問題,要麼設計時出了問題,要麼對當前架構的判斷是不正確的。
舉個例子,一個(業務普通的)小應用,非要對標騰訊QQ的架構,硬是要設計高TPS高使用者量的架構,除了設計和開發時間長外,還出現了各種各樣的問題:
- 系統複雜,運維效率低下
- 迭代開發效率低
- 問題定位複雜
- 目標為高效能,卻表現出低效能
同時,面對這樣一個燙手山芋,想要改造也不簡單:
- 需要改的地方太多,無從下手
- 落地時間太久遠
- 多個解決方案衝突,不好操作
這時,最好將主要的複雜度問題都列出來,然後根據實際情況排序,按優先順序解決。
存在可能:按優先順序解決多個複雜度,前一個問題剛解決成功落地,後一個問題卻需要推翻前一個重來。因此,解決方案的敲定也需要斟酌,一般可能存在一個方案或引入一種新技術,能同時解決前後的問題。
案例
假設一個微博like的系統:
- 使用者發微博後,微博子系統通知稽覈子系統,再通知統計子系統,再通知廣告子系統…即在一條微博發出後,微博子系統需要呼叫十幾個子系統的介面。開發、測試效率低下。
- 如果使用者有多個等級,比如VIP、SVIP..(現在很流行有很多種亂七八糟的會員啊),那麼上述的呼叫將會更復雜。
通過對當前架構的瞭解,分析得問題的根源是系統強耦合,較好的方法就是引入訊息佇列解耦。
該系統相關人員立項,並總結當前資訊:
- 中介軟體人員大約6人
- 團隊熟悉Java,少數精通C/C++
- 平臺選用Linux,資料庫MySQL
- 單機房部署
高效能複雜度分析
假設該系統的使用者每天傳送1000萬條微博,平均一條微博有10個子系統讀取,那麼子系統讀取大約為1億次。換算過來,每秒寫入115條,每秒讀取1150條,按峰值三倍算,TPS=345,QPS=3450,總體來說目前階段效能需求就這樣。
考慮後續業務發展,效能需要再向後考慮多一點,按四倍擴充套件算,TPS=1380,QPS=13800,這種程度下,高效能讀取已經是複雜度之一了。
高可用複雜度分析
如稽覈子系統的高可用是必須要保證的,如等級子系統的高可用的必要性雖然沒那麼高,但是卻十分影響業務的正常功能和使用者的滿意度。
因此高可用也是需要的。
高可擴充套件複雜度分析
訊息佇列整個系統中只充當了一箇中間件,只需要保證佇列功能即可。因此功能需求較為明確,無需過多考慮可擴充套件性。
分析總結
該中介軟體的複雜性主要表現在:
- 高效能訊息讀取
- 高效能訊息寫入
- 高可用訊息儲存
- 高可用訊息讀取
設計備選方案
架構師需要對已存在的技術非常熟悉,對已驗證過的架構模式爛熟於心,只有目前的模式無法滿足需求的情況下,才需要進行方案創新。而目前大部分情況下,創新方案也是基於已有的成熟技術,如:
- NoSQL,KV儲存與資料庫索引類似
- Hadoop,基礎是叢集方案+資料複製方案
- Docker虛擬化,基礎LXC
- LevelDB,其檔案儲存結構是Skip List
新技術都是在現有技術的基礎上發展起來的,現有技術又來源與先前的技術。將技術進行功能性分組,可以大大簡化設計過程,這是技術「模組化」的首要原因。技術的」組合「和」遞迴「特徵,將徹底改變我們對技術本質的認識。
設計備選方案容易犯的錯
設計最優秀的方案
架構設計中最常見的錯誤是「設計最優秀的方案」,即比拼業界最優秀的架構,但適合自己業務、團隊、技術能力的方案纔是好方案。
只做一個方案
還有一種常見的錯誤是「只做一個方案」,存在問題:
- 可能第二種方案也有優點,卻因為簡單的評估被篩選掉了
- 架構師個人能力有限導致判斷錯誤,就會導致方案並不是最合適的
- 單一方案設計會出現過度辯護的情況
因此:
- 設計備選方案3-5個
- 備選方案之間的差異要明顯
- 備選方案不要侷限於已經熟悉的技術
備選方案過於詳細
第三種常見的錯誤是「備選方案過於詳細」,存在問題:
- 會耗費了大量的時間和精力
- 注意了細節會容易忽略整體
- 方案評審容易被細節吸引,導致評審效果不佳
備選方案階段關注的是技術選型,而不是技術細節,關鍵是差異要夠大。如Zookeeper和Keepalived兩種不同技術差異就夠大,而Zookeeper的xx方案和xx方案則沒有那麼大的差異。
案例
故事說到微博系統需要一個訊息佇列,其複雜度為
- 高效能訊息讀取
- 高效能訊息寫入
- 高可用訊息儲存
- 高可用訊息讀取
現在就設計備選方案:
- 採用開源的Kafka,優點為方案成熟
- 叢集 + MySQL儲存 + Netty,叢集負載均衡滿足高效能讀取,MySQL主備複製滿足高可用儲存和高可用讀取
- 叢集 + 自研儲存方案 + Netty,由於方案中MySQL關係型資料庫的特點不是很契合訊息佇列的資料特點,因此也可以參考Kafka重新設計一套
架構師的技術儲存越豐富,備選方案可能就越多,如開源方案也能是ActiveMQ、RabbitMQ,叢集也能是HBase、Redis等等。
評估和選擇備選方案
在完成備選方案後,如果挑選出最終的方案也較難,因為:
- 每個方案都可行,因為不可行的方案是不能作為備選方案的
- 每個方案都有自己的優缺點
- 評價標註主觀性比較強,因為複雜和困難是很難去量化的,特別是在對某技術部熟悉的情況下
存在那麼幾個型別的選擇方案,不一定適用於所有場合:
- 最簡派,哪個原理簡單用哪個
- 最牛派,哪個效能好、功能強等等就用哪個
- 最熟派,熟悉哪個選哪個
- 領導派,給領導選
在評估時,遵循「合適原則」、「簡單原則」,避免貪大基本上就可以了。同時,不能太過長遠地考慮業務暴漲的情況,應該遵循「演化原則」,避免多過地一步到位。如果存在業務暴漲超過預期,那麼值得重構。
存在幾種看似正確但實際錯誤的方案:
- 數量對比法,哪個方案優點多就選哪個。主要問題是沒有考慮優先順序
- 加權法。主要問題是評選會漸漸變成數字遊戲
正確的做法:按當前業務、團隊規模、技能、業務未來發展的實際情況,將質量屬性(效能、可用性、成本、安全、可擴充套件等)按照優先順序排序,首先挑選滿足第一優先順序的,如果都滿足再挑選滿足第二優先順序的,類推。
案例
上回故事說到微博中介軟體系統設計了三個備選方案,接下來就是方案評選會議。
方案一:採用開源Kafka方案
主管傾向於Kafka方案,因為成熟。
中介軟體團隊支援Kafka,因為能節省開發投入,但部分人員認為Kafka不太適合業務場景。
運維反對Kafka,因為運維團隊沒有Scala的開發經驗,問題處理困難。
測試傾向於Kafka,因為成熟。
方案二:叢集 + MySQL儲存
中介軟體研發人員認為較簡單,部分對效能持懷疑態度,並覺得用MySQL做訊息佇列很low。
運維團隊贊同該方案,因為MySQL運維難度低。
測試代表認為該方案需要投入較大的人力,包括功能測試、效能測試、可靠性測試。
主管不肯定也不否定,只要保證業務穩定可靠即可。
方案三:叢集 + 自研儲存系統
中介軟體團隊較為支援,是展現團隊例項的一個好機會,效能比方案二要好,但人力研發成本較大、迭代時間較長。
運維不贊成,因為不相信研發團隊實力,容易造成運維失誤。
測試團隊贊同運維團隊的意見,測試難度大。
主管保留一鍵,新系統一般迭代較差。
根據屬性評審
質量屬性 | 方案一 | 方案二 | 方案三 |
---|---|---|---|
效能 | 高 | 中 | 高 |
複雜度 | 低,開箱即用 | 中 | 高,研發成本高 |
硬體成本 | 低 | 高,叢集部署 | 低,和Kafka一樣 |
可運維性 | 低,無法融入現有運維體系,且無Scala經驗 | 高,可以融入現有運維體系,MySQL經驗成熟 | 高,無需維護MySQL |
可靠性 | 高,成熟方案 | 高,MySQL成熟方案 | 低,自研需要迭代 |
人力投入 | 低 | 中,開發叢集伺服器 | 高 |
架構師選擇了方案二,原因有:
- 從可運維性排除了方案一,如果上線了出問題不能技術修復就無法滿足業務需求,且Kafka的設計目標為日誌傳輸而非訊息傳輸。
- 從複雜度排除方案三,因為人力成本不足。
- 方案二優點是複雜度不高,可靠性有保障。
但方案二也有缺點:
- 效能容易到瓶頸,但目前業務不高
- 硬體成本較高
- 技術不優越,但是適應業務
評審總結
備選方案的選擇和很多因素有關,並不能單純地考慮技術因素,不同的業務會造成不同的選擇。
詳細方案設計
詳細方案設計就是在選擇方案後,將方案設計的關鍵技術細節給確定下來。
舉幾個例子:
- 如果確定使用Elasticsearch全文搜尋,那麼就要確認的索引是按照業務劃分的還是直接用一個大索引,副本數量和叢集節點數量是多少。
- 如果確定使用MySQL分庫分表,那麼就要確定那些表要分,按哪個維度分,分了之後的處理方法。
- 如果確定引入Nginx來做負載均衡,那麼Nginx的主備怎麼做,負載均衡的策略用哪個。
看起來和備選方案類似,但簡單了一點,因為無需選擇,只需要簡單地根據原物場景選擇詳細技術就好。
詳細設計方案階段可能會遇到一種情況,就是發現備選方案不可行,可能是因為在設計時遺漏,可以通過如下方法有效避免:
- 架構師不但要進行備選方案設計還要對備選方案的關鍵細節有較深的瞭解。
- 通過分步驟、分階段、分系統,能有效降低方案的複雜度。
- 如果方案本身很複雜,那麼就採取設計團隊的方式進行設計,即避免一兩人出現了盲區導致方案出問題。
案例
上回故事去到微博訊息系統根據方案二設計詳細方案。
資料庫表的設計
- 設計兩類表,一類是日誌表,用於訊息寫入時快速儲存到MySQL,另一類是訊息表,每個訊息佇列一張表。
- 釋出訊息時,先寫入日誌表,日誌表寫入成功就代表訊息寫入成功,後臺執行緒從日誌表中讀取訊息寫入訊息表中。
- 讀取訊息時,從訊息表中讀取。
- 日誌表包含欄位有:日誌ID、釋出者資訊、釋出時間、佇列名稱、訊息內容
- 訊息表包含欄位有: 訊息ID、訊息內容、訊息釋出時間、訊息釋出者
- 日誌表需要及時清楚已經寫入訊息表的日誌,訊息表可以設定儲存較長的時間
資料如何複製
直接採用MySQL主從複製,而且只複製訊息表。
主備伺服器如何倒換
採用Zookeeper來做主備決策,主備伺服器都連線到Zookeeper建立自己的節點,主伺服器路徑規則為 /MQ/server/{id}/master
,備機為 /MQ/server/{id}/slave
,節點型別為EPHEMERAL。
當發現主伺服器斷開,備機修改自己的狀態,對外提供訊息讀取服務。
業務伺服器寫入訊息的方法
訊息佇列提供SDK供各業務系統呼叫,SDK從配置中讀取所有訊息佇列系統的伺服器資訊,SDK輪詢發起寫入請求給主伺服器,如果請求錯誤則請求下一臺。
業務伺服器讀取訊息的方法
訊息佇列提供SDK供各業務系統呼叫,輪詢方案與寫入類似。
訊息佇列伺服器需要記錄每個消費者的消費狀態,即當前消費者已經讀取到哪條訊息,當收到訊息讀取請求時,返回下一條未讀訊息給消費者
業務伺服器和訊息佇列伺服器之間的通訊協議
爲了提升相容性,傳輸協議用TCP,資料格式使用ProtocolBuffer。
總結
學習了架構設計四部曲,分別是識別複雜度、設計備選方案、評估和選擇備選方案、詳細方案設計,以及通過一個微博訊息系統的方案設計案例,更深入地熟悉架構設計。
先這樣吧
若有錯誤之處請指出,更多地關注煎魚。