11 | 服務發布和引用的實踐
服務發布和引用常見的三種方式:Restful API、XML配置以及IDL文件。今天我將以XML配置方式為例,給你講解服務發布和引用的具體實踐以及可能會遇到的問題。
XML配置方式的服務發布和引用流程
1. 服務提供者定義接口
服務提供者發布服務之前首先要定義接口,聲明接口名、傳遞參數以及返回值類型,然后把接口打包成JAR包發布出去。
比如下面這段代碼,聲明了接口UserLastStatusService,包含兩個方法getLastStatusId和getLastStatusIds,傳遞參數一個是long值、一個是long數組,返回值一個是long值、一個是map。
package com.weibo.api.common.status.service;public interface UserLastStatusService {* @param uids* @return*/public long getLastStatusId(long uid);/**** @param uids* @return*/public Map<Long, Long> getLastStatusIds(long[] uids);
}
2. 服務提供者發布接口
服務提供者發布的接口是通過在服務發布配置文件中定義接口來實現的。
下面我以一個具體的服務發布配置文件user-last-status.xml來給你講解,它定義了要發布的接口userLastStatusLocalService,對外暴露的協議是Motan協議,端口是8882。并且針對兩個方法getLastStatusId和getLastStatusIds,通過requestTimeout=“300”單獨定義了超時時間是300ms,通過retries=“0”單獨定義了調用失敗后重試次數為0,也就是不重試。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
"><motan:service ref="userLastStatusLocalService"requestTimeout="50" retries="2" interface="com.weibo.api.common.status.service.UserLastStatusService"basicService="serviceBasicConfig" export="motan:8882"><motan:method name="getLastStatusId" requestTimeout="300"retries="0" /><motan:method name="getLastStatusIds" requestTimeout="300"retries="0" />
</motan:service>
</beans>
然后服務發布者在進程啟動的時候,會加載配置文件user-last-status.xml,把接口對外暴露出去。
3. 服務消費者引用接口
服務消費者引用接口是通過在服務引用配置文件中定義要引用的接口,并把包含接口定義的JAR包引入到代碼依賴中。
下面我再以一個具體的服務引用配置文件user-last-status-client.xml來給你講解,它定義服務消費者引用了接口commonUserLastStatusService,接口通信協議是Motan。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
"><motan:protocol name="motan" default="true" loadbalance="${service.loadbalance.name}" />
<motan:basicReferer id="userLastStatusServiceClientBasicConfig"protocol="motan" /><!-- 導出接口 -->
<motan:referer id="commonUserLastStatusService" interface="com.weibo.api.common.status.service.UserLastStatusService"basicReferer="userLastStatusServiceClientBasicConfig" /></beans>
然后服務消費者在進程啟動時,會加載配置文件user-last-status-client.xml來完成服務引用。
上面所講的服務發布和引用流程看似比較簡單,但在實際使用過程中,還是有很多坑的,比如在實際項目中經常會遇到這個問題:一個服務包含了多個接口,可能有上行接口也可能有下行接口,每個接口都有超時控制以及是否重試等配置,如果有多個服務消費者引用這個服務,是不是每個服務消費者都必須在服務引用配置文件中定義?
服務發布和引用的那些坑
在一個服務被多個服務消費者引用的情況下,由于業務經驗的參差不齊,可能不同的服務消費者對服務的認知水平不一,比如某個服務可能調用超時了,最好可以重試來提供調用成功率。但可能有的服務消費者會忽視這一點,并沒有在服務引用配置文件中配置接口調用超時重試的次數,因此最好是可以在服務發布的配置文件中預定義好類似超時重試次數,即使服務消費者沒有在服務引用配置文件中定義,也能繼承服務提供者的定義。這就是下面要講的服務發布預定義配置。
1. 服務發布預定義配置
以下面的服務發布配置文件server.xml為例,它提供了一個服務contentSliceRPCService,并且明確了其中三個方法的調用超時時間為500ms以及超時重試次數為3。
<motan:service ref="contentSliceRPCService" interface="cn.sina.api.data.service.ContentSliceRPCService"basicService="serviceBasicConfig" export="motan:8882" ><motan:method name="saveContent" requestTimeout="500"retries="3" /><motan:method name="deleteContent" requestTimeout="500"retries="3" /><motan:method name="updateContent" requestTimeout="500"retries="3" />
</motan:service>
假設服務引用的配置文件client.xml的內容如下,那么服務消費者就會默認繼承服務發布配置文件中設置的方法調用的超時時間以及超時重試次數。
<motan:referer id="contentSliceRPCService" interface="cn.sina.api.data.service.ContentSliceRPCService" basicReferer="contentSliceClientBasicConfig" >
</motan:referer>
通過服務發布預定義配置可以解決多個服務消費者引用服務可能帶來的配置復雜的問題,這樣是不是最優的解決方案呢?
實際上我還遇到過另外一種極端情況,一個服務提供者發布的服務有上百個方法,并且每個方法都有各自的超時時間、重試次數等信息。服務消費者引用服務時,完全繼承了服務發布預定義的各項配置。這種情況下,服務提供者所發布服務的詳細配置信息都需要存儲在注冊中心中,這樣服務消費者才能在實際引用時從服務發布預定義配置中繼承各種配置。
這里就存在一種風險,當服務提供者發生節點變更,尤其是在網絡頻繁抖動的情況下,所有的服務消費者都會從注冊中心拉取最新的服務節點信息,就包括了服務發布配置中預定的各項接口信息,這個信息不加限制的話可能達到1M以上,如果同時有上百個服務消費者從注冊中心拉取服務節點信息,在注冊中心機器部署為百兆帶寬的情況下,很有可能會導致網絡帶寬打滿的情況發生。
面對這種情況,最好的辦法是把服務發布端的詳細服務配置信息轉移到服務引用端,這樣的話注冊中心中就不需要存儲服務提供者發布的詳細服務配置信息了。這就是下面要講的服務引用定義配置。
2. 服務引用定義配置
以下面的服務發布配置文件為例,它詳細定義了服務userInfoService的各個方法的配置信息,比如超時時間和重試次數等。
<motan:service ref="userInfoService" requestTimeout="50" retries="2" interface="cn.sina.api.user.service.UserInfoService" basicService="serviceBasicConfig">
<motan:method name="addUserInfo" requestTimeout="300" retries="0"/><motan:method name="updateUserPortrait" requestTimeout="300" retries="0"/><motan:method name="modifyUserInfo" requestTimeout="300" retries="0"/><motan:method name="addUserTags" requestTimeout="300" retries="0"/><motan:method name="delUserTags" requestTimeout="300" retries="0"/><motan:method name="processUserCacheByNewMyTriggerQ" requestTimeout="300" retries="0"/><motan:method name="modifyObjectUserInfo" requestTimeout="300" retries="0"/><motan:method name="addObjectUserInfo" requestTimeout="300" retries="0"/><motan:method name="updateObjectUserPortrait" requestTimeout="300" retries="0"/><motan:method name="updateObjectManager" requestTimeout="300" retries="0"/><motan:method name="add" requestTimeout="300" retries="0"/><motan:method name="deleteObjectManager" requestTimeout="300" retries="0"/><motan:method name="getUserAttr" requestTimeout="300" retries="1" /><motan:method name="getUserAttrList" requestTimeout="300" retries="1" /><motan:method name="getAllUserAttr" requestTimeout="300" retries="1" /><motan:method name="getUserAttr2" requestTimeout="300" retries="1" /></motan:service>
可以像下面一樣,把服務userInfoService的詳細配置信息轉移到服務引用配置文件中。
<motan:referer id="userInfoService" interface="cn.sina.api.user.service.UserInfoService" basicReferer="userClientBasicConfig"><motan:method name="addUserInfo" requestTimeout="300" retries="0"/><motan:method name="updateUserPortrait" requestTimeout="300" retries="0"/><motan:method name="modifyUserInfo" requestTimeout="300" retries="0"/><motan:method name="addUserTags" requestTimeout="300" retries="0"/><motan:method name="delUserTags" requestTimeout="300" retries="0"/><motan:method name="processUserCacheByNewMyTriggerQ" requestTimeout="300" retries="0"/><motan:method name="modifyObjectUserInfo" requestTimeout="300" retries="0"/><motan:method name="addObjectUserInfo" requestTimeout="300" retries="0"/><motan:method name="updateObjectUserPortrait" requestTimeout="300" retries="0"/><motan:method name="updateObjectManager" requestTimeout="300" retries="0"/><motan:method name="add" requestTimeout="300" retries="0"/><motan:method name="deleteObjectManager" requestTimeout="300" retries="0"/><motan:method name="getUserAttr" requestTimeout="300" retries="1" /><motan:method name="getUserAttrList" requestTimeout="300" retries="1" /><motan:method name="getAllUserAttr" requestTimeout="300" retries="1" /><motan:method name="getUserAttr2" requestTimeout="300" retries="1" />
</motan:referer>
這樣的話,服務發布配置文件可以簡化為下面這段代碼,是不是信息精簡了許多。
<motan:service ref="userInfoService" requestTimeout="50" retries="2" interface="cn.sina.api.user.service.UserInfoService" basicService="serviceBasicConfig">
</motan:service>
在進行類似的服務詳細信息配置,由服務發布配置文件遷移到服務引用配置文件的過程時,尤其要注意遷移步驟問題,這就是接下來我要給你講的服務配置升級問題。
3. 服務配置升級
實際項目中,我就經歷過一次服務配置升級的過程。由于引用服務的服務消費者眾多,并且涉及多個部門,升級步驟就顯得異常重要,通常可以按照下面步驟操作。
- 各個服務消費者在服務引用配置文件中添加服務詳細信息。
- 服務提供者升級兩臺服務器,在服務發布配置文件中刪除服務詳細信息,并觀察是否所有的服務消費者引用時都包含服務詳細信息。
- 如果都包含,說明所有服務消費者均完成升級,那么服務提供者就可以刪除服務發布配置中的服務詳細信息。
- 如果有不包含服務詳細信息的服務消費者,排查出相應的業務方進行升級,直至所有業務方完成升級。
總結
簡單來說就是服務提供者定義好接口,并且在服務發布配置文件中配置要發布的接口名,在進程啟動時加載服務發布配置文件就可以對外提供服務了。而服務消費者通過在服務引用配置文件中定義相同的接口名,并且在服務引用配置文件中配置要引用的接口名,在進程啟動時加載服務引用配置文件就可以引用服務了。
在業務具體實踐過程中可能會遇到引用服務的服務消費者眾多,對業務的敏感度參差不齊的問題,所以在服務發布的時候,最好預定義好接口的各種配置。在服務規模不大,業務比較簡單的時候,這樣做比較合適。但是對于復雜業務,雖然服務發布時預定義好接口的各種配置,但在引用的服務消費者眾多且同時訪問的時候,可能會引起網絡風暴。這種情況下,比較保險的方式是,把接口的各種配置放在服務引用配置文件里。
在進行服務配置升級過程時,要考慮好步驟,在所有服務消費者完成升級之前,服務提供者還不能把服務的詳細信息去掉,否則可能會導致沒有升級的服務消費者引用異常。
12 | 如何將注冊中心落地?
掌握了服務注冊和發現的原理之后,我們就需要考慮如何把注冊中心落地實現。結合前面所講的服務注冊與發現的流程,在落地注冊中心的過程中,我們需要解決一系列的問題,包括如何存儲服務信息、如何注冊節點、如何反注冊、如何查詢節點信息以及如何訂閱服務變更等。這些問題你都知道如何解決嗎?如果還沒答案,沒關系,下面我來給你一一講解。
注冊中心如何存儲服務信息
注冊中心既然是用來存儲服務信息的,那么服務信息都包含哪些內容呢?
根據我的實踐經驗,服務信息除了包含節點信息(IP和端口號)以外,還包含其他一些信息,比如請求失敗時重試的次數、請求結果是否壓縮等信息。因此服務信息通常用JSON字符串來存儲,包含多個字段,每個字段代表不同的含義。
除此之外,服務一般會分成多個不同的分組,每個分組的目的不同。一般來說有下面幾種分組方式。
- 核心與非核心,從業務的核心程度來分。
- 機房,從機房的維度來分。
- 線上環境與測試環境,從業務場景維度來區分。
所以注冊中心存儲的服務信息一般包含三部分內容:分組、服務名以及節點信息,節點信息又包括節點地址和節點其他信息。從注冊中心中獲取的信息結構大致如下圖所示。
具體存儲的時候,一般是按照“服務-分組-節點信息”三層結構來存儲,可以用下圖來描述。Service代表服務的具體分組,Cluster代表服務的接口名,節點信息用KV存儲。
搞清楚了注冊中心存儲服務信息的原理后,再來看下注冊中心具體是如何工作的,包括四個流程。
- 服務提供者注冊流程。
- 服務提供者反注冊流程。
- 服務消費者查詢流程。
- 服務消費者訂閱變更流程。
注冊中心是如何工作的
1. 如何注冊節點
知道了服務的節點信息如何存儲之后,服務注冊流程是怎么樣的呢?可以用下面這張流程圖來描述。
根據我的經驗,服務注冊流程主要有下面幾個步驟:
- 首先查看要注冊的節點是否在白名單內?如果不在就拋出異常,在的話繼續下一步。
- 其次要查看注冊的Cluster(服務的接口名)是否存在?如果不存在就拋出異常,存在的話繼續下一步。
- 然后要檢查Service(服務的分組)是否存在?如果不存在則拋出異常,存在的話繼續下一步。
- 最后將節點信息添加到對應的Service和Cluster下面的存儲中。
2. 如何反注冊
再來看下服務提供者節點反注冊的流程,可以用下面這張流程圖來描述。
根據我的經驗,節點反注冊流程主要包含下面幾個步驟:
- 查看Service(服務的分組)是否存在,不存在就拋出異常,存在就繼續下一步。
- 查看Cluster(服務的接口名)是否存在,不存在就拋出異常,存在就繼續下一步。
- 刪除存儲中Service和Cluster下對應的節點信息。
- 更新Cluster的sign值。
3. 如何查詢節點信息
關于服務消費者是如何從注冊中心查詢服務提供者的節點信息,可以用下面這張流程圖來描述。
服務消費者查詢節點信息主要分為下面幾個步驟:
- 首先從localcache(本機內存)中查找,如果沒有就繼續下一步。這里為什么服務消費者要把服務信息存在本機內存呢?主要是因為服務節點信息并不總是時刻變化的,并不需要每一次服務調用都要調用注冊中心獲取最新的節點信息,只需要在本機內存中保留最新的服務提供者的節點列表就可以。
- 接著從snapshot(本地快照)中查找,如果沒有就繼續下一步。這里為什么服務消費者要在本地磁盤存儲一份服務提供者的節點信息的快照呢?這是因為服務消費者同注冊中心之間的網絡不一定總是可靠的,服務消費者重啟時,本機內存中還不存在服務提供者的節點信息,如果此時調用注冊中心失敗,那么服務消費者就拿不到服務節點信息了,也就沒法調用了。本地快照就是為了防止這種情況的發生,即使服務消費者重啟后請求注冊中心失敗,依然可以讀取本地快照,獲取到服務節點信息。
4. 如何訂閱服務變更
最后看下,服務消費者如何訂閱服務提供者的變更信息呢?可以用下面這張流程圖來描述。
主要分為下面幾個步驟:
- 服務消費者從注冊中心獲取了服務的信息后,就訂閱了服務的變化,會在本地保留Cluster的sign值。
- 服務消費者每隔一段時間,調用getSign()函數,從注冊中心獲取服務端該Cluster的sign值,并與本地保留的sign值做對比,如果不一致,就從服務端拉取新的節點信息,并更新localcache和snapshot。
注冊與發現的幾個問題
1. 多注冊中心
理論上對于一個服務消費者來說,同一個注冊中心交互是最簡單的。但是不可避免的是,服務消費者可能訂閱了多個服務,多個服務可能是由多個業務部門提供的,而且每個業務部門都有自己的注冊中心,提供的服務只在自己的注冊中心里有記錄。這樣的話,就要求服務消費者要具備在啟動時,能夠從多個注冊中心訂閱服務的能力。
根據我的經驗,還有一種情況是,一個服務提供者提供了某個服務,可能作為靜態服務對外提供,有可能又作為動態服務對外提供,這兩個服務部署在不同的注冊中心,所以要求服務提供者在啟動的時候,要能夠同時向多個注冊中心注冊服務。
也就是說,對于服務消費者來說,要能夠同時從多個注冊中心訂閱服務;對于服務提供者來說,要能夠同時向多個注冊中心注冊服務。
2. 并行訂閱服務
通常一個服務消費者訂閱了不止一個服務,在我經歷的一個項目中,一個服務消費者訂閱了幾十個不同的服務,每個服務都有自己的方法列表以及節點列表。服務消費者在服務啟動時,會加載訂閱的服務配置,調用注冊中心的訂閱接口,獲取每個服務的節點列表并初始化連接。
最開始我們采用了串行訂閱的方式,每訂閱一個服務,服務消費者調用一次注冊中心的訂閱接口,獲取這個服務的節點列表并初始化連接,總共需要執行幾十次這樣的過程。在某些服務節點的初始化連接過程中,出現連接超時的情況,后續所有的服務節點的初始化連接都需要等待它完成,導致服務消費者啟動變慢,最后耗費了將近五分鐘時間來完成所有服務節點的初始化連接過程。
后來我們改成了并行訂閱的方式,每訂閱一個服務就單獨用一個線程來處理
,這樣的話即使遇到個別服務節點連接超時,其他服務節點的初始化連接也不受影響,最慢也就是這個服務節點的初始化連接耗費的時間,最終所有服務節點的初始化連接耗時控制在了30秒以內。
3. 批量反注冊服務
通常一個服務提供者節點提供不止一個服務,所以注冊和反注冊都需要多次調用注冊中心。在與注冊中心的多次交互中,可能由于網絡抖動、注冊中心集群異常等原因,導致個別調用失敗。對于注冊中心來說,偶發的注冊調用失敗對服務調用基本沒有影響,其結果頂多就是某一個服務少了一個可用的節點。但偶發的反注冊調用失敗會導致不可用的節點殘留在注冊中心中,變成“僵尸節點”,但服務消費者端還會把它當成“活節點”,繼續發起調用,最終導致調用失敗。
以前我們的業務中經常遇到這個問題,需要定時去清理注冊中心中的“僵尸節點”。后來我們通過優化反注冊邏輯,對于下線機器、節點銷毀的場景,通過調用注冊中心提供的批量反注冊接口,一次調用就可以把該節點上提供的所有服務同時反注冊掉,從而避免了“僵尸節點”的出現。
4. 服務變更信息增量更新
服務消費者端啟動時,除了會查詢訂閱服務的可用節點列表做初始化連接,還會訂閱服務的變更,每隔一段時間從注冊中心獲取最新的服務節點信息標記sign,并與本地保存的sign值作比對,如果不一樣,就會調用注冊中心獲取最新的服務節點信息。
一般情況下,按照這個過程是沒問題的,但是在網絡頻繁抖動時,服務提供者上報給注冊中心的心跳可能會一會兒失敗一會兒成功,這時候注冊中心就會頻繁更新服務的可用節點信息,導致服務消費者頻繁從注冊中心拉取最新的服務可用節點信息,嚴重時可能產生網絡風暴,導致注冊中心帶寬被打滿。
為了減少服務消費者從注冊中心中拉取的服務可用節點信息的數據量,這個時候可以通過增量更新的方式,注冊中心只返回變化的那部分節點信息,尤其在只有少數節點信息變更時,此舉可以大大減少服務消費者從注冊中心拉取的數據量,從而最大程度避免產生網絡風暴。
總結
今天我給你講解了在注冊中心實際使用過程中,服務注冊、服務反注冊、服務訂閱和服務變更的實現方式,并列舉了幾個我在服務注冊與發現的過程中遇到的典型問題。
而針對這些異常情況,我都給出了對應的解決方案,這些方案都是經過實際業務驗證的,對于大部分中小團隊在應用場景面臨的問題,應該足以應對。
13 | 開源服務注冊中心如何選型?
關于注冊中心,如果你的團隊有足夠的人才和技術儲備,可以選擇自己研發注冊中心。但對于大多數中小規模團隊來說,我的建議是最好使用業界開源的、應用比較成熟的注冊中心解決方案,把精力投入到業務架構的改造中,不要自己造輪子。
當下主流的服務注冊與發現的解決方案,主要有兩種:
- 應用內注冊與發現:注冊中心提供服務端和客戶端的SDK,業務應用通過引入注冊中心提供的SDK,通過SDK與注冊中心交互,來實現服務的注冊和發現。
- 應用外注冊與發現:業務應用本身不需要通過SDK與注冊中心打交道,而是通過其他方式與注冊中心交互,間接完成服務注冊與發現。
下面我會用兩個業界使用比較成熟的注冊中心開源實現,來講解下應用內和應用外兩種解決方案的不同之處。
兩種典型的注冊中心實現
1. 應用內
采用應用內注冊與發現的方式,最典型的案例要屬Netflix開源的Eureka,官方架構圖如下。
對著這張圖,我來介紹下Eureka的架構,它主要由三個重要的組件組成:
- Eureka Server:注冊中心的服務端,實現了服務信息注冊、存儲以及查詢等功能。
- 服務端的Eureka Client:集成在服務端的注冊中心SDK,服務提供者通過調用SDK,實現服務注冊、反注冊等功能。
- 客戶端的Eureka Client:集成在客戶端的注冊中心SDK,服務消費者通過調用SDK,實現服務訂閱、服務更新等功能。
2. 應用外
采用應用外方式實現服務注冊和發現,最典型的案例是開源注冊中心Consul,它的架構圖如下。
通過這張架構圖,可以看出來使用Consul實現應用外服務注冊和發現主要依靠三個重要的組件:
- Consul:注冊中心的服務端,實現服務注冊信息的存儲,并提供注冊和發現服務。
- Registrator:一個開源的第三方服務管理器項目,它通過監聽服務部署的Docker實例是否存活,來負責服務提供者的注冊和銷毀。
- Consul Template:定時從注冊中心服務端獲取最新的服務提供者節點列表并刷新LB配置(比如Nginx的upstream),這樣服務消費者就通過訪問Nginx就可以獲取最新的服務提供者信息。
對比小結一下,這兩種解決方案的不同之處在于應用場景,應用內的解決方案一般適用于服務提供者和服務消費者同屬于一個技術體系;應用外的解決方案一般適合服務提供者和服務消費者采用了不同技術體系的業務場景,比如服務提供者提供的是C++服務,而服務消費者是一個Java應用,這時候采用應用外的解決方案就不依賴于具體一個技術體系。同時,對于容器化后的云應用來說,一般不適合采用應用內SDK的解決方案,因為這樣會侵入業務,而應用外的解決方案正好能夠解決這個問題。
注冊中心選型要考慮的兩個問題
在選擇注冊中心解決方案的時候,除了要考慮是采用應用內注冊還是應用外注冊的方式以外,還有兩個最值得關注的問題,一個是高可用性,一個是數據一致性,下面我來給你詳細解釋下為什么。
1. 高可用性
注冊中心作為服務提供者和服務消費者之間溝通的紐帶,它的高可用性十分重要。試想,如果注冊中心不可用了,那么服務提供者就無法對外暴露自己的服務,而服務消費者也無法知道自己想要調用的服務的具體地址,后果將不堪設想。
實現高可用性的方法主要有兩種:
- 集群部署,顧名思義就是通過部署多個實例組成集群來保證高可用性,這樣的話即使有部分機器宕機,將訪問遷移到正常的機器上就可以保證服務的正常訪問。
- 多IDC部署,就是部署在不止一個機房,這樣能保證即使一個機房因為斷電或者光纜被挖斷等不可抗力因素不可用時,仍然可以通過把請求遷移到其他機房來保證服務的正常訪問。
我們以Consul為例,來看看它是如何通過這兩種方法來保證注冊中心的高可用性。
從下面的官方架構圖中你可以看到,一方面,在每個數據中心(DATACENTER)內都有多個注冊中心Server節點可供訪問;另一方面還可以部署在多個數據中心來保證多機房高可用性。
2. 數據一致性
為了保證注冊中心的高可用性,注冊中心的部署往往都采用集群部署,并且還通常部署在不止一個數據中心,這樣的話就會引出另一個問題,多個數據中心之間如何保證數據一致?如何確保訪問數據中心中任何一臺機器都能得到正確的數據?
這里就涉及分布式系統中著名的CAP理論,即同時滿足一致性、可用性、分區容錯性這三者是不可能的,其中C(Consistency)代表一致性,A(Availability)代表可用性,P(Partition Tolerance)代表分區容錯性。
而注冊中心一般采用分布式集群部署,也面臨著CAP的問題,根據CAP不能同時滿足,所以不同的注冊中心解決方案選擇的方向也就不同,大致可分為兩種。
- CP型注冊中心,犧牲可用性來保證數據強一致性,最典型的例子就是ZooKeeper,etcd,Consul了。
Zookeeper集群內只有一個Leader,而且在Leader無法使用的時候通過Paxos算法選舉出一個新的Leader。這個Leader的目的就是保證寫信息的時候只向這個Leader寫入,Leader會同步信息到Followers,這個過程就可以保證數據的強一致性。但如果多個ZooKeeper之間網絡出現問題,造成出現多個Leader,發生腦裂的話,注冊中心就不可用了。而etcd和Consul集群內都是通過raft協議來保證強一致性,如果出現 - AP型注冊中心,犧牲一致性來保證可用性,最典型的例子就是Eureka了。對比下Zookeeper,Eureka不用選舉一個Leader,每個Eureka服務器單獨保存服務注冊地址,因此有可能出現數據信息不一致的情況。但是當網絡出現問題的時候,每臺服務器都可以完成獨立的服務。
而對于注冊中心來說,最主要的功能是服務的注冊和發現,在網絡出現問題的時候,可用性的需求要遠遠高于數據一致性。即使因為數據不一致,注冊中心內引入了不可用的服務節點,也可以通過其他措施來避免,比如客戶端的快速失敗機制等,只要實現最終一致性,對于注冊中心來說就足夠了。因此,選擇AP型注冊中心,一般更加合適。
總結
總的來說,在選擇開源注冊中心解決方案的時候,要看業務的具體場景。
- 如果你的業務體系都采用Java語言的話,Netflix開源的Eureka是一個不錯的選擇,并且它作為服務注冊與發現解決方案,能夠最大程度的保證可用性,即使出現了網絡問題導致不同節點間數據不一致,你仍然能夠訪問Eureka獲取數據。
- 如果你的業務體系語言比較復雜,Eureka也提供了Sidecar的解決方案;也可以考慮使用Consul,它支持了多種語言接入,包括Go、Python、PHP、Scala、Java,Erlang、Ruby、Node.js、.NET、Perl等。
- 如果你的業務已經是云原生的應用,可以考慮使用Consul,搭配Registrator和Consul Template來實現應用外的服務注冊與發現。
14 | 開源RPC框架如何選型?
簡單回顧一下一個完整的RPC框架主要有三部分組成:通信框架、通信協議、序列化和反序列化格式。根據我的經驗,想要開發一個完整的RPC框架,并且應用到線上生產環境,至少需要投入三個人力半年以上的時間。這對于大部分中小團隊來說,人力成本和時間成本都是不可接受的,所以我建議還是選擇開源的RPC框架比較合適。
那么業界應用比較廣泛的開源RPC框架有哪些呢?
簡單劃分的話,主要分為兩類:一類是跟某種特定語言平臺綁定的,另一類是與語言無關即跨語言平臺的。
跟語言平臺綁定的開源RPC框架主要有下面幾種。
- Dubbo:國內最早開源的RPC框架,由阿里巴巴公司開發并于2011年末對外開源,僅支持Java語言。
- Motan:微博內部使用的RPC框架,于2016年對外開源,僅支持Java語言。
- Tars:騰訊內部使用的RPC框架,于2017年對外開源,僅支持C++語言。
- Spring Cloud:國外Pivotal公司2014年對外開源的RPC框架,僅支持Java語言,最近幾年生態發展得比較好,是比較火的RPC框架。
而跨語言平臺的開源RPC框架主要有以下幾種。
- gRPC:Google于2015年對外開源的跨語言RPC框架,支持常用的C++、Java、Python、Go、Ruby、PHP、Android Java、Objective-C等多種語言。
- Thrift:最初是由Facebook開發的內部系統跨語言的RPC框架,2007年貢獻給了Apache基金,成為Apache開源項目之一,支持常用的C++、Java、PHP、Python、Ruby、Erlang等多種語言。
如果你的業務場景僅僅局限于一種語言的話,可以選擇跟語言綁定的RPC框架中的一種;如果涉及多個語言平臺之間的相互調用,就應該選擇跨語言平臺的RPC框架。
針對每一種RPC框架,它們具體有何區別?該如何選擇呢?接下來,我就從每個框架的實現角度來具體給你講解。當你知道了他們的具體實現,也就能知道他們的優缺點以及適用場景了。
限定語言平臺的開源RPC框架
1. Dubbo
先來聊聊Dubbo,Dubbo可以說是國內開源最早的RPC框架了,目前只支持Java語言,它的架構可以用下面這張圖展示。
Dubbo的架構主要包含四個角色,其中Consumer是服務消費者,Provider是服務提供者,Registry是注冊中心,Monitor是監控系統。
具體的交互流是Consumer一端通過注冊中心獲取到Provider節點后,通過Dubbo的客戶端SDK與Provider建立連接,并發起調用。Provider一端通過Dubbo的服務端SDK接收到Consumer的請求,處理后再把結果返回給Consumer。
可以看到服務消費者和服務提供者都需要引入Dubbo的SDK才來完成RPC調用,因為Dubbo本身是采用Java語言實現的,所以要求服務消費者和服務提供者也都必須采用Java語言實現才可以應用。
我們看下Dubbo的調用框架是如何實現的。
- 通信框架方面,Dubbo默認采用了Netty作為通信框架。
- 通信協議方面,Dubbo除了支持私有的Dubbo協議外,還支持RMI協議、Hession協議、HTTP協議、Thrift協議等。
- 序列化格式方面,Dubbo支持多種序列化格式,比如Dubbo、Hession、JSON、Kryo、FST等。
2. Motan
Motan是國內另外一個比較有名的開源的RPC框架,同樣也只支持Java語言實現,它的架構可以用下面這張圖描述。
Motan與Dubbo的架構類似,都需要在Client端(服務消費者)和Server端(服務提供者)引入SDK,其中Motan框架主要包含下面幾個功能模塊。
- register:用來和注冊中心交互,包括注冊服務、訂閱服務、服務變更通知、服務心跳發送等功能。Server端會在系統初始化時通過register模塊注冊服務,Client端會在系統初始化時通過register模塊訂閱到具體提供服務的Server列表,當Server列表發生變更時也由register模塊通知Client。
- protocol:用來進行RPC服務的描述和RPC服務的配置管理,這一層還可以添加不同功能的filter用來完成統計、并發限制等功能。
- serialize:將RPC請求中的參數、結果等對象進行序列化與反序列化,即進行對象與字節流的互相轉換,默認使用對Java更友好的Hessian 2進行序列化。
- transport:用來進行遠程通信,默認使用Netty NIO的TCP長鏈接方式。
- cluster:Client端使用的模塊,cluster是一組可用的Server在邏輯上的封裝,包含若干可以提供RPC服務的Server,實際請求時會根據不同的高可用與負載均衡策略選擇一個可用的Server發起遠程調用。
3. Tars
Tars是騰訊根據內部多年使用微服務架構的實踐,總結而成的開源項目,僅支持C++語言,它的架構圖如下。
Tars的架構交互主要包括以下幾個流程:
- 服務發布流程:在web系統上傳server的發布包到patch,上傳成功后,在web上提交發布server請求,由registry服務傳達到node,然后node拉取server的發布包到本地,拉起server服務。
- 管理命令流程:web系統上的可以提交管理server服務命令請求,由registry服務傳達到node服務,然后由node向server發送管理命令。
- 心跳上報流程:server服務運行后,會定期上報心跳到node,node然后把服務心跳信息上報到registry服務,由registry進行統一管理。
- 信息上報流程:server服務運行后,會定期上報統計信息到stat,打印遠程日志到log,定期上報屬性信息到prop、上報異常信息到notify、從config拉取服務配置信息。
- client訪問server流程:client可以通過server的對象名Obj間接訪問server,client會從registry上拉取server的路由信息(如IP、Port信息),然后根據具體的業務特性(同步或者異步,TCP或者UDP方式)訪問server(當然client也可以通過IP/Port直接訪問server)。
4. Spring Cloud
Spring Cloud是為了解決微服務架構中服務治理而提供的一系列功能的開發框架,它是完全基于Spring Boot進行開發的,Spring Cloud利用Spring Boot特性整合了開源行業中優秀的組件,整體對外提供了一套在微服務架構中服務治理的解決方案。因為Spring Boot是用Java語言編寫的,所以目前Spring Cloud也只支持Java語言平臺,它的架構圖可以用下面這張圖來描述。
由此可見,Spring Cloud微服務架構是由多個組件一起組成的,各個組件的交互流程如下。
- 請求統一通過API網關Zuul來訪問內部服務,先經過Token進行安全認證。
- 通過安全認證后,網關Zuul從注冊中心Eureka獲取可用服務節點列表。
- 從可用服務節點中選取一個可用節點,然后把請求分發到這個節點。
- 整個請求過程中,Hystrix組件負責處理服務超時熔斷,Turbine組件負責監控服務間的調用和熔斷相關指標,Sleuth組件負責調用鏈監控,ELK負責日志分析。
5. 對比選型
介紹完這4種限定語言的開源RPC框架后,我們該如何選擇呢?
很顯然,如果你的語言平臺是C++,那么只能選擇Tars;而如果是Java的話,可以選擇Dubbo、Motan或者Spring Cloud。這時你又要問了,它們三個又該如何抉擇呢?
仔細分析,可以看出Spring Cloud不僅提供了基本的RPC框架功能,還提供了服務注冊組件、配置中心組件、負載均衡組件、斷路器組件、分布式消息追蹤組件等一系列組件,也難怪被技術圈的人稱之為“Spring Cloud全家桶”。如果你不想自己實現以上這些功能,那么Spring Cloud基本可以滿足你的全部需求。而Dubbo、Motan基本上只提供了最基礎的RPC框架的功能,其他微服務組件都需要自己去實現。
不過由于Spring Cloud的RPC通信采用了HTTP協議,相比Dubbo和Motan所采用的私有協議來說,在高并發的通信場景下,性能相對要差一些,所以對性能有苛刻要求的情況下,可以考慮Dubbo和Motan。
跨語言平臺的開源RPC框架
1. gRPC
先來看下gRPC,它的原理是通過IDL(Interface Definition Language)文件定義服務接口的參數和返回值類型,然后通過代碼生成程序生成服務端和客戶端的具體實現代碼,這樣在gRPC里,客戶端應用可以像調用本地對象一樣調用另一臺服務器上對應的方法。
它的主要特征包括三個方面:
- 通信協議采用了HTTP/2,因為HTTP/2提供了連接復用、雙向流、服務器推送、請求優先級、首部壓縮等機制,所以在通信過程中可以節省帶寬、降低TCP連接次數、節省CPU,尤其對于移動端應用來說,可以幫助延長電池壽命。
- IDL使用了ProtoBuf,ProtoBuf是由Google開發的一種數據序列化協議,它的壓縮和傳輸效率極高,語法也簡單,所以被廣泛應用在數據存儲和通信協議上。
- 多語言支持,能夠基于多種語言自動生成對應語言的客戶端和服務端的代碼。
2. Thrift
再來看下Thrift,Thrift是一種輕量級的跨語言RPC通信方案,支持多達25種編程語言。為了支持多種語言,跟gRPC一樣,Thrift也有一套自己的接口定義語言IDL,可以通過代碼生成器,生成各種編程語言的Client端和Server端的SDK代碼,這樣就保證了不同語言之間可以相互通信。它的架構圖可以用下圖來描述。
從這張圖上可以看出Thrift RPC框架的特性。
- 支持多種序列化格式:如Binary、Compact、JSON、Multiplexed等。
- 支持多種通信方式:如Socket、Framed、File、Memory、zlib等。
- 服務端支持多種處理方式:如Simple 、Thread Pool、Non-Blocking等。
3. 對比選型
那么涉及跨語言的服務調用場景,到底該選擇gRPC還是Thrift呢?
從成熟度上來講,Thrift因為誕生的時間要早于gRPC,所以使用的范圍要高于gRPC,在HBase、Hadoop、Scribe、Cassandra等許多開源組件中都得到了廣泛地應用。而且Thrift支持多達25種語言,這要比gRPC支持的語言更多,所以如果遇到gRPC不支持的語言場景下,選擇Thrift更合適。
但gRPC作為后起之秀,因為采用了HTTP/2作為通信協議、ProtoBuf作為數據序列化格式,在移動端設備的應用以及對傳輸帶寬比較敏感的場景下具有很大的優勢,而且開發文檔豐富,根據ProtoBuf文件生成的代碼要比Thrift更簡潔一些,從使用難易程度上更占優勢,所以如果使用的語言平臺gRPC支持的話,建議還是采用gRPC比較好。
總結
以上就是我對幾種使用最廣泛的開源RPC框架的選型建議,也是基于它們目前現狀所作出的判斷,從長遠來看,支持多語言是RPC框架未來的發展趨勢。正是基于此判斷,各個RPC框架都提供了Sidecar組件來支持多語言平臺之間的RPC調用。
- Dubbo在去年年底又重啟了維護,并且宣稱要引入Sidecar組件來構建Dubbo Mesh提供多語言支持。
- Motan也在去年對外開源了其內部的Sidecar組件:Motan-go,目前支持PHP、Java語言之間的相互調用。
- Spring Cloud也提供了Sidecar組件spring-cloud-netflix-sideca,可以讓其他語言也可以使用Spring Cloud的組件。
所以未來語言不會成為使用上面這幾種RPC框架的約束,而gRPC和Thrift雖然支持跨語言的RPC調用,但是因為它們只提供了最基本的RPC框架功能,缺乏一系列配套的服務化組件和服務治理功能的支撐,所以使用它們作為跨語言調用的RPC框架,就需要自己考慮注冊中心、熔斷、限流、監控、分布式追蹤等功能的實現,不過好在大多數功能都有開源實現,可以直接采用。
15 | 如何搭建一個可靠的監控系統?
一個監控系統的組成主要涉及四個環節:數據收集、數據傳輸、數據處理和數據展示。不同的監控系統實現方案,在這四個環節所使用的技術方案不同,適合的業務場景也不一樣。
目前,比較流行的開源監控系統實現方案主要有兩種:以ELK為代表的集中式日志解決方案,以及Graphite、TICK和Prometheus等為代表的時序數據庫解決方案。接下來我就以這幾個常見的監控系統實現方案,談談它們的實現原理,分別適用于什么場景,以及具體該如何做技術選型。
ELK
ELK是Elasticsearch、Logstash、Kibana三個開源軟件產品首字母的縮寫,它們三個通常配合使用,所以被稱為ELK Stack,它的架構可以用下面的圖片來描述。
這三個軟件的功能也各不相同。
- Logstash負責數據收集和傳輸,它支持動態地從各種數據源收集數據,并對數據進行過濾、分析、格式化等,然后存儲到指定的位置。
- Elasticsearch負責數據處理,它是一個開源分布式搜索和分析引擎,具有可伸縮、高可靠和易管理等特點,基于Apache Lucene構建,能對大容量的數據進行接近實時的存儲、搜索和分析操作,通常被用作基礎搜索引擎。
- Kibana負責數據展示,也是一個開源和免費的工具,通常和Elasticsearch搭配使用,對其中的數據進行搜索、分析并且以圖表的方式展示。
這種架構因為需要在各個服務器上部署Logstash來從不同的數據源收集數據,所以比較消耗CPU和內存資源,容易造成服務器性能下降,因此后來又在Elasticsearch、Logstash、Kibana之外引入了Beats作為數據收集器。相比于Logstash,Beats所占系統的CPU和內存幾乎可以忽略不計,可以安裝在每臺服務器上做輕量型代理,從成百上千或成千上萬臺機器向Logstash或者直接向Elasticsearch發送數據。
其中,Beats支持多種數據源,主要包括:
- Packetbeat,用來收集網絡流量數據。
- Topbeat,用來收集系統、進程的CPU和內存使用情況等數據。
- Filebeat,用來收集文件數據。
- Winlogbeat,用來收集Windows事件日志收據。
Beats將收集到的數據發送到Logstash,經過Logstash解析、過濾后,再將數據發送到Elasticsearch,最后由Kibana展示,架構就變成下面這張圖里描述的了。
Graphite
Graphite的組成主要包括三部分:Carbon、Whisper、Graphite-Web,它的架構可以用下圖來描述。
- Carbon:主要作用是接收被監控節點的連接,收集各個指標的數據,將這些數據寫入carbon-cache并最終持久化到Whisper存儲文件中去。
- Whisper:一個簡單的時序數據庫,主要作用是存儲時間序列數據,可以按照不同的時間粒度來存儲數據,比如1分鐘1個點、5分鐘1個點、15分鐘1個點三個精度來存儲監控數據。
- Graphite-Web:一個Web App,其主要功能繪制報表與展示,即數據展示。為了保證Graphite-Web能及時繪制出圖形,Carbon在將數據寫入Whisper存儲的同時,會在carbon-cache中同時寫入一份數據,Graphite-Web會先查詢carbon-cache,如果沒有再查詢Whisper存儲。
也就是說Carbon負責數據處理,Whisper負責數據存儲,Graphite-Web負責數據展示,可見Graphite自身并不包含數據采集組件,但可以接入StatsD等開源數據采集組件來采集數據,再傳送給Carbon。
其中Carbon對寫入的數據格式有一定的要求,比如:
servers.www01.cpuUsage 42 1286269200
products.snake-oil.salesPerMinute 123 1286269200
[one minute passes]
servers.www01.cpuUsageUser 44 1286269260
products.snake-oil.salesPerMinute 119 1286269260
其中“servers.www01.cpuUsage 42 1286269200”是“key” + 空格分隔符 + “value + 時間戳”的數據格式,“servers.www01.cpuUsage”是以“.”分割的key,代表具體的路徑信息,“42”是具體的值,“1286269200”是當前的Unix時間戳。
Graphite-Web對外提供了HTTP API可以查詢某個key的數據以繪圖展示,查詢方式如下。
http://graphite.example.com/render?target=servers.www01.cpuUsage&
width=500&height=300&from=-24h
這個HTTP請求意思是查詢key“servers.www01.cpuUsage”在過去24小時的數據,并且要求返回500*300大小的數據圖。
除此之外,Graphite-Web還支持豐富的函數,比如:
target=sumSeries(products.*.salesPerMinute)
代表了查詢匹配規則“products.*.salesPerMinute”的所有key的數據之和。
TICK
TICK是Telegraf、InfluxDB、Chronograf、Kapacitor四個軟件首字母的縮寫,是由InfluxData開發的一套開源監控工具棧,因此也叫作TICK Stack,它的架構可以看用下面這張圖來描述。
從這張圖可以看出,其中Telegraf負責數據收集,InfluxDB負責數據存儲,Chronograf負責數據展示,Kapacitor負責數據告警。
這里面,InfluxDB對寫入的數據格式要求如下。
<measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [unix-nano-timestamp]
下面我用一個具體示例來說明它的格式。
cpu,host=serverA,region=us_west value=0.64 1434067467100293230
其中,“cpu,host=serverA,region=us_west value=0.64 1434067467100293230”代表了host為serverA、region為us_west的服務器CPU的值是0.64,時間戳是1434067467100293230,時間精確到nano。
Prometheus
還有一種比較有名的時間序數據庫解決方案Prometheus,它是一套開源的系統監控報警框架,受Google的集群監控系統Borgmon啟發,由工作在SoundCloud的Google前員工在2012年創建,后來作為社區開源項目進行開發,并于2015年正式發布,2016年正式加入CNCF(Cloud Native Computing Foundation),成為受歡迎程度僅次于Kubernetes的項目,它的架構可以用下圖來描述。
從這張圖可以看出,Prometheus主要包含下面幾個組件:
- Prometheus Server:用于拉取metrics信息并將數據存儲在時間序列數據庫。
- Jobs/exporters:用于暴露已有的第三方服務的metrics給Prometheus Server,比如StatsD、Graphite等,負責數據收集。
- Pushgateway:主要用于短期jobs,由于這類jobs存在時間短,可能在Prometheus Server來拉取metrics信息之前就消失了,所以這類的jobs可以直接向Prometheus Server推送它們的metrics信息。
- Alertmanager:用于數據報警。
- Prometheus web UI:負責數據展示。
它的工作流程大致是:
- Prometheus Server定期從配置好的jobs或者exporters中拉取metrics信息,或者接收來自Pushgateway發過來的metrics信息。
- Prometheus Server把收集到的metrics信息存儲到時間序列數據庫中,并運行已經定義好的alert.rules,向Alertmanager推送警報。
- Alertmanager根據配置文件,對接收的警報進行處理,發出告警。
- 通過Prometheus web UI進行可視化展示。
Prometheus存儲數據也是用的時間序列數據庫,格式如下。
<metric name>{<label name>=<label value>, …}
比如下面這段代碼代表了位于集群cluster 1上,節點IP為1.1.1.1,端口為80,訪問路徑為“/a”的http請求的總數為100。
http_requests_total{instance="1.1.1.1:80",job="cluster1",location="/a"} 100
講到這里,四種監控系統的解決方案都已經介紹完了,接下來我們對比一下這四種方案,看看如何選型。
選型對比
我們從監控系統的四個環節來分別對比。
1. 數據收集
ELK是通過在每臺服務器上部署Beats代理來采集數據;Graphite本身沒有收據采集組件,需要配合使用開源收據采集組件,比如StatsD;TICK使用了Telegraf作為數據采集組件;Prometheus通過jobs/exporters組件來獲取StatsD等采集過來的metrics信息。
2. 數據傳輸
ELK是Beats采集的數據傳輸給Logstash,經過Logstash清洗后再傳輸給Elasticsearch;Graphite是通過第三方采集組件采集的數據,傳輸給Carbon;TICK是Telegraf采集的數據,傳輸給InfluxDB;而Prometheus是Prometheus Server隔一段時間定期去從jobs/exporters拉取數據。可見前三種都是采用“推數據”的方式,而Prometheus是采取拉數據的方式,因此Prometheus的解決方案對服務端的侵入最小,不需要在服務端部署數據采集代理。
3. 數據處理
ELK可以對日志的任意字段索引,適合多維度的數據查詢,在存儲時間序列數據方面與時間序列數據庫相比會有額外的性能和存儲開銷。除此之外,時間序列數據庫的幾種解決方案都支持多種功能的數據查詢處理,功能也更強大。
- Graphite通過Graphite-Web支持正則表達式匹配、sumSeries求和、alias給監控項重新命名等函數功能,同時還支持這些功能的組合,比如下面這個表達式的意思是,要查詢所有匹配路徑“stats.open.profile.*.API._comments_flow”的監控項之和,并且把監控項重命名為Total QPS。
alias(sumSeries(stats.openapi.profile.*.API._comments_flow.total_count,“Total QPS”)
- InfluxDB通過類似SQL語言的InfluxQL,能對監控數據進行復雜操作,比如查詢一分鐘CPU的使用率,用InfluxDB實現的示例是:
SELECT 100 - usage_idel FROM “autogen”.“cpu” WHERE time > now() - 1m and “cpu”=‘cpu0’
- Prometheus通過私有的PromQL查詢語言,如果要和上面InfluxDB實現同樣的功能,PromQL語句如下,看起來更加簡潔。
100 - (node_cpu{job=“node”,mode=“idle”}[1m])
4. 數據展示
Graphite、TICK和Prometheus自帶的展示功能都比較弱,界面也不好看,不過好在它們都支持Grafana來做數據展示。Grafana是一個開源的儀表盤工具,它支持多種數據源比如Graphite、InfluxDB、Prometheus以及Elasticsearch等。ELK采用了Kibana做數據展示,Kibana包含的數據展示功能比較強大,但只支持Elasticsearch,而且界面展示UI效果不如Grafana美觀。
總結
以上幾種監控系統實現方式,所采用的技術均為開源的,其中:
- ELK的技術棧比較成熟,應用范圍也比較廣,除了可用作監控系統外,還可以用作日志查詢和分析。
- Graphite是基于時間序列數據庫存儲的監控系統,并且提供了功能強大的各種聚合函數比如sum、average、top5等可用于監控分析,而且對外提供了API也可以接入其他圖形化監控系統如Grafana。
- TICK的核心在于其時間序列數據庫InfluxDB的存儲功能強大,且支持類似SQL語言的復雜數據處理操作。
- Prometheus的獨特之處在于它采用了拉數據的方式,對業務影響較小,同時也采用了時間序列數據庫存儲,而且支持獨有的PromQL查詢語言,功能強大而且簡潔。
從對實時性要求角度考慮,時間序列數據庫的實時性要好于ELK,通常可以做到10s級別內的延遲,如果對實時性敏感的話,建議選擇時間序列數據庫解決方案。
從使用的靈活性角度考慮,幾種時間序列數據庫的監控處理功能都要比ELK更加豐富,使用更靈活也更現代化。
所以如果要搭建一套新的監控系統,我建議可以考慮采用Graphite、TICK或者Prometheus其中之一。不過Graphite還需要搭配數據采集系統比如StatsD或者Collectd使用,而且界面展示建議使用Grafana接入Graphite的數據源,它的效果要比Graphite Web本身提供的界面美觀很多。TICK提供了完整的監控系統框架,包括從數據采集、數據傳輸、數據處理再到數據展示,不過在數據展示方面同樣也建議用Grafana替換掉TICK默認的數據展示組件Chronograf,這樣展示效果更好。Prometheus因為采用拉數據的方式,所以對業務的侵入性最小,比較適合Docker封裝好的云原生應用,比如Kubernetes默認就采用了Prometheus作為監控系統。
16 | 如何搭建一套適合你的服務追蹤系統?
服務追蹤系統的實現,主要包括三個部分。
- 埋點數據收集,負責在服務端進行埋點,來收集服務調用的上下文數據。
- 實時數據處理,負責對收集到的鏈路信息,按照traceId和spanId進行串聯和存儲。
- 數據鏈路展示,把處理后的服務調用數據,按照調用鏈的形式展示出來。
如果要自己從0開始實現一個服務追蹤系統,針對以上三個部分你都必須有相應的解決方案。首先你需要在業務代碼的框架層開發調用攔截程序,在調用的前后收集相關信息,把信息傳輸給到一個統一的處理中心。然后處理中心需要實時處理收集到鏈路信息,并按照traceId和spanId進行串聯,處理完以后再存到合適的存儲中。最后還要能把存儲中存儲的信息,以調用鏈路圖或者調用拓撲圖的形式對外展示。
可以想象這個技術難度以及開發工作量都不小,對于大部分中小業務團隊來說,都十分具有挑戰。不過幸運的是,業界已經有不少開源的服務追蹤系統實現,并且應用范圍也已經十分廣泛,對大部分的中小業務團隊來說,足以滿足對服務追蹤系統的需求。
業界比較有名的服務追蹤系統實現有阿里的鷹眼、Twitter開源的OpenZipkin,還有Naver開源的Pinpoint,它們都是受Google發布的Dapper論文啟發而實現的。其中阿里的鷹眼解決方案沒有開源,而且由于阿里需要處理數據量比較大,所以鷹眼的定位相對定制化。
下面我主要來介紹下開源實現方案OpenZipkin和Pinpoint,再看看它們有什么區別。
OpenZipkin
OpenZipkin是Twitter開源的服務追蹤系統,下面這張圖展示了它的架構設計。
從圖中看,OpenZipkin主要由四個核心部分組成。
- Collector:負責收集探針Reporter埋點采集的數據,經過驗證處理并建立索引。
- Storage:存儲服務調用的鏈路數據,默認使用的是Cassandra,是因為Twitter內部大量使用了Cassandra,你也可以替換成Elasticsearch或者MySQL。
- API:將格式化和建立索引的鏈路數據以API的方式對外提供服務,比如被UI調用。
- UI:以圖形化的方式展示服務調用的鏈路數據。
它的工作原理可以用下面這張圖來描述。
具體流程是,通過在業務的HTTP Client前后引入服務追蹤代碼,這樣在HTTP方法“/foo”調用前,生成trace信息:TraceId:aa、SpanId:6b、annotation:GET /foo,以及當前時刻的timestamp:1483945573944000,然后調用結果返回后,記錄下耗時duration,之后再把這些trace信息和duration異步上傳給Zipkin Collector。
Pinpoint
Pinpoint是Naver開源的一款深度支持Java語言的服務追蹤系統,下面這張圖是它的架構設計。
具體來看,就是請求進入TomcatA,然后生成TraceId:TomcatA^ TIME ^ 1、SpanId:10、pSpanId:-1(代表是根請求),接著TomatA調用TomcatB的hello方法,TomcatB生成TraceId:TomcatA^ TIME ^1、新的SpanId:20、pSpanId:10(代表是TomcatA的請求),返回調用結果后將trace信息發給Collector,TomcatA收到調用結果后,將trace信息也發給Collector。Collector把trace信息寫入到HBase中,Rowkey就是traceId,SpanId和pSpanId都是列。然后就可以通過UI查詢調用鏈路信息了。
選型對比
根據我的經驗,考察服務追蹤系統主要從下面這幾個方面。
1. 埋點探針支持平臺的廣泛性
OpenZipkin和Pinpoint都支持哪些語言平臺呢?
OpenZipkin提供了不同語言的Library,不同語言實現時需要引入不同版本的Library。
官方提供了C#、Go、Java、JavaScript、Ruby、Scala、PHP等主流語言版本的Library,而且開源社區還提供了更豐富的不同語言版本的Library,詳細的可以點擊這里查看;而Pinpoint目前只支持Java語言。
所以從探針支持的語言平臺廣泛性上來看,OpenZipkin比Pinpoint的使用范圍要廣,而且開源社區很活躍,生命力更強。
2. 系統集成難易程度
再來看下系統集成的難易程度。
以OpenZipkin的Java探針Brave為例,它只提供了基本的操作API,如果系統要想集成Brave,必須在配置里手動里添加相應的配置文件并且增加trace業務代碼。具體來講,就是你需要先修改工程的POM依賴,以引入Brave相關的JAR包。
<dependencyManagement><dependencies><dependency><groupId>io.zipkin.brave</groupId><artifactId>brave-bom</artifactId><version>${brave.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
然后假如你想收集每一次HTTP調用的信息,你就可以使用Brave在Apache Httpclient基礎上封裝的httpClient,它會記錄每一次HTTP調用的信息,并上報給OpenZipkin。
httpclient =TracingHttpClientBuilder.create(tracing).build();
而Pinpoint是通過字節碼注入的方式來實現攔截服務調用,從而收集trace信息的,所以不需要代碼做任何改動。Java字節碼注入的大致原理你可以參考下圖。
就是JVM在加載class二進制文件時,動態地修改加載的class文件,在方法的前后執行攔截器的before()和after()方法,在before()和after()方法里記錄trace()信息。而應用不需要修改業務代碼,只需要在JVM啟動時,添加類似下面的啟動參數就可以了。
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=<Agent's UniqueId>
-Dpinpoint.applicationName=<The name indicating a same service (AgentId collection)
所以從系統集成難易程度上看,Pinpoint要比OpenZipkin簡單。
3. 調用鏈路數據的精確度
從下面這張OpenZipkin的調用鏈路圖可以看出,OpenZipkin收集到的數據只到接口級別,進一步的信息就沒有了。
再來看下Pinpoint,因為Pinpoint采用了字節碼注入的方式實現trace信息收集,所以它能拿到的信息比OpenZipkin多得多。從下面這張圖可以看出,它不僅能夠查看接口級別的鏈路調用信息,還能深入到調用所關聯的數據庫信息。
同理在繪制鏈路拓撲圖時,OpenZipkin只能繪制服務與服務之間的調用鏈路拓撲圖,比如下面這張示意圖。
而Pinpoint不僅能夠繪制服務與服務之間,還能繪制與DB之間的調用鏈路拓撲圖,比如下圖。
所以,從調用鏈路數據的精確度上看,Pinpoint要比OpenZipkin精確得多。
總結
講解了兩個開源服務追蹤系統OpenZipkin和Pinpoint的具體實現,并從埋點探針支持平臺廣泛性、系統集成難易程度、調用鏈路數據精確度三個方面對它們進行了對比。
從選型的角度來講,如果你的業務采用的是Java語言,那么采用Pinpoint是個不錯的選擇,因為它不需要業務改動一行代碼就可以實現trace信息的收集。除此之外,Pinpoint不僅能看到服務與服務之間的鏈路調用,還能看到服務內部與資源層的鏈路調用,功能更為強大,如果你有這方面的需求,Pinpoint正好能滿足。
如果你的業務不是Java語言實現,或者采用了多種語言,那毫無疑問應該選擇OpenZipkin,并且,由于其開源社區很活躍,基本上各種語言平臺都能找到對應的解決方案。不過想要使用OpenZipkin,還需要做一些額外的代碼開發工作,以引入OpenZipkin提供的Library到你的系統中。
除了OpenZipkin和Pinpoint,業界還有其他開源追蹤系統實現,比如Uber開源的Jaeger,以及國內的一款開源服務追蹤系統SkyWalking。
17 | 如何識別服務節點是否存活?
講解注冊中心原理的時候,以開源注冊中心ZooKeeper為例,描述了它是如何管理注冊到注冊中心的節點的存活的。
其實ZooKeeper判斷注冊中心節點存活的機制其實就是注冊中心摘除機制,服務消費者以注冊中心中的數據為準,當服務端節點有變更時,注冊中心就會把變更通知給服務消費者,服務消費者就會調用注冊中心來拉取最新的節點信息。
這種機制在大部分情況下都可以工作得很好,但是在網絡頻繁抖動時,服務提供者向注冊中心匯報心跳信息可能會失敗,如果在規定的時間內,注冊中心都沒有收到服務提供者的心跳信息,就會把這個節點從可用節點列表中移除。更糟糕的是,在服務池擁有上百個節點的的時候,每個節點都可能會被移除,導致注冊中心可用節點的狀態一直在變化,這個時候應該如何處理呢?
下面就結合我在實踐中的經驗,給你講解幾種解決方案。
心跳開關保護機制
在網絡頻繁抖動的情況下,注冊中心中可用的節點會不斷變化,這時候服務消費者會頻繁收到服務提供者節點變更的信息,于是就不斷地請求注冊中心來拉取最新的可用服務節點信息。當有成百上千個服務消費者,同時請求注冊中心獲取最新的服務提供者的節點信息時,可能會把注冊中心的帶寬給占滿,尤其是注冊中心是百兆網卡的情況下。
所以針對這種情況,需要一種保護機制,即使在網絡頻繁抖動的時候,服務消費者也不至于同時去請求注冊中心獲取最新的服務節點信息。
我曾經就遇到過這種情況,一個可行的解決方案就是給注冊中心設置一個開關,當開關打開時,即使網絡頻繁抖動,注冊中心也不會通知所有的服務消費者有服務節點信息變更,比如只給10%的服務消費者返回變更,這樣的話就能將注冊中心的請求量減少到原來的1/10。
當然打開這個開關也是有一定代價的,它會導致服務消費者感知最新的服務節點信息延遲,原先可能在10s內就能感知到服務提供者節點信息的變更,現在可能會延遲到幾分鐘,所以在網絡正常的情況下,開關并不適合打開;可以作為一個緊急措施,在網絡頻繁抖動的時候,才打開這個開關。
服務節點摘除保護機制
服務提供者在進程啟動時,會注冊服務到注冊中心,并每隔一段時間,匯報心跳給注冊中心,以標識自己的存活狀態。如果隔了一段固定時間后,服務提供者仍然沒有匯報心跳給注冊中心,注冊中心就會認為該節點已經處于“dead”狀態,于是從服務的可用節點信息中移除出去。
如果遇到網絡問題,大批服務提供者節點匯報給注冊中心的心跳信息都可能會傳達失敗,注冊中心就會把它們都從可用節點列表中移除出去,造成剩下的可用節點難以承受所有的調用,引起“雪崩”。但是這種情況下,可能大部分服務提供者節點是可用的,僅僅因為網絡原因無法匯報心跳給注冊中心就被“無情”的摘除了。
這個是時候就需要根據實際業務的情況,設定一個閾值比例,即使遇到剛才說的這種情況,注冊中心也不能摘除超過這個閾值比例的節點。
這個閾值比例可以根據實際業務的冗余度來確定,我通常會把這個比例設定在20%
,就是說注冊中心不能摘除超過20%的節點。因為大部分情況下,節點的變化不會這么頻繁,只有在網絡抖動或者業務明確要下線大批量節點的情況下才有可能發生。而業務明確要下線大批量節點的情況是可以預知的,這種情況下可以關閉閾值保護;而正常情況下,應該打開閾值保護,以防止網絡抖動時,大批量可用的服務節點被摘除。
講到這里,我們先小結一下。
心跳開關保護機制,是為了防止服務提供者節點頻繁變更導致的服務消費者同時去注冊中心獲取最新服務節點信息;服務節點摘除保護機制,是為了防止服務提供者節點被大量摘除引起服務消費者可以調用的節點不足。
可見,無論是心跳開關保護機制還是服務節點摘除保護機制,都是因為注冊中心里的節點信息是隨時可能發生變化的,所以也可以把注冊中心叫作動態注冊中心。
那么是不是可以換個思路,服務消費者并不嚴格以注冊中心中的服務節點信息為準,而是更多的以服務消費者實際調用信息來判斷服務提供者節點是否可用。這就是下面我要講的靜態注冊中心。
靜態注冊中心
前面講過心跳機制能保證在服務提供者出現異常時,注冊中心可以及時把不可用的服務提供者從可用節點列表中移除出去,正常情況下這是個很好的機制。
但是仔細思考一下,為什么不把這種心跳機制直接用在服務消費者端呢?
因為服務提供者是向服務消費者提供給服務的,是否可用服務消費者應該比注冊中心更清楚,因此可以直接在服務消費者端根據調用服務提供者是否成功來判定服務提供者是否可用。如果服務消費者調用某一個服務提供者節點連續失敗超過一定次數,可以在本地內存中將這個節點標記為不可用。并且每隔一段固定時間,服務消費者都要向標記為不可用的節點發起保活探測
,如果探測成功了,就將標記為不可用的節點再恢復為可用狀態,重新發起調用。
這樣的話,服務提供者節點就不需要向注冊中心匯報心跳信息,注冊中心中的服務節點信息也不會動態變化,也可以稱之為靜態注冊中心。
從我的實踐經歷來看,一開始采用了動態注冊中心,后來考慮考慮到網絡的復雜性,心跳機制不一定是可靠的,而后開始改為采用服務消費者端的保活機制,事實證明這種機制足以應對網絡頻繁抖動等復雜的場景。
當然靜態注冊中心中的服務節點信息并不是一直不變,當在業務上線或者運維人工增加或者刪除服務節點這種預先感知的情況下,還是有必要去修改注冊中心中的服務節點信息。
比如在業務上線過程中,需要把正在部署的服務節點從注冊中心中移除,等到服務部署完畢,完全可用的時候,再加入到注冊中心。還有就是在業務新增或者下線服務節點的時候,需要調用注冊中心提供的接口,添加節點信息或者刪除節點。這個時候靜態注冊中心有點退化到配置中心的意思,只不過這個時候配置中心里存儲的不是某一項配置,而是某個服務的可用節點信息。
總結
今天我給你講解了動態注冊中心在實際線上業務運行時,如果遇到網絡不可靠等因素,可能會帶來的兩個問題,一個是服務消費者同時并發訪問注冊中心獲取最新服務信息導致注冊中心帶寬被打滿;另一個是服務提供者節點被大量摘除導致服務消費者沒有足夠的節點可以調用。
這兩個問題都是我在業務實戰過程中遇到過的,我給出的兩個解決方案:心跳開關保護機制和服務節點摘除保護機制都是在實戰中應用過的,并且被證明是行之有效的。
而靜態注冊中心的思路,是在斟酌注冊中心的本質之后,引入的另外一個解決方案,相比于動態注冊中心更加簡單,并且基于服務消費者本身調用來判斷服務節點是否可用,更加直接也更加準確,尤其在注冊中心或者網絡出現問題的時候,這種方案基本不受影響。
18 | 如何使用負載均衡算法?
假設你訂閱了一個別人的服務,從注冊中心查詢得到了這個服務的可用節點列表,而這個列表里包含了幾十個節點,這個時候你該選擇哪個節點發起調用呢?這就是今天我要給你講解的關于客戶端負載均衡算法的問題。
為什么要引入負載均衡算法呢?主要有兩個原因:一個是要考慮調用的均勻性,也就是要讓每個節點都接收到調用,發揮所有節點的作用;另一個是要考慮調用的性能,也就是哪個節點響應最快,優先調用哪個節點。
常見的負載均衡算法
1. 隨機算法
從可用的服務節點中,隨機挑選一個節點來訪問。
在實現時,隨機算法通常是通過生成一個隨機數來實現,比如服務有10個節點,那么就每一次生成一個1~10之間的隨機數,假設生成的是2,那么就訪問編號為2的節點。
采用隨機算法,在節點數量足夠多,并且訪問量比較大的情況下,各個節點被訪問的概率是基本相同的。
2. 輪詢算法
輪詢算法,就是按照固定的順序,把可用的服務節點,挨個訪問一次。
在實現時,輪詢算法通常是把所有可用節點放到一個數組里,然后按照數組編號,挨個訪問。比如服務有10個節點,放到數組里就是一個大小為10的數組,這樣的話就可以從序號為0的節點開始訪問,訪問后序號自動加1,下一次就會訪問序號為1的節點,以此類推。
輪詢算法能夠保證所有節點被訪問到的概率是相同的。
3. 加權輪詢算法
輪詢算法能夠保證所有節點被訪問的概率相同,而加權輪詢算法是在此基礎上,給每個節點賦予一個權重,從而使每個節點被訪問到的概率不同,權重大的節點被訪問的概率就高,權重小的節點被訪問的概率就小。
在實現時,加權輪詢算法是生成一個節點序列,該序列里有n個節點,n是所有節點的權重之和。在這個序列中,每個節點出現的次數,就是它的權重值。比如有三個節點:a、b、c,權重分別是3、2、1,那么生成的序列就是{a、a、b、c、b、a},這樣的話按照這個序列訪問,前6次請求就會分別訪問節點a三次,節點b兩次,節點c一次。從第7個請求開始,又重新按照這個序列的順序來訪問節點。
在應用加權輪詢算法的時候,根據我的經驗,要盡可能保證生產的序列的均勻,如果生成的不均勻會造成節點訪問失衡,比如剛才的例子,如果生成的序列是{a、a、a、b、b、c},就會導致前3次訪問的節點都是a。
4. 最少活躍連接算法
最少活躍連接算法,顧名思義就是每一次訪問都選擇連接數最少的節點。因為不同節點處理請求的速度不同,使得同一個服務消費者同每一個節點的連接數都不相同。連接數大的節點,可以認為是處理請求慢,而連接數小的節點,可以認為是處理請求快。所以在挑選節點時,可以以連接數為依據,選擇連接數最少的節點訪問。
在實現時,需要記錄跟每一個節點的連接數,這樣在選擇節點時,才能比較出連接數最小的節點。
5. 一致性hash算法
一致性hash算法,是通過某個hash函數,把同一個來源的請求都映射到同一個節點上。一致性hash算法最大的特點就是同一個來源的請求,只會映射到同一個節點上,可以說是具有記憶功能。只有當這個節點不可用時,請求才會被分配到相鄰的可用節點上。
負載均衡算法的使用場景
上面這五種負載均衡算法,具體在業務中該如何選擇呢?根據我的經驗,它們的各自應用場景如下:
- 隨機算法:實現比較簡單,在請求量遠超可用服務節點數量的情況下,各個服務節點被訪問的概率基本相同,主要應用在各個服務節點的性能差異不大的情況下。
- 輪詢算法:跟隨機算法類似,各個服務節點被訪問的概率也基本相同,也主要應用在各個服務節點性能差異不大的情況下。
- 加權輪詢算法:在輪詢算法基礎上的改進,可以通過給每個節點設置不同的權重來控制訪問的概率,因此主要被用在服務節點性能差異比較大的情況。比如經常會出現一種情況,因為采購時間的不同,新的服務節點的性能往往要高于舊的節點,這個時候可以給新的節點設置更高的權重,讓它承擔更多的請求,充分發揮新節點的性能優勢。
- 最少活躍連接算法:與加權輪詢算法預先定義好每個節點的訪問權重不同,采用最少活躍連接算法,客戶端同服務端節點的連接數是在時刻變化的,理論上連接數越少代表此時服務端節點越空閑,選擇最空閑的節點發起請求,能獲取更快的響應速度。尤其在服務端節點性能差異較大,而又不好做到預先定義權重時,采用最少活躍連接算法是比較好的選擇。
- 一致性hash算法:因為它能夠保證同一個客戶端的請求始終訪問同一個服務節點,所以適合服務端節點處理不同客戶端請求差異較大的場景。比如服務端緩存里保存著客戶端的請求結果,如果同一客戶端一直訪問一個服務節點,那么就可以一直從緩存中獲取數據。
這五種負載均衡算法是業界最常用的,不光在RPC調用中被廣泛采用,在一些負載均衡組件比如Nginx中也有應用,所以說是一種通用的負載均衡算法,但是不是所有的業務場景都能很好解決呢?
我曾經遇到過這種場景:
- 服務節點數量眾多,且性能差異比較大;
- 服務節點列表經常發生變化,增加節點或者減少節點時有發生;
- 客戶端和服務節點之間的網絡情況比較復雜,有些在一個數據中心,有些不在一個數據中心需要跨網訪問,而且網絡經常延遲或者抖動。
顯然無論是隨機算法還是輪詢算法,第一個情況就不滿足,加權輪詢算法需要預先配置服務節點的權重,在節點列表經常變化的情況下不好維護,所以也不適合。而最少活躍連接算法是從客戶端自身維度去判斷的,在實際應用時,并不能直接反映出服務節點的請求量大小,尤其是在網絡情況比較復雜的情況下,并不能做到動態的把請求發送給最合適的服務節點。至于一致性hash算法,顯然不適合這種場景。
針對上面這種場景,有一種算法更加適合,這種算法就是自適應最優選擇算法。
自適應最優選擇算法
這種泛的主要思想是在客戶端本地維護一份同每一個服務節點的性能統計快照,并且每隔一段時間去更新這個快照。在發起請求時,根據“二八原則”,吧服務節點分為兩部分,找出20%的那部分響應最慢的節點,然后降低權重。這樣的話,客戶端就能夠實時的根據自身訪問每個節點性能的快慢,動態調整訪問最慢的那些節點的權重,來減少訪問量,從而可以優化長尾請求。
由此可見,自適應最優選擇算法是對加權輪詢算法的改良,可以看作是一種動態加權輪詢算法
。它的實現關鍵之處就在于兩點:第一點是每隔一段時間獲取客戶端同每個服務節點之間調用的平均性能統計;第二點是按照這個性能統計對服務節點進行排序,對排在性能倒數20%的那部分節點賦予一個較低的權重,其余的節點賦予正常的權重。
在具體實現時,針對第一點,需要在內存中開辟一塊空間記錄客戶端同每一個服務節點之間調用的平均性能,并每隔一段固定時間去更新。這個更新的時間間隔不能太短,太短的話很容易受瞬時的性能抖動影響,導致統計變化太快,沒有參考性;同時也不能太長,太長的話時效性就會大打折扣,效果不佳。根據我的經驗,1分鐘
的更新時間間隔是個比較合適的值。
針對第二點,關鍵點是權重值的設定,即使服務節點之間的性能差異較大,也不適合把權重設置得差異太大,這樣會導致性能較好的節點與性能較差的節點之間調用量相差太大,這樣也不是一種合理的狀態。在實際設定時,可以設置20%性能較差的節點權重為3,其余節點權重為5。
總結
講解了最常用的五種客戶端負載均衡算法的原理以及適用場景,在業務實踐的過程匯總,究竟采用哪種,需要根據實際情況來決定,并不是算法越復雜越好。
比如在一種簡單的業務場景下,有10個服務節點,并且配置基本相同,位于同一個數據中心,此時客戶端選擇隨機算法或者輪詢算法既簡單又高效,并沒有必要選擇加權輪詢算法或者最少活躍連接算法。
但在遇到前面提到的那種復雜業務場景下,服務節點數量眾多,配置差異比較大,而且位于不同的數據中心,客戶端與服務節點之間的網絡情況也比較復雜,這個時候簡單的負載均衡算法通常都難以應對,需要針對實際情況,選擇更有針對性的負載均衡算法,比如自適應最優選擇算法。
思考題
今天我給你講的都屬于軟件層面的負載均衡算法,它與F5這種硬件負載均衡器有什么不同呢?
19 | 如何使用服務路由?
在業務中經常還會遇到這樣的場景,比如服務A部署在北京、上海、廣州三個數據中心,所有的服務節點按照所在的數據中心被分成了三組,那么服務A的消費者在發起調用時,該如何選擇呢?這就是今天我要給你講解的服務路由的問題。
那么什么是服務路由呢?我的理解是服務路由就是服務消費者在發起服務調用時,必須根據特定的規則來選擇服務節點,從而滿足某些特定的需求。
那么服務路由都有哪些應用場景?具體都有哪些規則呢?
服務路由的應用場景
服務路由主要有以下幾種應用場景:
- 分組調用。一般來講,為了保證服務的高可用性,實現異地多活的需求,一個服務往往不止部署在一個數據中心,而且出于節省成本等考慮,有些業務可能不僅在私有機房部署,還會采用公有云部署,甚至采用多家公有云部署。服務節點也會按照不同的數據中心分成不同的分組,這時對于服務消費者來說,選擇哪一個分組調用,就必須有相應的路由規則。
- 灰度發布。在服務上線發布的過程中,一般需要先在一小部分規模的服務節點上先發布服務,然后驗證功能是否正常。如果正常的話就繼續擴大發布范圍;如果不正常的話,就需要排查問題,解決問題后繼續發布。這個過程就叫作灰度發布,也叫金絲雀部署。
- 流量切換。在業務線上運行過程中,經常會遇到一些不可抗力因素導致業務故障,比如某個機房的光纜被挖斷,或者發生著火等事故導致整個機房的服務都不可用。這個時候就需要按照某個指令,能夠把原來調用這個機房服務的流量切換到其他正常的機房。
- 讀寫分離。對于大多數互聯網業務來說都是讀多寫少,所以在進行服務部署的時候,可以把讀寫分開部署,所有寫接口可以部署在一起,而讀接口部署在另外的節點上。
上面四種應用場景是實際業務中很常見的,服務路由可以通過各種規則來實現,那么服務路由都有哪些規則呢?
服務路由的規則
根據我的實踐經驗,服務路由主要有兩種規則:一種是條件路由,一種是腳本路由。
1. 條件路由
條件路由是基于條件表達式的路由規則,以下面的條件路由為例,我來給你詳細講解下它的用法。
condition://0.0.0.0/dubbo.test.interfaces.TestService?category=routers&dynamic=true&priority=2&enabled=true&rule=" + URL.encode(" host = 10.20.153.10=> host = 10.20.153.11")
這里面“condition://”代表了這是一段用條件表達式編寫的路由規則,具體的規則是
host = 10.20.153.10 => host = 10.20.153.11
分隔符“=>”前面是服務消費者的匹配條件,后面是服務提供者的過濾條件。當服務消費者節點滿足匹配條件時,就對該服務消費者執行后面的過濾規則。那么上面這段表達式表達的意義就是IP為“10.20.153.10”的服務消費者都調用IP為“10.20.153.11”的服務提供者節點。
如果服務消費者的匹配條件為空,就表示對所有的服務消費者應用,就像下面的表達式一樣。
=> host != 10.20.153.11
如果服務提供者的過濾條件為空,就表示禁止服務消費者訪問,就像下面的表達式一樣。
host = 10.20.153.10=>
下面我舉一些Dubbo框架中的條件路由,來給你講解下條件路由的具體應用場景。
- 排除某個服務節點
=> host != 172.22.3.91
一旦這條路由規則被應用到線上,所有的服務消費者都不會訪問IP為172.22.3.91的服務節點,這種路由規則一般應用在線上流量排除預發布機以及摘除某個故障節點的場景。
- 白名單和黑名單功能
host != 10.20.153.10,10.20.153.11 =>
這條路由規則意思是除了IP為10.20.153.10和10.20.153.11的服務消費者可以發起服務調用以外,其他服務消費者都不可以,主要用于白名單訪問邏輯,比如某個后臺服務只允許特定的幾臺機器才可以訪問,這樣的話可以機器控制訪問權限。
host = 10.20.153.10,10.20.153.11 =>
同理,這條路由規則意思是除了IP為10.20.153.10和10.20.153.11的服務消費者不能發起服務調用以外,其他服務消費者都可以,也就是實現了黑名單功能,比如線上經常會遇到某些調用方不管是出于有意還是無意的不合理調用,影響了服務的穩定性,這時候可以通過黑名單功能暫時予以封殺。
- 機房隔離
host = 172.22.3.* => host = 172.22.3.*
這條路由規則意思是IP網段為172.22.3.*的服務消費者,才可以訪問同網段的服務節點,這種規則一般應用于服務部署在多個IDC,理論上同一個IDC內的調用性能要比跨IDC調用性能要好,應用這個規則是為了實現同IDC就近訪問。
- 讀寫分離
method = find,list,get,is => host =172.22.3.94,172.22.3.95 method != find,list,get,is => host = 172.22.3.97,172.22.3.98
這條路由規則意思是find*、get*、is*等讀方法調用IP為172.22.3.94和172.22.3.95的節點,除此以外的寫方法調用IP為172.22.3.97和172.22.3.98的節點。對于大部分互聯網業務來說,往往讀請求要遠遠大于寫請求,而寫請求的重要性往往要遠遠高于讀請求,所以需要把讀寫請求進行分離,以避免讀請求異常影響到寫請求,這時候就可以應用這種規則。
2. 腳本路由
腳本路由是基于腳本語言的路由規則,常用的腳本語言比如JavaScript、Groovy、JRuby等。以下面的腳本路由規則為例,我來給你詳細講解它的用法。
"script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("(function route(invokers) { ... } (invokers))")
這里面“script://”就代表了這是一段腳本語言編寫的路由規則,具體規則定義在腳本語言的route方法實現里,比如下面這段用JavaScript編寫的route()方法表達的意思是,只有IP為10.20.153.10的服務消費者可以發起服務調用。
function route(invokers){var result = new java.util.ArrayList(invokers.size());for(i =0; i < invokers.size(); i ++){if("10.20.153.10".equals(invokers.get(i).getUrl().getHost())){ result.add(invokers.get(i));} }return result; } (invokers));
既然服務路由是通過路由規則來實現的,那么服務消費者該如何獲取路由規則呢?
服務路由的獲取方式
根據我的實踐經驗,服務路由的獲取方式主要有三種:
- 本地配置
顧名思義就是路由規則存儲在服務消費者本地上。服務消費者發起調用時,從本地固定位置讀取路由規則,然后按照路由規則選取一個服務節點發起調用。 - 配置中心管理
這種方式下,所有的服務消費者都從配置中心獲取路由規則,由配置中心來統一管理。 - 動態下發
這種方式下,一般是運維人員或者開發人員,通過服務治理平臺修改路由規則,服務治理平臺調用配置中心接口,把修改后的路由規則持久化到配置中心。因為服務消費者訂閱了路由規則的變更,于是就會從配置中心獲取最新的路由規則,按照最新的路由規則來執行。
上面三種方式實際使用時,還是有一定區別的。
一般來講,服務路由最好是存儲在配置中心中,由配置中心來統一管理。這樣的話,所有的服務消費者就不需要在本地管理服務路由,因為大部分的服務消費者并不關心服務路由的問題,或者說也不需要去了解其中的細節。通過配置中心,統一給各個服務消費者下發統一的服務路由,節省了溝通和管理成本。
但也不排除某些服務消費者有特定的需求,需要定制自己的路由規則,這個時候就適合通過本地配置來定制。
而動態下發可以理解為一種高級功能,它能夠動態地修改路由規則,在某些業務場景下十分有用。比如某個數據中心存在問題,需要把調用這個數據中心的服務消費者都切換到其他數據中心,這時就可以通過動態下發的方式,向配置中心下發一條路由規則,將所有調用這個數據中心的請求都遷移到別的地方。
當然,這三種方式也可以一起使用,這個時候服務消費者的判斷優先級是本地配置>動態下發>配置中心管理。
總結
服務路由的作用就是為了實現某些調用的特殊需求,比如分組調用、灰度發布、流量切換、讀寫分離等。在業務規模比較小的時候,可能所有的服務節點都部署在一起,也就不需要服務路由。但隨著業務規模的擴大、服務節點增多,尤其是涉及多數據中心部署的情況,把服務節點按照數據中心進行分組,或者按照業務的核心程度進行分組,對提高服務的可用性是十分有用的。以微博業務為例,有的服務不僅進行了核心服務和非核心服務分組,還針對私有云和公有云所處的不同數據中心也進行了分組,這樣的話就可以將服務之間的調用盡量都限定在同一個數據中心內部,最大限度避免跨數據中心的網絡延遲、抖動等影響。
而服務路由具體是在本地配置,還是在配置中心統一管理,也是視具體業務需求而定的。如果沒有定制化的需求,建議把路由規則都放到配置中心中統一存儲管理。而動態下發路由規則對于服務治理十分有幫助,當數據中心出現故障的時候,可以實現動態切換流量,還可以摘除一些有故障的服務節點。
20 | 服務端出現故障時該如何應對?
微服務系統可能出現故障的種類,主要有三種故障。
- 集群故障。根據我的經驗,微服務系統一般都是集群部署的,根據業務量大小而定,集群規模從幾臺到甚至上萬臺都有可能。一旦某些代碼出現bug,可能整個集群都會發生故障,不能提供對外提供服務。
- 單IDC故障。現在大多數互聯網公司為了保證業務的高可用性,往往業務部署在不止一個IDC。然而現實中時常會發生某個IDC的光纜因為道路施工被挖斷,導致整個IDC脫網。
- 單機故障。顧名思義就是集群中的個別機器出現故障,這種情況往往對全局沒有太大影響,但會導致調用到故障機器上的請求都失敗,影響整個系統的成功率。
集群故障
一般而言,集群故障的產生原因不外乎有兩種:一種是代碼bug所導致,比如說某一段Java代碼不斷地分配大對象,但沒有及時回收導致JVM OOM退出;另一種是突發的流量沖擊,超出了系統的最大承載能力,比如“雙11”這種購物活動,電商系統會在零點一瞬間涌入大量流量,超出系統的最大承載能力,一下子就把整個系統給壓垮了。
應付集群故障的思路,主要有兩種:限流和降級。
1. 限流
顧名思義,限流就是限制流量,通常情況下,系統能夠承載的流量根據集群規模的大小是固定的,可以稱之為系統的最大容量。當真實流量超過了系統的最大容量后,就會導致系統響應變慢,服務調用出現大量超時,反映給用戶的感覺就是卡頓、無響應。所以,應該根據系統的最大容量,給系統設置一個閾值,超過這個閾值的請求會被自動拋棄,這樣的話可以最大限度地保證系統提供的服務正常。
除此之外,通常一個微服務系統會同時提供多個服務,每個服務在同一時刻的請求量也是不同的,很可能出現的一種情況就是,系統中某個服務的請求量突增,占用了系統中大部分資源,導致其他服務沒有資源可用。因此,還要針對系統中每個服務的請求量也設置一個閾值,超過這個閾值的請求也要被自動拋棄,這樣的話不至于因為一個服務影響了其他所有服務。
在實際項目中,可以用兩個指標來衡量服務的請求量,一個是QPS即每秒請求量,一個是工作線程數。不過QPS因為不同服務的響應快慢不同,所以系統能夠承載的QPS相差很大,因此一般選擇工作線程數來作為限流的指標,給系統設置一個總的最大工作線程數
以及單個服務的最大工作線程數,這樣的話無論是系統的總請求量過大導致整體工作線程數量達到最大工作線程數,還是某個服務的請求量超過單個服務的最大工作線程數,都會被限流,以起到保護整個系統的作用。
2. 降級
在我看來,降級就是通過停止系統中的某些功能,來保證系統整體的可用性。降級可以說是一種被動防御的措施,為什么這么說呢?因為它一般是系統已經出現故障后所采取的一種止損措施。
那么降級一般是如何實現的呢?根據我的實踐來看, 一種可行的方案是通過開關來實現。
具體來講,就是在系統運行的內存中開辟一塊區域,專門用于存儲開關的狀態,也就是開啟還是關閉。并且需要監聽某個端口,通過這個端口可以向系統下發命令,來改變內存中開關的狀態。當開關開啟時,業務的某一段邏輯就不再執行,而正常情況下,開關是關閉的狀態。
開關一般用在兩種地方,一種是新增的業務邏輯,因為新增的業務邏輯相對來說不成熟,往往具備一定的風險,所以需要加開關來控制新業務邏輯是否執行;另一種是依賴的服務或資源,因為依賴的服務或者資源不總是可靠的,所以最好是有開關能夠控制是否對依賴服務或資源發起調用,來保證即使依賴出現問題,也能通過降級來避免影響。
在實際業務應用的時候,降級要按照對業務的影響程度進行分級,一般分為三級:
- 一級降級是對業務影響最小的降級,在故障的情況下,首先執行一級降級,所以一級降級也可以設置成自動降級,不需要人為干預;
- 二級降級是對業務有一定影響的降級,在故障的情況下,如果一級降級起不到多大作用的時候,可以人為采取措施,執行二級降級;
- 三級降級是對業務有較大影響的降級,這種降級要么是對商業收入有重大影響,要么是對用戶體驗有重大影響,所以操作起來要非常謹慎,不在最后時刻一般不予采用。
單IDC故障
在現實情況下,整個IDC脫網的事情時有發生,多半是因為不可抗力比如機房著火、光纜被挖斷等,如果業務全部部署在這個IDC,那就完全不可訪問了,所以國內大部分的互聯網業務多采用多IDC部署。具體來說,有的采用同城雙活,也就是在一個城市的兩個IDC內部署;有的采用異地多活,一般是在兩個城市的兩個IDC內部署;當然也有支付寶這種金融級別的應用采用了“三地五中心
”部署,這種部署成本顯然高比兩個IDC要高得多,但可用性的保障要更高。
采用多IDC部署的最大好處就是當有一個IDC發生故障時,可以把原來訪問故障IDC的流量切換到正常的IDC,來保證業務的正常訪問。
流量切換的方式一般有兩種,一種是基于DNS解析的流量切換,一種是基于RPC分組的流量切換。
1. 基于DNS解析的瀏覽切換
基于DNS解析流量的切換,一般是通過把請求訪問域名解析的VIP從一個IDC切換到另外一個IDC。比如訪問“www.weibo.com”,正常情況下北方用戶會解析到聯通機房的VIP,南方用戶會解析到電信機房的VIP,如果聯通機房發生故障的話,會把北方用戶訪問也解析到電信機房的VIP,只不過此時網絡延遲可能會變長。
2. 基于RPC分組的流量切換
對于一個服務來說,如果是部署在多個IDC的話,一般每個IDC就是一個分組。假如一個IDC出現故障,那么原先路由到這個分組的流量,就可以通過向配置中心
下發命令,把原先路由到這個分組的流量全部切換到別的分組,這樣的話就可以切換故障IDC的流量了。
單機故障
單機故障是發生概率最高的一種故障了,尤其對于業務量大的互聯網應用來說,上萬臺機器的規模也是很常見的。這種情況下,發生單機故障的概率就很高了,這個時候只靠運維人肉處理顯然不可行,所以就要求有某種手段來自動處理單機故障。
根據我的經驗,處理單機故障一個有效的辦法就是自動重啟。具體來講,你可以設置一個閾值,比如以某個接口的平均耗時為準,當監控單機上某個接口的平均耗時超過一定閾值時,就認為這臺機器有問題,這個時候就需要把有問題的機器從線上集群中摘除掉,然后在重啟服務后,重新加入到集群中。
不過這里要注意的是,需要防止網絡抖動
造成的接口超時從而觸發自動重啟。一種方法是在收集單機接口耗時數據時,多采集幾個點,比如每10s采集一個點,采集5個點,當5個點中有超過3個點的數據都超過設定的閾值范圍,才認為是真正的單機問題,這時會觸發自動重啟策略。
除此之外,為了防止某些特殊情況下,短時間內被重啟的單機過多,造成整個服務池可用節點數太少,最好是設置一個可重啟的單機數量占整個集群的最大比例,一般這個比例不要超過10%,因為正常情況下,不大可能有超過10%的單機都出現故障。
總結
今天我們探討了微服務系統可能出現的三種故障:集群故障、單IDC故障、單機故障,并且針對這三種故障我給出了分別的解決方案,包括降級、限流、流量切換以及自動重啟。
在遇到實際的故障時,往往多個手段是并用的,比如在出現單IDC故障,首先要快速切換流量到正常的IDC,但此時可能正常IDC并不足以支撐兩個IDC的流量,所以這個時候首先要降級部分功能,保證正常的IDC順利支撐切換過來的流量。
而且要盡量讓故障處理自動化,這樣可以大大減少故障影響的時間。因為一旦需要引入人為干預,往往故障處理的時間都得是10分鐘以上,這對大部分用戶敏感型業務的影響是巨大的,如果能做到自動化故障處理的話,可以將故障處理的時間降低到1分鐘以內甚至秒級別,這樣的話對于用戶的影響最小。
21 | 服務調用失敗時有哪些處理手段?
微服務相比于單體應用最大的不同之處在于,服務的調用從同一臺機器內部的本地調用變成了不同機器之間的遠程方法調用,但是這個過程也引入了兩個不確定的因素。
一個是調用的執行在服務調用者一端,即使服務消費者本身是正常的,服務提供者也可能由于諸如CPU、網絡I/O、磁盤、內存、網卡等硬件原因導致調用失敗,還有可能由于本身程序執行問題比如GC暫停導致調用失敗。
另一個不確定因素是調用發生在兩臺機器之間,所以要經過網絡傳輸,而網絡的復雜性是不可控的,網絡丟包、延遲以及隨時可能發生的瞬間抖動都有可能造成調用失敗。
所以,單體應用改造為微服務架構后,要針對服務調用失敗進行特殊處理。那具體來說有哪些處理手段呢?下面我就結合自己的實戰經驗,一起來聊聊服務調用失敗都有哪些處理手段。
超時
單體應用被改造成微服務架構后,一次用戶調用可能會被拆分成多個系統之間的服務調用,任何一次服務調用如果發生問題都可能會導致最后用戶調用失敗。而且在微服務架構下,一個系統的問題會影響所有調用這個系統所提供服務的服務消費者,如果不加以控制,嚴重的話會引起整個系統雪崩。
所以在實際項目中,針對服務調用都要設置一個超時時間,以避免依賴的服務遲遲沒有返回調用結果,把服務消費者拖死。這其中,超時時間的設定也是有講究的,不是越短越好,因為太短可能會導致有些服務調用還沒有來得及執行完就被丟棄了;當然時間也不能太長,太長有可能導致服務消費者被拖垮。根據我的經驗,找到比較合適的超時時間需要根據正常情況下,服務提供者的服務水平來決定。具體來說,就是按照服務提供者線上真實的服務水平,取P999或者P9999的值,也就是以99.9%或者99.99%的調用都在多少毫秒內返回為準。
重試
雖然設置超時時間可以起到及時止損的效果,但是服務調用的結果畢竟是失敗了,而大部分情況下,調用失敗都是因為偶發的網絡問題或者個別服務提供者節點有問題導致的,如果能換個節點再次訪問說不定就能成功。而且從概率論的角度來講,假如一次服務調用失敗的概率為1%,那么連續兩次服務調用失敗的概率就是0.01%,失敗率降低到原來的1%。
所以,在實際服務調用時,經常還要設置一個服務調用超時后的重試次數。假如某個服務調用的超時時間設置為100ms,重試次數設置為1,那么當服務調用超過100ms后,服務消費者就會立即發起第二次服務調用,而不會再等待第一次調用返回的結果了。
雙發
假如一次調用不成功的概率為1%,那么連續兩次調用都不成功的概率就是0.01%,根據這個推論,一個簡單的提高服務調用成功率的辦法就是每次服務消費者要發起服務調用的時候,都同時發起兩次服務調用,一方面可以提高調用的成功率,另一方面兩次服務調用哪個先返回就采用哪次的返回結果,平均響應時間也要比一次調用更快,這就是雙發。
但是這樣的話,一次調用會給后端服務兩倍的壓力,所要消耗的資源也是加倍的,所以一般情況下,這種“魯莽”的雙發是不可取的。我這里講一個更為聰明的雙發,即“備份請求”(Backup Requests),它的大致思想是服務消費者發起一次服務調用后,在給定的時間內如果沒有返回請求結果,那么服務消費者就立即發起另一次服務調用。這里需要注意的是,這個設定的時候通常要比超時時間短得多,比如超時時間取的是P999,那么備份請求時間取的可能是P99或者P90,這是因為如果在P99或者P90的時間內調用還沒有返回結果,那么大概率可以認為這次請求屬于慢請求了,再次發起調用理論上返回要更快一些。
在實際線上服務運行時,P999由于長尾請求時間較長的緣故,可能要遠遠大于P99和P90。在我經歷的一個項目中,一個服務的P999是1s,而P99只有200ms、P90只有50ms,這樣的話,如果備份請求時間取的是P90,那么第二次請求等待的時間只有50ms。不過這里需要注意的是,備份請求要設置一個最大重試比例,以避免在服務端出現問題的時,大部分請求響應時間都會超過P90的值,導致請求量幾乎翻倍,給服務提供者造成更大的壓力。我的經驗是這個最大重試比例可以設置成15%,一方面能盡量體現備份請求的優勢,另一方面不會給服務提供者額外增加太大的壓力。
熔斷
假如服務提供者出現故障,短時間內無法恢復時,無論是超時重試還是雙發不但不能提高服務調用的成功率,反而會因為重試給服務提供者帶來更大的壓力,從而加劇故障。
針對這種情況,就需要服務消費者能夠探測到服務提供者發生故障,并短時間內停止請求,給服務提供者故障恢復的時間,待服務提供者恢復后,再繼續請求。這就好比一條電路,電流負載過高的話,保險絲就會熔斷,以防止火災的發生,所以這種手段就被叫作“熔斷”。
首先我們先來簡單了解一下熔斷的工作原理。
簡單來講,熔斷就是把客戶端的每每一次服務調用用斷路器封裝起來,通過斷路器來監控每一次服務調用。如果某一段時間內,服務調用失敗的次數達到一定閾值,那么斷路器就會被觸發,后續的服務調用就直接返回,也就不會再向服務提供者發起請求了。
再來看下面這張圖,熔斷之后,一旦服務提供者恢復之后,服務調用如何恢復呢?這就牽扯到熔斷中斷路器的幾種狀態。
- Closed狀態:正常情況下,斷路器是處于關閉狀態的,偶發的調用失敗也不影響。
- Open狀態:當服務調用失敗次數達到一定閾值時,斷路器就會處于開啟狀態,后續的服務調用就直接返回,不會向服務提供者發起請求。
- Half Open狀態:當斷路器開啟后,每隔一段時間,會進入半打開狀態,這時候會向服務提供者發起探測調用,以確定服務提供者是否恢復正常。如果調用成功了,斷路器就關閉;如果沒有成功,斷路器就繼續保持開啟狀態,并等待下一個周期重新進入半打開狀態。
關于斷路器的實現,最經典也是使用最廣泛的莫過于Netflix開源的Hystrix了,下面我來給你介紹下Hystrix是如何實現斷路器的。
Hystrix的短路器也包含三種狀態:關閉、打開、半打開。Hystrix會把每一次服務調用都用HystrixCommand封裝起來,它會實時記錄每一次服務調用的狀態,包括成功、失敗、超時還是被線程拒絕。當一段時間內服務調用的失敗率高于設定的閾值后,Hystrix的斷路器就會進入進入打開狀態,新的服務調用就會直接返回,不會向服務提供者發起調用。再等待設定的時間間隔后,Hystrix的斷路器又會進入半打開狀態,新的服務調用又可以重新發給服務提供者了;如果一段時間內服務調用的失敗率依然高于設定的閾值的話,斷路器會重新進入打開狀態,否則的話,斷路器會被重置為關閉狀態。
其中決定斷路器是否打開的失敗率閾值可以通過下面這個參數來設定:
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
而決定斷路器何時進入半打開的狀態的時間間隔可以通過下面這個參數來設定:
HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
斷路器實現的關鍵就在于如何計算一段時間內服務調用的失敗率,那么Hystrix是如何做的呢?
答案就是下圖所示的滑動窗口算法,下面我來解釋一下具體原理。
Hystrix通過滑動窗口來對數據進行統計,默認情況下,滑動窗口包含10個桶,每個桶時間寬度為1秒,每個桶內記錄了這1秒內所有服務調用中成功的、失敗的、超時的以及被線程拒絕的次數。當新的1秒到來時,滑動窗口就會往前滑動,丟棄掉最舊的1個桶,把最新1個桶包含進來。
任意時刻,Hystrix都會取滑動窗口內所有服務調用的失敗率作為斷路器開關狀態的判斷依據,這10個桶內記錄的所有失敗的、超時的、被線程拒絕的調用次數之和除以總的調用次數就是滑動窗口內所有服務的調用的失敗率。
總結
微服務架構下服務調用失敗的幾種常見手段:超時、重試、雙發以及熔斷,實際使用時,具體選擇哪種手段要根據具體業務情況來決定。
根據我的經驗,大部分的服務調用都需要設置超時時間以及重試次數,當然對于非冪等的也就是同一個服務調用重復多次返回結果不一樣的來說,不可以重試,比如大部分上行請求都是非冪等的。至于雙發,它是在重試基礎上進行一定程度的優化,減少了超時等待的時間,對于長尾請求的場景十分有效。采用雙發策略后,服務調用的P999能大幅減少,經過我的實踐證明是提高服務調用成功率非常有效的手段。而熔斷能很好地解決依賴服務故障引起的連鎖反應,對于線上存在大規模服務調用的情況是必不可少的,尤其是對非關鍵路徑的調用,也就是說即使調用失敗也對最終結果影響不大的情況下,更加應該引入熔斷。
22 | 如何管理服務配置?
曾經的單體應用只需要管理一套配置;而拆分為微服務后,每一個系統都有自己的配置,并且都各不相同,而且因為服務治理的需要,有些配置還需要能夠動態改變,以達到動態降級、切流量、擴縮容等目的,這也是今天我要與你探討的,在微服務架構下服務配置如何管理的問題。
本地配置
服務配置管理最簡單的方案就是把配置當作代碼同等看待,隨著應用程序代碼一起發布。比如下面這段代碼用到了開源熔斷框架Hystrix,并且在代碼里定義了幾個配置,一個是線程的超時時間是3000ms,一個是熔斷器觸發的錯誤比率是60%。
@HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value="60")}
)
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{....
}
還有一種方案就是把配置都抽離到單獨的配置文件當中,使配置與代碼分離,比如下面這段代碼。
@HystrixCommand(commandKey = "inventory-by-productcode", fallbackMethod = "getDefaultProductInventoryByCode")
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{...
}
相應的配置可以抽離到配置文件中,配置文件的內容如下:
hystrix.command.inventory-by-productcode.execution.isolation.thread.timeoutInMilliseconds=2000
hystrix.command.inventory-by-productcode.circuitBreaker.errorThresholdPercentage=60
無論是把配置定義在代碼里,還是把配置從代碼中抽離出來,都相當于把配置存在了應用程序的本地。這樣做的話,如果需要修改配置,就需要重新走一遍代碼或者配置的發布流程,在實際的線上業務當中,這是一個很重的操作,往往相當于一次上線發布過程,甚至更繁瑣,需要更謹慎。
這時你自然會想,如果能有一個集中管理配置的地方,如果需要修改配置,只需要在這個地方修改一下,線上服務就自動從這個地方同步過去,不需要走代碼或者配置的發布流程,不就簡單多了嗎?沒錯,這就是下面要講的配置中心。
配置中心
配置中心的思路就是把服務的各種配置,如代碼里配置的各種參數、服務降級的開關甚至依賴的資源等都在一個地方統一進行管理。服務啟動時,可以自動從配置中心中拉取所需的配置,并且如果有配置變更的情況,同樣可以自動從配置中心拉取最新的配置信息,服務無須重新發布。
具體來講,配置中心一般包含下面幾個功能:
- 配置注冊功能
- 配置反注冊功能
- 配置查看功能
- 配置變更訂閱功能
1. 配置存儲結構
如下圖所示,一般來講,配置中心存儲配置是按照Group來存儲的,同一類配置放在一個Group下,以K, V鍵值對存儲。
2. 配置注冊
配置中心對外提供接口/config/service?action=register來完成配置注冊功能,需要傳遞的參數包括配置對應的分組Group,以及對應的Key、Value值。比如調用下面接口請求就會向配置項global.property中添加Key為reload.locations、Value為/data1/confs/system/reload.properties的配置。
curl "http://ip:port/config/service?action=register" -d "group=global.property&key=reload.locations&value=/data1/confs/system/reload.properties"
3. 配置反注冊
配置中心對外提供接口config/service?action=unregister來完成配置反注冊功能,需要傳遞的參數包括配置對象的分組Group,以及對應的Key。比如調用下面的接口請求就會從配置項global.property中把Key為reload.locations的配置刪除。
curl "http://ip:port/config/service?action=unregister"-d "group=global.property&key=reload.locations"
4. 配置查看
配置中心對外提供接口config/service?action=lookup來完成配置查看功能,需要傳遞的參數包括配置對象的分組Group,以及對應的Key。比如調用下面的接口請求就會返回配置項global.property中Key為reload.locations的配置值。
curl "http://ip:port/config/service?action=lookup&group=global.property&key=reload.locations"
5. 配置變更訂閱
配置中心對外提供接口config/service?action=getSign來完成配置變更訂閱接口,客戶端本地會保存一個配置對象的分組Group的sign值,同時每隔一段時間去配置中心拉取該Group的sign值,與本地保存的sign值做對比。一旦配置中心中的sign值與本地的sign值不同,客戶端就會從配置中心拉取最新的配置信息。比如調用下面的接口請求就會返回配置項global.property中Key為reload.locations的配置值。
curl "http://ip:port/config/service?action=getSign&group=global.property"
實際業務中,有哪些場景應用配置中心比較合適呢?下面我就結合自己的經驗,列舉幾個配置中心的典型應用場景,希望能給你一些啟發。
- 資源服務化。對于大部分互聯網業務來說,在應用規模不大的時候,所依賴的資源如Memcached緩存或者MCQ消息隊列的數量也不多,因此對應的資源的IP可以直接寫在配置里。但是當業務規模發展到一定程度后,所依賴的這些資源的數量也開始急劇膨脹。以微博的業務為例,核心緩存Memcached就有上千臺機器,經常會遇到個別機器因為硬件故障而不可用,這個時候如果采用的是本地配置的話,就需要去更改本地配置,把不可用的IP改成可用的IP,然后發布新的配置,這樣的過程十分不便。但如果采用資源服務化的話,把對應的緩存統統歸結為一類配置,然后如果有個別機器不可用的話,只需要在配置中心把對應的IP換成可用的IP即可,應用程序會自動同步到本機,也無須發布。
- 業務動態降級。微服務架構下,拆分的服務越多,出現故障的概率就越大,因此需要有對應的服務治理手段,比如要具備動態降級能力,在依賴的服務出現故障的情況下,可以快速降級對這個服務的調用,從而保證不受影響。為此,服務消費者可以通過訂閱依賴服務是否降級的配置,當依賴服務出現故障的時候,通過向配置中心下達指令,修改服務的配置為降級狀態,這樣服務消費者就可以訂閱到配置的變更,從而降級對該服務的調用。
- 分組流量切換。前面我提到過,為了保證異地多活以及本地機房調用,一般服務提供者的部署會按照IDC維度進行部署,每個IDC劃分為一個分組,這樣的話,如果一個IDC出現故障,可以把故障IDC機房的調用切換到其他正常IDC。為此,服務消費者可以通過訂閱依賴服務的分組配置,當依賴服務的分組配置發生變更時,服務消費者就對應的把調用切換到新的分組,從而實現分組流量切換。
開源配置中心與選型
講到這里,你可以根據我前面對配置中心的講解自己去實現一個配置中心,但其實對于大部分中小團隊來說,目前業界已經開源的配置中心實現可以說功能已經十分完善了,并且經過很多公司實際線上業務的充分論證,能滿足大多數業務的需求,所以我建議是盡量選擇成熟的開源配置中心實現,那么有哪些開源的配置中心可以使用呢?下面我就簡單介紹下三個典型的開源實現:
- Spring Cloud Config。Spring Cloud中使用的配置中心組件,只支持Java語言,配置存儲在git中,變更配置也需要通過git操作,如果配置中心有配置變更,需要手動刷新。
- Disconf。百度開源的分布式配置管理平臺,只支持Java語言,基于Zookeeper來實現配置變更實時推送給訂閱的客戶端,并且可以通過統一的管理界面來修改配置中心的配置。
- Apollo。攜程開源的分布式配置中心,支持Java和.Net語言,客戶端和配置中心通過HTTP長連接實現實時推送,并且有統一的管理界面來實現配置管理。
在實際選擇的時候,Spring Cloud Config作為配置中心的功能比較弱,只能通過git命令操作,而且變更配置的話還需要手動刷新,如果不是采用Spring Cloud框架的話不建議選擇。而Disconf和Apollo的功能都比較強大,在國內許多互聯網公司內部都有大量應用,其中Apollo對Spring Boot的支持比較好,如果應用本身采用的是Spring Boot開發的話,集成Apollo會更容易一些。
總結
關于業務中是否需要用到配置中心,以及選擇哪種配置中心,要根據實際情況而定,如果業務比較簡單,配置比較少并且不經常變更的話,采用本地配置是最簡單的方案,這樣的話不需要額外引入配置中心組件;相反,如果業務比較復雜,配置多而且有動態修改配置的需求的話,強烈建議引入配置中心來進行管理,而且最好做到配置變更實時推送給客戶端,并且可以通過統一的管理界面來管理配置,這樣的話能極大地降低運維的復雜度,減少人為介入,從而提高效率。
23 | 如何搭建微服務治理平臺?
單體應用改造為微服務架構后,服務調用從本地調用變成了遠程方法調用后,面臨的各種不確定因素變多了,一方面你需要能夠監控各個服務的實時運行狀態、服務調用的鏈路和拓撲圖;另一方面你需要在出現故障時,能夠快速定位故障的原因并可以通過諸如降級、限流、切流量、擴容等手段快速干預止損。這個時候就需要我今天要講的微服務治理平臺了。
微服務治理平臺的基本功能
微服務治理平臺就是與服務打交道的統一入口,無論是開發人員還是運維人員,都能通過這個平臺對服務進行各種操作,比如開發人員可以通過這個平臺對服務進行降級操作,運維人員可以通過這個平臺對服務進行上下線操作,而不需要關心這個操作背后的具體實現。
接下來我就結合下面這張圖,給你介紹一下一個微服務治理平臺應該具備哪些基本功能。
1. 服務管理
通過微服務治理平臺,可以調用注冊中心提供的各種管理接口來實現服務的管理。根據我的經驗,服務管理一般包括以下幾種操作:
- 服務上下線。當上線一個新服務的時候,可以通過調用注冊中心的服務添加接口,新添加一個服務,同樣要下線一個已有服務的時候,也可以通過調用注冊中心的服務注銷接口,刪除一個服務。
- 節點添加/刪除。當需要給服務新添加節點時候,可以通過調用注冊中心的節點注冊接口,來給服務新增加一個節點。而當有故障節點出現或者想臨時下線一些節點時,可以通過調用注冊中心的節點反注冊接口,來刪除節點。
- 服務查詢。這個操作會調用注冊中心的服務查詢接口,可以查詢當前注冊中心里共注冊了多少個服務,每個服務的詳細信息。
- 服務節點查詢。這個操作會調用注冊中心的節點查詢接口,來查詢某個服務下一共有多少個節點。
2. 服務治理
通過微服務治理平臺,可以調用配置中心提供的接口,動態地修改各種配置來實現服務的治理。根據我的經驗,常用的服務治理手段包括以下幾種:
- 限流。一般是在系統出現故障的時候,比如像微博因為熱點突發事件的發生,可能會在短時間內流量翻幾倍,超出系統的最大容量。這個時候就需要調用配置中心的接口,去修改非核心服務的限流閾值,從而減少非核心服務的調用,給核心服務留出充足的冗余度。
- 降級。跟限流一樣,降級也是系統出現故障時的應對方案。要么是因為突發流量的到來,導致系統的容量不足,這時可以通過降級一些非核心業務,來增加系統的冗余度;要么是因為某些依賴服務的問題,導致系統被拖慢,這時可以降級對依賴服務的調用,避免被拖死。
- 切流量。通常為了服務的異地容災考慮,服務部署在不止一個IDC內。當某個IDC因為電纜被挖斷、機房斷電等不可抗力時,需要把故障IDC的流量切換到其他正常IDC,這時候可以調用配置中心的接口,向所有訂閱了故障IDC服務的消費者下發指令,將流量統統切換到其他正常IDC,從而避免服務消費者受影響。
3. 服務監控
微服務治理平臺一般包括兩個層面的監控。一個是整體監控,比如服務依賴拓撲圖,將整個系統內服務間的調用關系和依賴關系進行可視化的展示;一個是具體服務監控,比如服務的QPS、AvgTime、P999等監控指標。其中整體監控可以使用服務追蹤系統提供的服務依賴拓撲圖,而具體服務監控則可以通過Grafana等監控系統UI來展示。
4. 問題定位
微服務治理平臺實現問題定位,可以從兩個方面來進行。一個是宏觀層面,即通過服務監控來發覺異常,比如某個服務的平均耗時異常導致調用失敗;一個是微觀層面,即通過服務追蹤來具體定位一次用戶請求失敗具體是因為服務調用全鏈路的哪一層導致的。
5.日志查詢
微服務治理平臺可以通過接入類似ELK的日志系統,能夠實時地查詢某個用戶的請求的詳細信息或者某一類用戶請求的數據統計。
6. 服務運維
微服務治理平臺可以調用容器管理平臺,來實現常見的運維操作。根據我的經驗,服務運維主要包括下面幾種操作:
- 發布部署。當服務有功能變更,需要重新發布部署的時候,可以調用容器管理平臺分批按比例進行重新部署,然后發布到線上。
- 擴縮容。在流量增加或者減少的時候,需要相應地增加或者縮減服務在線上部署的實例,這時候可以調用容器管理平臺來擴容或者縮容。
如何搭建微服務治理平臺
微服務治理平臺之所以能夠實現上面所說的功能,關鍵之處就在于它能夠封裝對微服務架構內的各個基礎設施組件的調用,從而對外提供統一的服務操作API,而且還提供了可視化的界面,以方便開發人員和運維人員操作。
根據我的經驗,一個微服務治理平臺的組成主要包括三部分:Web Portal層、API層以及數據存儲DB層,結合下面這張圖我來詳細講解下每一層該如何實現。
第一層:Web Portal。也就是微服務治理平臺的前端展示層,一般包含以下幾個功能界面:
- 服務管理界面,可以進行節點的操作,比如查詢節點、刪除節點。
- 服務治理界面,可以進行服務治理操作,比如切流量、降級等,還可以查看操作記錄。
- 服務監控界面,可以查看服務的詳細信息,比如QPS、AvgTime、耗時分布區間以及P999等。
- 服務運維界面,可以執行服務的擴縮容操作,還可以查看擴縮容的操作歷史。
第二層,API。也就是微服務治理平臺的后端服務層,這一層對應的需要提供Web Portal接口以調用,對應的一般包含下面幾個接口功能:
- 添加服務接口。這個接口會調用注冊中心提供的服務添加接口來新發布一個服務。
- 刪除服務接口。這個接口會調用注冊中心提供的服務注銷接口來下線一個服務。
- 服務降級/限流/切流量接口。這幾個接口會調用配置中心提供的配置修改接口,來修改對應服務的配置,然后訂閱這個服務的消費者就會從配置中心拉取最新的配置,從而實現降級、限流以及流量切換。
- 服務擴縮容接口。這個接口會調用容器平臺提供的擴縮容接口,來實現服務的實例添加和刪除。
- 服務部署接口。這個接口會調用容器平臺提供的上線部署接口,來實現服務的線上部署。
第三層,DB。也就是微服務治理平臺的數據存儲層,因為微服務治理平臺不僅需要調用其他組件提供的接口,還需要存儲一些基本信息,主要分為以下幾種:
- 用戶權限。因為微服務治理平臺的功能十分強大,所以要對用戶的權限進行管理。一般可以分為可瀏覽、可更改以及管理員三個權限。而且還需要對可更改的權限進行細分,按照不同服務的負責人進行權限劃分,一個人只能對它負責的服務的進行更改操作,而不能修改其他人負責的服務。
- 操作記錄。用來記錄下用戶在平臺上所進行的變更操作,比如降級記錄、擴縮容記錄、切流量記錄等。
- 元數據。主要是用來把服務在各個系統中對應的記錄映射到微服務治理平臺中,統一進行管理。比如某個服務在監控系統里可能有個特殊標識,在注冊中心里又使用了另外一個標識,為了統一就需要在微服務治理平臺統一進行轉換,然后進行數據串聯。
總結
可以說一個微服務框架是否成熟,除了要看它是否具備服務治理能力,還要看是否有強大的微服務治理平臺。因為微服務治理平臺能夠將多個系統整合在一起,無論是對開發還是運維來說,都能起到事半功倍的作用,這也是當前大部分開源微服務框架所欠缺的部分,所以對于大部分團隊來說,都需要自己搭建微服務治理平臺。不過好在微服務治理平臺本身的架構并不復雜,你可以根據自己的實際需要,來決定微服務治理平臺具備哪些功能。
24 | 微服務架構該如何落地?
在實際項目中,如何讓一個團隊把我們所學的微服務架構落地呢?
今天我就結合自己的經驗,定位在中小規模團隊,談談微服務架構到底該如何落地。
組建合適的技術團隊
微服務架構相比于單體應用來說復雜度提升了很多,這其中涉及很多組件,比如注冊中心、配置中心、RPC框架、監控系統、追蹤系統、服務治理等,每個組件都需要專門的人甚至專家把控才能hold住,不然微服務架構的落地就相當于空中樓閣,虛無縹緲。
想要落地微服務,首先需要合適的人,也就是組建一支合適的技術團隊。你一定很容易想到,是不是只有架構師適合做微服務架構的開發?一定程度上,這是合理的,因為微服務架構所涉及的具體技術,比如CAP理論、底層網絡可靠性保證、Netty高并發框架等,都對技術的深度要求比較高,一般有經驗的架構師才能掌握,所以這個技術團隊必須包含技術能力很強的架構師。但是還要考慮到微服務架構最后還是要落地到業務當中,既要滿足業務的需求,也要防止一種情況的發生,那就是全部由架構人員組成技術團隊,根據自己的設想,脫離了實際的業務場景,最后開發出來的架構中看不中用,業務無法實際落地,既打擊了團隊人員積極性,又對業務沒有實際價值,勞民傷財。所以這支技術團隊,也必須包含做業務懂業務的開發人員,只有他們了解業務的實際痛點以及落地過程中的難點,這樣才能保證最后設計出的微服務架構是貼合業務實際的,并且最后是能夠實際落地的。
從一個案例入手
當你的團隊決定要對業務進行微服務架構改造時,要避免一上來就妄想將整個業務進行服務化拆分、追求完美。這種想法是很危險的,一切的技術改造都應當以給業務創造價值為宗旨
,所以業務的穩定性要放在第一位,切忌好高騖遠。
正確的方法是首先從眾多業務中找到一個小的業務進行試點,前期的技術方案以滿足這個小的業務需求為準,力求先把這個小業務的微服務架構落地實施,從中發現各種問題并予以解決,然后才可以繼續考慮更大規模的推廣。這樣的話,即使微服務架構的改造因為技術方案不成熟,對業務造成了影響,也只是局限在一個小的業務之中,不會對整體業務造成太大影響。否則的話,如果因為微服務架構的改造給業務帶來災難性的后果,在許多技術團隊的決策者來看,可能微服務架構的所帶來的種種好處也不足以抵消其帶來的風險,最后整個微服務架構的改造可能就夭折了。
回想一下微博業務的微服務改造,從2013年開始進行微服務架構的研發,到2014年用戶關系服務開始進行微服務改造,再到2015年Feed業務開始進行微服務改造,從幾個服務上線后經過春晚流量的考驗后,逐步推廣到上百個服務的上線,整個過程持續了兩年多時間。雖然周期比較長,但是對于大流量的業務系統來說,穩定性永遠是在第一位的,業務架構改造追求的是穩步推進,中間可以有小的波折,但對整體架構的演進方向不會產生影響。
做好技術取舍
我在搭建微服務架構的時候,其實做的最多的工作就是技術取舍。比如在開發RPC框架的時候,是選擇自研呢還是采用開源RPC框架呢?如果自研的話,目前團隊系統的主要語言是Java,那么RPC框架是只支持Java語言就可以了,還是考慮到將來有可能需要支持其他語言呢?
我的經驗就是一切以業務的實際情況為準,只要滿足當前的需求就好,切忌好高騖遠,尤其是對于技術能力很強的開發者來說,很容易陷入對技術的完美追求,投入過多精力在架構的雕花工作上,而忽視了眼下業務最實際的需求。尤其是在團隊技術人力緊張,開發周期短的時候,更需要集中力量去滿足業務最迫切的需求。而對于架構的完善以及一些附加功能的追求,可以在后面業務落地后逐步進行完善。
以微博的服務化框架Motan為例,因為微博平臺的開發語言主要是Java,所以最早Motan只支持Java語言。從2017年開始,有了跨語言服務化調用的需求,才在此基礎上,對架構進行了升級,加入了對Go、PHP等語言的支持。而且在早期業務開始使用時,只開發了最基本的幾個核心組件如RPC框架、注冊中心和配置中心,以及簡單的監控系統,而服務追蹤系統、服務治理平臺這些高級的功能都沒有,后來隨著重要業務進行微服務改造的越來越多,不斷補充技術人力,才開始完善服務追蹤系統以及服務治理平臺。
除此之外,在做技術選型的時候,還要考慮到團隊的實際掌控能力,尤其是對一些新技術方案的引入要尤其慎重。如果沒有合適的人能夠掌控這些技術,那么貿然引入新技術,一旦業務受影響時,如果沒有人能有效干預,這對業務來說是災難性的后果。
微博在做注冊中心選型的時候,沒有選取當時很火的Zookeeper的一個重要原因就是,它底層依賴的是HBase存儲,當時團隊中還沒有有經驗的運維和開發人員;但團隊對Redis十分了解,所以基于Redis存儲,自研了一套注冊中心,完全能夠滿足需求,并且又沒有引入技術不可控因素。
采用DevOps
微服務架構帶來的不光是業務開發模式的改變,對測試和運維的影響也是根本性的。以往在單體應用架構時,開發只需要整體打包成一個服務,交給測試去做自動化測試、交給運維去部署發布就可以了。但是微服務架構下,一個單體應用被拆分成多個細的微服務,并且需要獨自開發、測試和上線,如果繼續按照之前的單體應用模式運維,那么測試和運維的工作量相當于成倍的增加。因此迫切需要對以往的開發、測試和運維模式進行升級,從我的經驗來看,最好的方案就是采用DevOps,對微服務架構進行一站式開發、測試、上線和運維。
在單體應用架構下,開發、測試和運維這三者角色的區分是十分比較明顯的,分屬于不同的部門。而在微服務架構下,由于服務被拆分得足夠細,每個服務都需要完成獨立的開發、測試和運維工作,有自己完整的生命周期,所以需要將一個服務從代碼開發、單元測試、集成測試以及服務發布都自動化起來。這樣的話,測試人員就可以從眾多微服務的測試中解放出來,著重進行自動化測試用例的維護;運維人員也可以從眾多微服務的上線發布工作中解放出來,著重進行DevOps體系工具的建設。而每個服務的開發負責人,需要對服務的整個生命周期負責,無論是在代碼檢查階段出現問題,還是測試階段和發布階段出現問題,都需要去解決。
統一微服務治理平臺
微服務架構下會衍生出許多新的問題,比如RPC調用超時、注冊中心獲取失敗、服務容量不足等,有些問題需要開發介入去定位分析,而有些問題需要運維介入,十分混亂。
微博在進行微服務改造初期,就面臨著諸多問題,比如某一個微服務的容量不足了,需要進行擴容,而它所依賴的服務也需要進行擴容,但這種依賴關系只有業務的開發人員清楚,運維人員其實并不知曉詳情。還有就是某個服務依賴的另一個服務出現故障,需要緊急降級,而此時如果運維人員操作的話并不知道哪個開關,雖然開發知曉,但開發實際上又沒有線上服務器的操作權限。
所以,這時就迫切需要一個微服務治理平臺,能夠將微服務的服務治理以及各種運維操作都統一管理起來,并且打破開發和運維之間的隔閡,給予同樣的權限,讓服務的開發人員真正做到對自己的服務負責,不僅要對自己的服務情況了如指掌,還需要能對自己的服務進行治理和運維。
需要開發和運維深入合作,發揮各自專業的特長,將微服務治理的功能以及之前運維系統的基礎功能結合在一起,打造成“一站式”微服務治理平臺。
總結
總結來講就是,首先你必須組建一支合適的技術團隊,這其中不僅要包含資深的架構師,還需要包含業務的開發者。在選擇業務進行微服務架構改造時,不能追大求全,正確的做法應當是先以一個適當規模的業務進行微服務改造,走完整個微服務架構落地的過程,從而找出問題,不斷打磨到成熟可用的狀態,再推廣到更多更重要的業務當中。在改造的過程中,要做好技術取舍,以團隊人員的實際情況以及業務的實際需求為準繩,切忌追新立異,避免給業務引入不可控因素,留下“架構債”。同時,微服務架構的過程,也是團隊組織變革的過程,傳統意義上的開發、測試和運維明確的分割線會被打破,出現一種DevOps工程師的角色,他需要對服務全生命周期負責。為了做到這一點,就需要一個統一的微服務治理平臺,融合服務治理和運維的各種功能。
實際上,每個團隊都有各自不同的情況,但只要秉承上面這些基本準則,就可以走出一條適合自己團隊的微服務架構路線出來,這其中沒有高低之分,適合自己的才是最好的。