高並發高可用服務設計思路

如何設計一個支持高併發的高可用服務?在前期設計時應該從哪些方面入手?

明確的一點:沒有哪一個系統是從一開始設計時就是高可用的,支持高併發的。都是在產品的發展壯大中,隨著業務量的增加,逐漸對系統架構進行一步步升級。所以出現了很多‘XXX系統的架構演進之路,日訂單千萬級別的系統演進歷程’等文章。老系統升級的次數多了,再設計新系統時就會考慮為以後的高可用高併發提供擴展,甚至開始設計時就支持,避免後面的升級痛苦。

系統設計層面:

一、分佈式計算

一個高併發的系統,需要對系統功能進行分佈式部署,核心生產流程進行異構化。特別是現如今微服務熱潮,Docker雲,更為分佈式部署提供了有利條件。一個電商系統,接受用戶訂單的功能、並完成支付是優先級最高的,其次是對訂單的生產,最後是運營人員對用戶訂單的管理。那麼就可以根據優先級把系統設計成:Web_Tomcat 、Order_Produce、Manager_Tomcat三個子系統。如果再進一步,還可以把支付功能從Web_Tomcat中拆出來,做成Pay_Tomcat四個應用。四個應用共同完成一筆訂單交易。

系統拆分成多個應用有以下優點:

1、系統計算訂單能力提升,提高擴展性。如果應用HTTP連接數不夠,但是CPU和內存佔用不高,這時候就可以只擴展Web_Tomcat,因為一個應用只接受用戶創建訂單,不做其他複雜計算邏輯,可以部署在一個低配置的Docker上。

2、應用與應用之間從物理上進行隔離。隨著業務的發展,業務邏輯代碼也隨著改變。雖說在設計時會考慮到以後的可擴展性,但還是會有代碼上的更新維護(系統上線後程序員就可以下崗了)。訂單的生產邏輯大都在Order_Produce和Manager_Tomcat應用上,Web_Tomcat僅僅接受用戶下單,邏輯簡單一般不會發生改變。這時候Order_Produce的版本迭代不會對Web_Tomcat造成風險,就算髮布失敗,也不會影響用戶下單。把可預見到的改變和不變進行隔離,也就是關注點分離。

3、為以後的團隊拆分打好基礎。支付應用Pay_Tomcat會涉及到跟銀行對接,財務對賬等一系列操作,而且這部分操作跟某個具體的業務沒有關係。隨著公司團隊的發展壯大,這個應用就可以建設一個獨立的團隊進行維護。

4、提升對變化的響應速度。因為子系統的功能獨立,由獨立的團隊進行維護,獨立的技術棧。對新需求的響應,不需要依賴其他應用,需要升級時可以選擇合適的技術棧。代碼量小,重構起來也方便。

任何事物都是雙面的,優點與缺點是共存的。缺點是:

1、拆分之後系統的複雜度提高,原來運行在一個JVM進程中的應用會運行在多個JVM中。

2、RPC框架的選擇

多個JVM進程間如何通信,該如何選擇RPC框架,是選擇同步處理還是異步處理,這都是需要架構師考慮的。一般需要立即獲取執行結果的調用選擇同步,這類型的框架有dubbo thirft webservice hession等,也可選擇HTTP RESTfull。如果不需要立即得到執行結果,只是通知遠端的JVM或者只需要發送一條數據,至於遠端的JVM什麼時候執行無需關注,則可以選擇使用MQ,這類型框架有Active MQ 、Rabbit MQ等。

3、依賴複雜度提高

在一個單體應用上執行可能不需要調用遠端服務或者很少涉及到對第三方服務的調用,但是如果把一個單塊應用拆分成多個,就會增加依賴,拆分的粒度越細依賴複雜度越高,這也是微服務設計時的一個難點。

依賴複雜度高了,相應的對每個依賴的管理越嚴格了。比如給每個依賴分配的線程個數CPU時間片網絡IO等,不能因為某一個外部依賴響應延遲就導致其他服務不可用,這是不可容忍的。關於如何分配資源,監控依賴和實現FastFail可以參考

3、分佈式事務

不論是系統拆分成多個子系統還是一個單塊應用拆分成多個微服務,很多時候都會從功能上考慮拆分,指責單一高內聚原則,儘量避免出現分佈式事務問題。如果無法避免可以從以下兩方面考慮:強一致性和最終一致性。強一致性需要讓每一個參與事務的服務都能提供undo操作,類似於2PC(二階段提交)3PC。弱一致性就可以通過異步實現最終一致性。

二、多機房入口

用戶的請求是從機房進去的,應用部署的再多,入口擁擠,用戶請求進不去也是無用。特別是現在動不動就某個機房網絡擁堵,動不動就光纖被挖斷。增加請求入口,把用戶請求分散到多個機房,解決請求入口擁堵問題。只有把這個問題解決了,請求才會打到後端應用上。

三、CDN加速

任何一個互聯網系統面向的用戶都遍佈於地球的每個角落,每個角落的請求到機房可用多種路徑可選,正所謂條條大路通羅馬,這裡是條條路徑通機房。其中有速度快的路徑有慢的路徑,如何選擇最優路徑,把每個角落的請求快速的傳遞到機房,這就是CDN的功能。

四、HTML頁面靜態化

這是最長見的一種優化方式,成本也最低,不需要考慮硬件成本。靜態頁面部署在NGNIX中,收到用戶請求,Ngnix不需要訪問Webapp即可響應用戶,減少應用渲染頁面的時間,同時也降低了應用的壓力。

五、Cache

這也是常見的一種優化方式,在數據庫層之上加一層緩存,減少對數據庫的訪問壓力。緩存中的數據都是存儲在內存裡的,而數據庫中的數據是寫在磁盤上的,訪問內存肯定是比訪問磁盤快的可不止一個數量級。

六、數據庫拆分

當數據量達到某個閥值時,數據庫拆分就會成為一個緊急的需求。一般從業務上進行垂直拆分,如果業務單一,也可從水平上進行拆分。拆分的原則一般是:避免跨數據庫事務和如何選擇shardingId。跨數據庫事務可以選擇在前期調研時把同一事務中的表放在一個數據庫中。如果數據冷熱不均shardingId可以是UserPin或者訂單號Hash打散後的值,如果數據冷熱均勻可以按段分庫也可以對某一個值取模後的值。

多數據庫事務管理,現在業界有很多已成型的中間價,如阿里的Corba 360的 MyBatis本身的代理等。大致可以分為兩類,一類是在Webapp層進行選擇數據源,一類是在代理層面上對SQL語句解析選擇數據源。後者需要配置shardingId,只有通過shardingId作為where的SQL語句才能針對某個數據源進行操作,其餘都是對所有數據源操作。

Spring本身也提供了一部分選擇數據源的功能,如AbstractRoutingDataSource和一些懶加載的數據源代理類等,也可以自己包裝JdbcDataSourceTransaction實現對多數據源的事務管理。在事務開啟時,傳入shardingId路由數據源然後對其進行SQL操作。這種方式會比第三方中間件更靈活,但對開發者的要求也更高。

七、多線程

接下來的方式就是在開發層面上的優化了。現在的機器都是多核的,如果還像之前那樣編寫串行的代碼,那多核機器就是個浪費。如何編寫多線程應用比較簡單這裡就不在贅述了,重點討論下如何管理線程。線程是機器寶貴且有限的資源,線程數量太多,上下文頻繁的切換也會帶來性能消耗,太少又不能物盡其用。線程可以交給線程池管理,設置好最大和核心線程數,存活時間等重要參數。

八、CAS指令

九、Nginx反向代理


分享到:


相關文章: