在dubbo系列的上一篇我們介紹了dubbo中的幾個高級配置,本篇我們繼續介紹dubbo中的高級配置。
負載均衡
在生產環境中,每個服務通常爲一個集羣,則需要進行負載均衡。
dubbo中的負載均衡算法
dubbo中內置了四種負載均衡算法。
- random:隨機算法,是默認的負載均衡策略。
- roundrobin:輪詢算法。按照權重進行訪問,權重設置在提供者端,數值越大,權重越大。
- leastactive:最少活躍度算法
- consistenthash:一致性hash算法。
在dubbo中對於方法的負載均衡設置可以通過在提供者的<dubbo:service>標籤中設置,也可以在消費者的<dubbo:reference>標籤中設置,如下所示。
<dubbo:service interface="com.abc.service.SomeService"
ref="oldService" loadbalance="roundrobin"/>
<dubbo:service interface="com.abc.service.SomeService"
ref="oldService" loadbalance="roundrobin"/>
<!-- 在消費者方指定負載均衡算法,可以在某一個service上,也可以在service中具體的method上指定 -->
<dubbo:reference id="someService" interface="com.abc.service.SomeService">
<dubbo:method name="hello" loadbalance="random"/>
<dubbo:method name="world" loadbalance="leastactive"/>
</dubbo:reference>
在使用一致性hash,即consistenthash輪詢算法時需要注意,其餘方法參數有關,相同的參數會被分配到相同的提供者;多個參數,對第一個參數進行hash操作。如果方法沒有參數,則不能使用一致性hash算法。
dubbo中的集羣容錯
集羣容錯指的是,當消費者調用提供者集羣時發生異常的處理方案。在dubbo中,內置了6種集羣容錯方式。
Failover
Failover是故障轉移機制,當消費者服務調用提供者服務某個服務器無法響應時,其會嘗試調用同服務的其他服務器,適用於讀操作。比如,消費者調用提供者從數據庫中讀取數據,可以設置retires來決定其重試次數,重試次數不包含第一次的正常調用。當第一次讀取數據失敗時,可以進行重試,嘗試調用其他服務器服務,進行故障的轉移。
Failfast
Failfast即快速失敗機制,當服務消費者調用服務提供者時,服務無法正確相應時,則立即報錯。通常適用於非冪等性的寫操作,例如新增一條數據記錄。
ps:所謂冪等性,每次發出請求對服務端產生的狀態影響是冪等的,任意次執行對資源本身所產生的影響和執行一次是相同的。
Failsafe
Failsafe即失敗安全機制,當服務消費者調用服務提供者時,服務無法正確響應時,直接忽略本次消費請求。通常適用於不太重要的服務,如日誌寫入操作等。
Failback
Failback即失敗自動回覆機制,當服務消費者調用服務提供者,服務未正常相應,dubbo會記錄本次失敗記錄,後面定時自動重新發送該請求。通常適用於對實時性要求不高的服務,如消息的通知等。
Forking
Foring即並行機制,消費者會同時調用服務提供者的多臺服務器,當有一臺服務器返回結果,即調用成功。通常適用於對實時性要求高的操作,但會造成服務器資源的浪費。
Broadcast
Broadcast即廣播機制,會逐個調用服務提供者,若有一個出現失敗則會報錯。通常適用於通知所有提供者更新緩存、日誌等信息時使用。
在dubbo中,默認的集羣容錯方式爲故障轉移機制(Failover),可以通過在服務提供者端的<dubbo:service>或者服務消費者端的<dubbo:reference>中添加cluster參數進行更改。
服務降級
何爲服務降級
服務降級,當服務器壓力劇增的情況下,根據當前業務情況以及流量對一些服務有策略的降低服務級別,釋放更多的服務器資源,保證核心業務的正常運行。例如:在雙十一0點-2點期間,淘寶不允許用戶查看歷史訂單;在晚上11點至次日7點,12306停止售票服務。這都是典型的服務降級。
服務降級的方式
能夠實現服務降級的方式由很多,常見的方式由以下幾種情況:
- 部分服務暫停。比如雙十一期間淘寶的不允許用戶查看歷史訂單,就屬於暫停了這部分業務。
- 部分服務延遲。
- 全部服務暫停。典型的代表就是12306的夜間服務停止操作。
- 隨機拒絕服務。
服務降級與mock機制
在dubbo中服務降級採用的爲mock機制,具有兩種降級處理方式:Mock Null降級處理與Class Mock降級處理。在適用場景方面,Mock Null降級處理適用於沒有返回值的情況,Class Mock降級處理則適用於沒有返回值的情況。
在測試服務降級時,要模擬服務提供失敗的情況,用不啓動服務提供者的方式來模擬,只需要修改消費者端即可。Dubbo對於存在返回值和無返回值的服務降級處理方式不同,定義如下所示接口,包含一個有返回值的方法和一個無返回值的方法。
public interface UserService {
String getUsernameById(int id);
void addUser(String username);
}
修改消費者端<dubbo:reference>標籤,添加mock屬性,指定服務降級的方式,這裏使用 Mock Null 服務降級處理方式。
<dubbo:reference id="userService" check="false" mock="return null"
interface="com.abc.service.UserService"/>
consumer的運行示例如下所示。mock情況下,消費者調用有返回值的方法時,返回結果爲null,而沒有返回值的方法則沒有任何顯示。
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-consumer.xml");
UserService service = (UserService) ac.getBean("userService");
System.out.println("開始執行");
// 有返回值的方法降級結果爲null
String usernameById = service.getUsernameById(3);
System.out.println(usernameById);
// 無返回值的方法降級結果是無任何顯示
service.addUser("China");
System.out.println("執行結束");
}
通過上面的示例可以看到,使用mock null時,有返回值的方法只會返回null,而沒有返回值的方法則沒有任何顯示,很不又要的樣子。Class Mock的服務降級解決了這兩個問題,在Mock Null的基礎上進行改造,在UserService接口同目錄下定義一個UserServiceMock,這裏的Mock Class的命名方法需要滿足以下規則:業務接口簡單類名 + Mock。該類要實現業務接口, 而方法的內容即爲服務降級後要要執行的代碼邏輯,如下所示。
public class UserServiceMock implements UserService {
@Override
public String getUsernameById(int id) {
return "沒有該用戶:" + id;
}
@Override
public void addUser(String username) {
System.out.println("添加該用戶失敗:" + username);
}
}
修改消費者端<dubbo:reference>標籤,如下所示。
<dubbo:reference id="userService" check="false" mock="true"
interface="com.abc.service.UserService"/>
consumer的運行示例與Mock Null相同,運行結果後如下所示。
沒有該用戶:3
添加該用戶失敗:China
服務限流
在dubbo中同樣提供了服務限流的兩種方法:直接限流和間接限流。直接限流,通過對連接的數量直接限制來達到限流的目的,超時限制則會讓消費者等待,直到等待超時,或者獲取到服務。間接限流,通過一些非連接數量設置的間接手段來達到限流的目的
直接限流
executes限流
該屬性僅能設置在提供者端。可以設置爲接口級別,也可以設置爲方法級別。
<!-- 限制當前接口中的每個方法併發連接數不超過10 -->
<dubbo:service interface="com.abc.service.SomeService"
ref="zhifubaoService" executes="10"/>
<!-- 限制當前接口中hello方法服務併發數量不超過10 -->
<dubbo:service interface="com.abc.service.SomeService"
ref="weixinService">
<dubbo:method name="hello" executes="10"/>
</dubbo:service>
accepts限流
該屬性僅可設置在提供者端的dubbo:protocol/與dubbo:provider/標籤中,用於對指定協議連接數的限制。前者基於協議連接數量限定的,而後者是針對服務本身在使用某種協議時最多接受多少個消費者連接。
<!-- 限制當前接口在dubbo協議下併發連接數不超過10 -->
<dubbo:protocol name="dubbo" port="20880" accepts="10"/>
<dubbo:provider protocol="dubbo" accepts="10"/>
actives限流
actives限流方式既可以在提供者方設置也可以在消費者方設置,同時消費者調用的長連接、短連接意義有所不同。
- 在提供者方設置時,若客戶端與當前服務的連接爲長連接,則表示當前服務的一個長連接可以並行處理請求的數目;若客戶端與當前服務的連接爲短連接時,則表示當前服務可以同時處理的短連接的數量。
- 在消費者方設置時,若連接爲長連接,則表示當前客戶端一個長連接能夠併發提交請求的個數,並沒有長連接數目的限制;若連接爲短連接,則表示當前客戶端能夠同時提交的短連接的數目。
<!-- 限制當前接口中的每個接口的併發執行能力不超過10 -->
<dubbo:service interface="com.abc.service.SomeService"
ref="zhifubaoService" actives="10"/>
<!-- 限制當前接口中hello方法併發數量不超過10 -->
<dubbo:service interface="com.abc.service.SomeService"
ref="weixinService">
<dubbo:method name="hello" actives="10"/>
</dubbo:service>
<!-- 限制當前接口中的每個方法提交的請求不超過10 -->
<dubbo:reference interface="com.abc.service.SomeService" id="zhifubaoService" actives="10"/>
<!-- 限制當前接口中hello方法提交的連接數不超過10 -->
<dubbo:reference interface="com.abc.service.SomeService" id="weixinService">
<dubbo:method name="hello" actives="10"/>
</dubbo:reference>
connections限流
connections限流與actives限流效果大體相同,同樣可以設置在服務提供者端和服務消費者端。connnections無論設置在消費者端還是提供者端,無論是長連接還是短連接,其都表示最多支持的連接個數。
間接限流
上面所說的直接限流是通過直接限制服務的連接數來達到效率的目的;而間接限流則是通過一些非連接數量設置的間接手段來達到限流的目的。
延遲連接
延遲連接用於減少長連接數。只有當消費者端的調用發起時,才創建長連接,所以其是設置在消費者端的。由於其僅作用於長連接,所以其僅對於 Dubbo 服務暴露協議生效。
注意,該屬性僅可設置在消費者端的dubbo:reference/與dubbo:consumer/標籤 中,且也不能設置爲方法級別。
<!-- 設置當前消費者在該接口上方法均採用延遲連接 -->
<dubbo:reference id="userService" check="false" layer="true"
interface="com.abc.service.UserService"/>
<dubbo:consumer layer="true"/>
粘連連接
粘連連接限制的不是流量,而是流向。所謂粘連連接是指,儘可能讓客戶端總是向同一提供者發起調用,除非該提供者掛了,再連另一臺。粘滯連接將自動開啓延遲連接,以減少長連接數。粘連連接僅能設置在消費者端,其可以設置爲接口級別,也可以設置爲方法級別。
<!-- 設置當前消費者在該接口上方法均採用延遲連接 -->
<dubbo:reference id="userService" check="false" sticky="true"
interface="com.abc.service.UserService"/>
<dubbo:reference id="userService" check="false"
interface="com.abc.service.UserService">
<dubbo:method name="hello" sticky="true" />
</dubbo:reference>
負載均衡
若將負載均衡策略設置爲leastactive的方式,可以使消費者調用併發數量最少的提供者,來達到限流的目的。所以,該方式限制的也不是流量,而是流向。當然,可以設置在消費者端,亦可設置在提供者端;可以設置在接口級別,亦可設置在方法級別。
<dubbo:service interface="com.abc.service.SomeService"
ref="zhifubaoService" loadbalance="leastactive"/>
聲明式緩存
爲了進一步提高消費者對用戶的響應速度,減輕提供者的壓力,Dubbo 提供了基於結果 的聲明式緩存。該緩存是基於消費者端的,所以使用很簡單,只需修改消費者配置文件,與提供者無關。
該緩存是緩存在消費者端內存中的,一旦緩存創建,即使提供者宕機也不會影響消費者 端的緩存。
由上面的描述可以總結出聲明式緩存適合使用的場景:對於不經常改版或者根本不改變的數據適合使用聲明式緩存,如字典項和菜單數據。僅修改消費者配置文件,如下所示,添加cache="true"的標籤。
<dubbo:reference id="someService" cache="true"
interface="com.abc.service.SomeService"/>
修改RunConsumer類內容如下,四次調用service的hello方法,分別輸出Tom和Jerry。
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-consumer.xml");
SomeService service = (SomeService) ac.getBean("someService");
System.out.println(service.hello("Tom"));
System.out.println(service.hello("Jerry"));
System.out.println(service.hello("Tom"));
System.out.println(service.hello("Jerry"));
執行上述代碼後,得到如下結果。可以發現服務提供者端只輸出了一次Tom和Jerry的調用結果,這就是神明是緩存的作用。緩存在消費者端的內存中創建,再次獲取則將從內存中讀取。
執行提供者的hello() Tom
執行提供者的hello() Jerry
若一個接口中只有部分方法需要緩存,則可使用方法級別的緩存。相對於此,前面的緩
存稱爲服務級別緩存。僅需修改配置文件。
默認緩存 1000 個結果
聲明式緩存中可以緩存多少個結果呢? 在聲明式緩存中,默認可以緩存1000個結果。若超出1000,將採用“最近最少使用策略”,即LRU策略來刪除緩存,以保證最熱的數據被緩存。注意,該刪 除緩存的策略不能修改。下面我們來測試一下緩存1000個的例子,修改RunConsumer類內容如下。
ApplicationContext ac =
new ClassPathXmlApplicationContext("spring-consumer.xml");
SomeService service = (SomeService) ac.getBean("someService");
// 1000次不同的消費結果,將佔滿1000個緩存空間
for (int i=1; i<=1000; i++) {
service.hello("i==" + i);
}
// 第1001次不同的消費結果,會將第1個緩存內容擠出去
System.out.println(service.hello("Tom"));
// 本次消費會從調用提供者,說明原來的第1個緩存的確被擠出去了
// 本次消費結果會將原來("i==2")的緩存結果擠出去
System.out.println(service.hello("i==1"));
// 本次消費會直接從緩存中獲取
System.out.println(service.hello("i==3"));
在上面的代碼中已經做了較爲詳細的註釋,運行測試,得到如下結果。從結果中可以看到實驗現象確實如註釋所示。
執行提供者的hello() i==997
執行提供者的hello() i==998
執行提供者的hello() i==999
執行提供者的hello() i==1000
執行提供者的hello() Tom
執行提供者的hello() i==1
多註冊中心
在生產環境中,我們的一個服務可能需要在不同的機房中發佈,而不同的機房所用的註冊中心不同,這就是多註冊中心的應用場景。
服務暴露延遲
如果我們的服務啓動過程需要 warmup 事件,就可以使用 delay 進行服務延遲暴露。
只需在服務提供者的dubbo:service/標籤中添加 delay 屬性。其值可以有三類:
- 若爲正數,則單位爲毫秒,表示在提供者對象創建完畢後的指定時間後再發布服務;
- 默認值爲 0,表示當前提供者創建完畢後馬上暴露服務。
- 若值爲-1,則表示在 Spring 容器初始化完畢後再暴露服務。
對於默認值爲0和值爲-1的時候,大家可能會有所混淆。默認值爲0時,當服務提供者被實例化後,馬上就會暴露服務;而當值爲-1的時候,需要等待所有的singletype類型的bean創建完成後統一進行暴露。