Dubbo学习笔记7:Dubbo的集群容错与负载均衡策略

Dubbo的集群容错策略

正常情况下,当我们进行系统设计时候,不仅要考虑正常逻辑下代码该如何走,还要考虑异常情况下代码逻辑应该怎么走。当服务消费方调用服务提供方的服务出现错误时候,Dubbo提供了多种容错方案,缺省模式为failover,也就是失败重试。

Dubbo提供的集群容错模式

下面看下Dubbo提供的集群容错模式:

Failover Cluster:失败重试

当服务消费方调用服务提供者失败后自动切换到其他服务提供者服务器进行重试。这通常用于读操作或者具有幂等的写操作,需要注意的是重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。

接口级别配置重试次数方法 <dubbo:reference retries="2" /> ,如上配置当服务消费方调用服务失败后,会再重试两次,也就是说最多会做三次调用,这里的配置对该接口的所有方法生效。当然你也可以针对某个方法配置重试次数如下:

<dubbo:reference>
    <dubbo:method name="sayHello" retries="2" />
</dubbo:reference>

Failfast Cluster:快速失败

当服务消费方调用服务提供者失败后,立即报错,也就是只调用一次。通常这种模式用于非幂等性的写操作。

Failsafe Cluster:失败安全

当服务消费者调用服务出现异常时,直接忽略异常。这种模式通常用于写入审计日志等操作。

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("抛出异常...");
}
  • 如上代码(2)从url参数里面获取设置的重试次数,如果用户没有设置则取默认的值,默认是重试2,这里需要注意的是代码(2)是获取配置重试次数又+1了。这说明 总共调用次数=重试次数+1 (1是正常调用)。
  • 代码(3)循环重复试用,如果第一次调用成功则直接跳出循环返回,否则循环重试。第一次调用时不会走代码(3.1)(3.2)(3.3)。如果第一次调用出现异常,则会循环,这时候i=1,所以会执行代码(3.1)检查是否有线程调用了当前ReferenceConfig的destroy()方法,销毁了当前消费者。如果当前消费者实例已经被消费,那么重试就没有意义了,所以会抛出RpcException异常。
  • 如果当前消费者实例没被销毁,则执行代码(3.2)重新获取当前服务提供者列表,这是因为从第一次调开始到线程可能提供者列表已经变化了,获取列表后,然后执行(3.2)又一次进行了校验。校验通过则执行(3.4),根据负载均衡策略选择一个服务提供者,再次尝试调用。负载均衡策略的选择下节会讲解。

猜你喜欢

转载自www.cnblogs.com/xhj123/p/9087532.html