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; }
这样就能够根据特定接口/参数值业务本身决定是否须要将请求发送到相同节点了。