一、背景
隨着互聯網的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分布式服務架構以及流動計算架構勢在必行,亟需一個治理系統確保架構有條不紊的演進。
二、傳統應用到分布式應用的演進過程
1、單體應用架構
當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。此時,用於簡化增刪改查工作量的數據訪問框架(ORM)是關鍵,例如SSM,Spring MVC,配合nginx做負載均衡使用已經滿足業務需要。
2、多個垂直應用架構
當訪問量逐漸增大,單一應用增加機器帶來的加速度越來越小,將應用拆成互不相干的幾個應用,以提升效率。此時,用於加速前端頁面開發的Web框架(MVC)是關鍵。但是不同應用之間無法高效的調用。
3、分布式服務架構
當垂直應用越來越多,應用之間交互不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務復用及整合的分布式服務框架(RPC)是關鍵,實現不同的應用之間的高效調用。
4、流動計算架構(soa服務化)
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個調度中心基於訪問壓力實時管理集群容量,動態更新調度策略,提高集群利用率。此時,用於提高機器利用率的資源調度和治理中心(SOA)是關鍵。
三、微服務的需求
在大規模服務化之前,應用可能只是通過RMI或Hessian等工具,簡單的暴露和引用遠程服務,通過配置服務的URL地址進行調用,通過F5等硬件進行負載均衡。
(1) 服務注冊與發現: 當服務越來越多時,服務URL配置管理變得非常困難,F5硬件負載均衡器的單點壓力也越來越大。
此時需要一個服務注冊中心,動態的注冊和發現服務,使服務的位置透明。
並通過在消費方獲取服務提供方地址列表,實現軟負載均衡和Failover,降低對F5硬件負載均衡器的依賴,也能減少部分成本。
(2) 服務依賴關系:當進一步發展,服務間依賴關系變得錯蹤復雜,甚至分不清哪個應用要在哪個應用之前啟動,架構師都不能完整的描述應用的架構關系。
這時,需要自動畫出應用間的依賴關系圖,以幫助架構師理清理關系。
(3) 服務監控與統計: 接着,服務的調用量越來越大,服務的容量問題就暴露出來,這個服務需要多少機器支撐?什么時候該加機器?
為了解決這些問題,第一步,要將服務現在每天的調用量,響應時間,都統計出來,作為容量規划的參考指標。
其次,要可以動態調整權重,在線上,將某台機器的權重一直加大,並在加大的過程中記錄響應時間的變化,直到響應時間到達閥值,記錄此時的訪問量,再以此訪問量乘以機器數反推總容量。
(4) 服務黑名單:規模繼續擴大,應用之間不再是扁平的對應關系,開始分層,比如核心數據層,業務集成層等,就算沒有出現循環依賴,也不允許從低層向高層依賴,以免后續被逼循環依賴。
這時,需要在注冊中心定義架構體系,列明有哪些層的定義,每個服務暴露或引用時,都必須聲明自己應用屬於哪一層,這樣注冊中心能更快的發現架構的腐化現象。
(5) 服務文檔:服務多了,溝通成本也開始上升,調某個服務失敗該找誰?服務的參數都有什么約定?
這時就需要登記每個服務都是誰負責的,並建立一個服務的文檔庫,方便檢索。
(6) 服務安全:慢慢一些敏感數據也都服務化了,安全問題開始變得重要,誰能調該服務?如何授權?
這樣的服務可能需要一個密碼,訪問時需帶着此密碼,但如果用密碼,要改密碼時,就會很不方便,所有的消費方都要改,所以動態生成令牌(Token)可能會更好,提供方將令牌告之注冊中心,由注冊中心決定是否告之消費方,這樣就能在注冊中心頁面上做復雜的授權模型。
(7) 服務限流:就算是不敏感的服務,也不是能任意調用,比如某服務突然多了一個消費者,這個消費者的請求量直接把服務給拖跨了,其它消費者跟着一起故障。
首先服務提供方需要流控,當流程超標時,能拒絕部分請求,進行自我保護。
其次,消費者上線前和提供者約定《服務質量等級協定(SLA)》,SLA包括消費者承諾每天調用量,請求數據量,提供方承諾響應時間,出錯率等,將SLA記錄在監控中心,定時與監控數據對比,超標則報警。
(8) 服務路由:雖然有SLA約定,如果不能控制,就只是君子協定,如何確保服務質量?
比如:一個應用很重要,一個不那么重要,它們調用同一個服務,這個服務就應該向重要應用傾斜,而不是一視同仁,當支撐不住時,應限制不重要應用的訪問,保障重要應用的可用,如何做到這一點呢。這時,就需要服務路由,控制不同應用訪問不同機器,比如:
應用分離:
consumer.application = foo => provider.host = 1,2,3
consumer.application != foo => provider.host = 5,6
讀寫分離:
method.name = find*,get* => provider.host = 1,2,3
method.name != find*,get* => provider.host = 5,6
(9) 服務自動化測試 :服務上線后,需要驗證服務是否可用,但因防火牆的限制,線下是不能訪問線上服務的,不得不先寫好一個測試Main,然后放到線上去執行,非常麻煩,並且容易忘記驗證。
所以線上需要有一個自動運行的驗證程序,用戶只需在界面上填上要驗證的服務方法,以及參數值和期望的返回值,當有一個服務提供者上線時,將自動運行該用例,並將運行結果發郵件通知負責人。
(10) 服務編排 :服務應用和Web應用是有區別的,它是一個后台Daemon程序,不需要Tomcat之類的Web容器。但因公司之前以Web應用為主,規范都是按Web應用的,所以不得不把服務跑在一個根本用不上的Web容器里,而搭一個這樣的Web工程也非常費事。
所以需要實現一個非Web的容器,只需簡單的Main加載Spring配置即可,並提供Maven模板工程,只需mvn dubbo:generate 即可創建一個五臟俱全的服務應用。
(11) 服務開發IDE :開發服務的人越來越多,更注重開發效率,IDE的集成支持必不可少。
通過插件,可以在Idea、Eclipse中直接運行服務,提供方可以直接填入測試數據測試服務,消費方可以直接Mock服務不依賴提供方開發。
(12) 服務審批:因為暴露服務很簡單,服務的上線越來越隨意,有時候負責服務化的架構師都不知道有人上線了某個服務,使得線上服務魚龍混雜,甚至出現重復的服務,而服務下線比上線還困難。
需要一個新服務上線審批流程,必須經過服務化的架構師審批過了,才可以上線。
而服務下線時,應先標識為過時,然后通知調用方盡快修改調用,直到沒有人調此服務,才能下線。
(13) 服務兼容性檢測:因服務接口設計的經驗一直在慢慢的積累過程中,很多接口並不能一促而蹴,在修改的過程中,如何保證兼容性,怎么判斷是否兼容?另外,更深層次的,業務行為兼容嗎?
可以根據使用的協議類型,分析接口及領域模型的變更是否兼容,比如:對比加減字段,方法簽名等。
而業務上,可能需要基於自動回歸測試用例,形成Technology Compatibility Kit (TCK),確保兼容升級。
(14) 服務降級:隨着服務的不停升級,總有些意想不到的事發生,比如cache寫錯了導致內存溢出,故障不可避免,每次核心服務一掛,影響一大片,人心慌慌,如何控制故障的影響面?服務是否可以功能降級?或者資源劣化?
應用間聲明依賴強度,哪些功能強依賴,哪些弱依賴,然后基於依賴強度,計算出影響面,並定期測試復查,加強關鍵路徑上的服務的優化和容錯,清理不該在關鍵路徑上的服務。
提供容錯Mock數據,Mock數據也應可以在注冊中心在運行時動態下發,當某服務不可用時,用Mock數據代替,可以減少故障的發生,比如某驗權服務,當驗權服務全部掛掉后,直接返回false表示沒有權限,並打印Error日志報警。
另外,前端的頁面也應采用Portal進行降級,當該Portal獲取不到數據時,直接隱藏,或替換為其它模塊展示,並提供功能開關,可人工干預是否展示,或限制多少流量可以展示。
(15) 服務調用鏈跟蹤 :當已有很多小服務,可能就需要組合多個小服務的大服務,為此,不得不增加一個中間層,暴露一個新服務,里面分別調其它小服務,這樣的新服務業務邏輯少,卻帶來很多開發工作量。
此時,需要一個服務編排引擎,內置簡單的流程引擎,只需用XML或DSL聲明如何聚合服務,注冊中心可以直接下發給消費者執行聚合邏輯,或者部署通用的編排服務器,所有請求有編排服務器轉發。
(16) 服務使用情況報告、服務權重動態調整 :並不是所有服務的訪問量都大,很多的服務都只有一丁點訪問量,卻需要部署兩台提供服務的機器,進行HA互備,如何減少浪費的機器。
此時可能需要讓服務容器支持在一台機器上部署多個應用,可以用多JVM隔離,也可以用ClassLoader隔離。
(17) 服務自動化部署:多個應用如果不是一個團隊開發的,部署在一台機器上,很有可以誤操作,停掉了別人的服務。
所以需要實現自動部署,所有的部署都無需人工干擾,最好是一鍵式部署。
(18) 服務自動化調度:機器總是的閑時和忙時,或者冗余機器防災,如何提高機器的利用率?
即然已經可以自動部署了,那根據監控數據,就可以實現資源調度,根據應用的壓力情況,自動添加機器並部署。
如果你的應用是國際化的,有中文站,美國站之類,因為時差,美國站的機器晚上閑的時候,可能正是中文站的白天忙時,可以通過資源調度,分時段自動調配和部署雙方應用。
四、傳統服務拆分
1、傳統服務分為MVC
- 視圖層:負責解析成頁面
- 控制層:負責業務邏輯關系
- 數據層:orm訪問數據庫
每個模塊一個包,每個包都有自己的MVC,項目迭代久了之后,包就會變得越來越大,業務、代碼交錯縱橫,管理混亂,大部分都共享一個庫。
2、常見微服務拆分
- 把相關性緊密的業務或模塊單獨拆分成一個服務(代碼、數據庫單獨管理)。
- 公共模塊拆分成一個服務,提供接口給其他模塊調用。
- 使用dubbo分布式服務框架進行服務間的遠程調用(消費者-生產者)
常見電商系統拆分:
3、微服務拆分優點
-
解耦,這樣每個子系統或者服務器都可以由專門的團隊去負責,解決模塊之間的耦合以及擴展性問題,即各個子系統都是相對獨立的。
-
容錯,每個子系統都是單獨部署在服務器上的,如果集中部署在一個服務器上,當這台服務器宕機了就會導致整個系統都不能使用。
- 集群,同一個服務可以部署部署多台機器。
- 性能更好,拆分成多個服務之后,相當於均分了單個系統應用的壓力,整個為服務系統能承受更大的壓力。
- 微服務架構與語言工具無關,自由選擇合適的語言和工具,高效的完成業務目標即可
4、微服務拆分缺點
-
依賴服務變更很難跟蹤,其他團隊的服務接口文檔過期怎么辦?依賴的服務沒有准備好,如何驗證開發的功能。
-
部分模塊重復構建,跨團隊、跨系統、跨語言會有很多的重復建設。
-
微服務放大了分布式架構的系列問題,如分布式事務怎么處理?依賴服務不穩定怎么辦?
-
運維復雜度陡增,如:部署物數量多、監控進程多導致整體運維復雜度提升。
上面這些問題我們應該都遇到過,並且也會有一些解決方案,比如提供文檔管理、服務治理、服務模擬的工具和框架; 實現統一認證、統一配置、統一日志框架、分布式匯總分析; 采用全局事務方案、采用異步模擬同步;搭建持續集成平台、統一監控平台等等。
5、四個拆分原則
1.AKF拆分原則
AKF擴展立方體(參考《The Art of Scalability》),是一個叫AKF的公司的技術專家抽象總結的應用擴展的三個維度。理論上按照這三個擴展模式,可以將一個單體系統,進行無限擴展。
X 軸 :指的是水平復制,很好理解,就是講單體系統多運行幾個實例,做個集群加負載均衡的模式。
Z 軸 :是基於類似的數據分區,比如一個互聯網打車應用突然用戶量激增,集群模式撐不住了,那就按照用戶請求的地區進行數據分區,北京、上海、四川等多建幾個集群。
Y 軸 :就是我們所說的微服務的拆分模式,就是基於不同的業務拆分。
場景說明:比如打車應用,一個集群撐不住時,分了多個集群,后來用戶激增還是不夠用,經過分析發現是乘客和車主訪問量很大,就將打車應用拆成了三個乘客服務、車主服務、支付服務。三個服務的業務特點各不相同,獨立維護,各自都可以再次按需擴展。
2.前后端分離
前后端分離原則,簡單來講就是前端和后端的代碼分離也就是技術上做分離,我們推薦的模式是最好直接采用物理分離的方式部署,進一步促使進行更徹底的分離。不要繼續以前的服務端模板技術,比如JSP ,把Java JS HTML CSS 都堆到一個頁面里,稍復雜的頁面就無法維護。這種分離模式的方式有幾個好處:
-
前后端技術分離,可以由各自的專家來對各自的領域進行優化,這樣前端的用戶體驗優化效果會更好。
-
分離模式下,前后端交互界面更加清晰,就剩下了接口和模型,后端的接口簡潔明了,更容易維護。
-
前端多渠道集成場景更容易實現,后端服務無需變更,采用統一的數據和模型,可以支撐前端的web UI\ 移動App等訪問。
3.無狀態服務
對於無狀態服務,首先說一下什么是狀態:如果一個數據需要被多個服務共享,才能完成一筆交易,那么這個數據被稱為狀態。進而依賴這個“狀態”數據的服務被稱為有狀態服務,反之稱為無狀態服務。
那么這個無狀態服務原則並不是說在微服務架構里就不允許存在狀態,表達的真實意思是要把有狀態的業務服務改變為無狀態的計算類服務,那么狀態數據也就相應的遷移到對應的“有狀態數據服務”中。
場景說明:例如我們以前在本地內存中建立的數據緩存、Session緩存,到現在的微服務架構中就應該把這些數據遷移到分布式緩存中存儲,讓業務服務變成一個無狀態的計算節點。遷移后,就可以做到按需動態伸縮,微服務應用在運行時動態增刪節點,就不再需要考慮緩存數據如何同步的問題。
4.Restful通信風格
作為一個原則來講本來應該是個“無狀態通信原則”,在這里我們直接推薦一個實踐優選的Restful 通信風格 ,因為他有很多好處:
-
無狀態協議HTTP,具備先天優勢,擴展能力很強。例如需要安全加密是,有現成的成熟方案HTTPS可用。
-
JSON 報文序列化,輕量簡單,人與機器均可讀,學習成本低,搜索引擎友好。
-
語言無關,各大熱門語言都提供成熟的Restful API框架,相對其他的一些RPC框架生態更完善。
當然在有些特殊業務場景下,也需要采用其他的RPC框架,如thrift、avro-rpc、grpc。但絕大多數情況下Restful就足夠用了。
五、dubbo 介紹(角色、調用關系、特性)
1、節點角色說明
節點 | 角色說明 |
---|---|
Provider |
暴露服務的服務提供方 |
Consumer |
調用遠程服務的服務消費方 |
Registry |
服務注冊與發現的注冊中心 |
Monitor |
統計服務的調用次數和調用時間的監控中心 |
Container |
服務運行容器 |
2、調用關系說明
- 服務容器負責啟動,加載,運行服務提供者。
- 服務提供者在啟動時,向注冊中心注冊自己提供的服務。
- 服務消費者在啟動時,向注冊中心訂閱自己所需的服務。
- 注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基於長連接推送變更數據給消費者。
- 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一台提供者進行調用,如果調用失敗,再選另一台調用。
- 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鍾發送一次統計數據到監控中心。
3、dubbo特性
Dubbo 架構具有以下幾個特點,分別是連通性、健壯性、伸縮性、以及向未來架構的升級性。
1、連通性
- 注冊中心負責服務地址的注冊與查找,相當於目錄服務,服務提供者和消費者只在啟動時與注冊中心交互,注冊中心不轉發請求,壓力較小
- 監控中心負責統計各服務調用次數,調用時間等,統計先在內存匯總后每分鍾一次發送到監控中心服務器,並以報表展示
- 服務提供者向注冊中心注冊其提供的服務,並匯報調用時間到監控中心,此時間不包含網絡開銷
- 服務消費者向注冊中心獲取服務提供者地址列表,並根據負載算法直接調用提供者,同時匯報調用時間到監控中心,此時間包含網絡開銷
- 注冊中心,服務提供者,服務消費者三者之間均為長連接,監控中心除外
- 注冊中心通過長連接感知服務提供者的存在,服務提供者宕機,注冊中心將立即推送事件通知消費者
- 注冊中心和監控中心全部宕機,不影響已運行的提供者和消費者,消費者在本地緩存了提供者列表
- 注冊中心和監控中心都是可選的,服務消費者可以直連服務提供者
2、健壯性
- 監控中心宕掉不影響使用,只是丟失部分采樣數據
- 數據庫宕掉后,注冊中心仍能通過緩存提供服務列表查詢,但不能注冊新服務
- 注冊中心對等集群,任意一台宕掉后,將自動切換到另一台
- 注冊中心全部宕掉后,服務提供者和服務消費者仍能通過本地緩存通訊
- 服務提供者無狀態,任意一台宕掉后,不影響使用
- 服務提供者全部宕掉后,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復
3、伸縮性
- 注冊中心為對等集群,可動態增加機器部署實例,所有客戶端將自動發現新的注冊中心
- 服務提供者無狀態,可動態增加機器部署實例,注冊中心將推送新的服務提供者信息給消費者