Dubbo的集羣容錯策略spring
正常狀況下,當咱們進行系統設計時候,不只要考慮正常邏輯下代碼該如何走,還要考慮異常狀況下代碼邏輯應該怎麼走。當服務消費方調用服務提供方的服務出現錯誤時候,Dubbo提供了多種容錯方案,缺省模式爲failover,也就是失敗重試。緩存
Dubbo提供的集羣容錯模式安全
下面看下Dubbo提供的集羣容錯模式:服務器
Failover Cluster:失敗重試併發
當服務消費方調用服務提供者失敗後自動切換到其餘服務提供者服務器進行重試。這一般用於讀操做或者具備冪等的寫操做,須要注意的是重試會帶來更長延遲。可經過 retries="2" 來設置重試次數(不含第一次)。app
接口級別配置重試次數方法 <dubbo:reference retries="2" /> ,如上配置當服務消費方調用服務失敗後,會再重試兩次,也就是說最多會作三次調用,這裏的配置對該接口的全部方法生效。固然你也能夠針對某個方法配置重試次數以下:負載均衡
<dubbo:reference> <dubbo:method name="sayHello" retries="2" /> </dubbo:reference>
Failfast Cluster:快速失敗dom
當服務消費方調用服務提供者失敗後,當即報錯,也就是隻調用一次。一般這種模式用於非冪等性的寫操做。ide
Failsafe Cluster:失敗安全ui
當服務消費者調用服務出現異常時,直接忽略異常。這種模式一般用於寫入審計日誌等操做。
Failback Cluster:失敗自動恢復
當服務消費端用服務出現異常後,在後臺記錄失敗的請求,並按照必定的策略後期再進行重試。這種模式一般用於消息通知操做。
Forking Cluster:並行調用
當消費方調用一個接口方法後,Dubbo Client會並行調用多個服務提供者的服務,只要一個成功即返回。這種模式一般用於實時性要求較高的讀操做,但須要浪費更多服務資源。可經過 forks="2" 來設置最大並行數。
Broadcast Cluster:廣播調用
當消費者調用一個接口方法後,Dubbo Client會逐個調用全部服務提供者,任意一臺調用異常則此次調用就標誌失敗。這種模式一般用於通知全部提供者更新緩存或日誌等本地資源信息。
如上,Dubbo自己提供了豐富的集羣容錯模式,可是若是您有定製化需求,能夠根據Dubbo提供的擴展接口Cluster進行定製。在後面的消費方啓動流程章節會講解什麼時候/如何使用的集羣容錯。
失敗重試策略實現分析
Dubbo中具體實現失敗重試的是FailoverClusterInvoker類,這裏咱們看下具體實現,主要看下doInvoke代碼:
public Result doInvoke(Invocation invocation,final List<Invoker<T>> invokers,LoadBalance loadbalance) throws RpcException{ // (1) 全部服務提供者 List<Invoker<T>> copyinvokers = invokers; checkInvokers(copyinvokers,invocation); // (2)獲取重試次數 int len = getUrl().getMethodParameter(invocation.getMethodName(),Constants.RETRIES_KEY,Constants.DEFAULT_RETRIES) + 1; if(len <= 0){ len = 1; } // (3)使用循環,失敗重試 RpcException le = null; // last exception List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); Set<String> providers = new HashSet<String>(); for(int i=0;i<len;i++){ // 重試時,進行從新選擇,避免重試時invoker列表已發生變化 // 注意:若是列表發生了變化,那麼invoked判斷會失效,由於invoker示例已經改變 if(i > 0){ // (3.1) checkWhetherDestroyed(); // 若是當前實例已經被銷燬,則拋出異常 // (3.2) 從新獲取全部服務提供者 copyinvokers = list(invocation); // (3.3) 從新檢查一下 checkInvokers(copyinvokers,invocation); } // (3.4) 選擇負載均衡策略 Invoker<T> invoker = select(loadbalance,invocation,copyinvokers,invoked); invoked.add(invoker); RpcContext.getContext().setInvokers((List)invoked); // (3.5) 具體發起遠程調用 try{ Result result = invoker.invoke(invocation); if(le != null && logger.isWarnEnabled()){ ... } return result; }catch(RpcException e){ if(e.isBiz()){ // biz exception throw e; } le = e; }catch(Throwable e){ le = new RpcException(e.getMessage(),e); }finally{ providers.add(invoker.getUrl().getAddress()); } } throw new RpcException("拋出異常..."); }
Dubbo的負載均衡策略
當服務提供方是集羣的時候,爲了不大量請求一直落到一個或幾個服務提供方機器上,從而使這些機器負載很高,甚至打死,須要作必定的負載均衡策略。Dubbo提供了多種均衡策略,缺省爲random,也就是每次隨機調用一臺服務提供者的機器。
Dubbo提供的負載均衡策略
在spring boot dubbo中,它能夠經過參數spring.dubbo.reference.loadbalance=consistentHash啓用,可是一致性哈希有一個並非很合理的地方,它是將相同參數的請求發送到相同提供者,默認爲第一個參數。若是全部接口的第一個參數爲公共入參還好,若是是採用泛型或繼承設計,這就比較悲劇了,咱們就遇到這個問題。分析源碼,可知它是調用com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance.ConsistentHashSelector#select生成的,以下:
public Invoker<T> select(Invocation invocation) { String key = toKey(invocation.getArguments()); byte[] digest = md5(key); Invoker<T> invoker = sekectForKey(hash(digest, 0)); return invoker; } private String toKey(Object[] args) { StringBuilder buf = new StringBuilder(); for (int i : argumentIndex) { if (i >= 0 && i < args.length) { buf.append(args[i]); } } return buf.toString(); }
因此,咱們徹底能夠經過將toKey暴露給業務自定義實現,以下:
public Invoker<T> select(Invocation invocation) { String key = toKey(invocation.getArguments()); // zjhua 自定義一致性哈希判斷用的key,用於指定請求發送到特定節點 if (rpcLoadBalance == null) { rpcLoadBalance = SpringContextHolder.getBean(RpcLoadBalance.class); } if (rpcLoadBalance != null) { key = rpcLoadBalance.toKey(invocation,invocation.getArguments()); } byte[] digest = md5(key); Invoker<T> invoker = sekectForKey(hash(digest, 0)); return invoker; }
這樣就能夠根據特定接口/參數值業務本身決定是否須要將請求發送到相同節點了。