Dubbo的启动检查、容错与负载均衡(二)

这篇博客探讨了Dubbo的启动检查、容错机制和负载均衡策略。启动检查确保服务在调用前可用,容错机制通过Failover、Failfast、Failsafe、Failback和Forking集群策略应对服务异常。负载均衡策略包括Random、RoundRobin和LeastActive,以及适用于分布式缓存的一致性哈希策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、参考

启动检查:http://dubbo.apache.org/en-us/docs/user/demos/preflight-check.html

容错:http://dubbo.apache.org/en-us/docs/user/demos/fault-tolerent-strategy.html

负载均衡:http://dubbo.apache.org/en-us/docs/user/demos/loadbalance.html

Dubbo中的Consumer端通过网络调用接口在Provider端的具体实现或者说是服务。因为网络是不稳定、不可靠的,另外Provider端的服务也可能因为各种其它原因变得不可用,Consumer端自然需要适当的处理这种情况。

在大的方面,Dubbo提供了两种机制,一种是事前检查:Provider是否可用,另一种是事后容错:发生Rpcexception时怎么办。

2、启动检查

事前检查也称为启动检查,意思是在Consumer端真正的调用Provider端具体实现之前,先检查一下Provider端是不是在线(网络是不是通的?Provider中的服务是否启动等),如果不在线则抛出异常。这样可以提前发现问题,避免Consumer端的任务进行到中途却发现某个服务不可用。

与启动检查相关的配置主要是在Consumer端:

<dubbo:consumer check = "false" />

这个标签是Consumer端引用的所有服务的缺省配置。

<dubbo:reference interface = "com.foo.BarService" check = "true" />

这个是某个具体服务的配置,这里是true,优先级要高一些,会覆盖掉<dubbo:consumer check="false"/>。

假如有如下Consumer端代码:

public class Consumer {
 
	public static void main(String[] args) {
		@SuppressWarnings("resource")
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
				new String[] { "META-INF/spring/dubbo-demo-consumer.xml" });
		context.start();
		// Obtaining a remote service proxy
		DemoService demoService = (DemoService) context.getBean("demoService");
		// Executing remote methods
		String hello = demoService.sayHello("world");
		// Display the call result
		System.out.println(hello);
	}
}

如果配置成true或者缺省:

<dubbo:reference id="demoService" check="true"
		interface="org.apache.dubbo.demo.DemoService" />

如果在启动运行Consumer时Provider不可用,则在执行代码:DemoService demoService = (DemoService) context.getBean("demoService");
时就会报错,并且返回一个NULL。

如果改成如下配置:

<dubbo:reference id="demoService" check="false"
		interface="org.apache.dubbo.demo.DemoService" />

则在执行代码:DemoService demoService = (DemoService) context.getBean("demoService");
时不会报错,并且返回一个非空引用,Dubbo在后台会试图使这个非空引用变得真正有效。

如果在调用代码:String hello = demoService.sayHello("world");
时,也就是真正的调用Provider端代码时,如果服务还是不可用,这个时候才会报错,当然调用过程实际会包含后边说的容错逻辑。

我理解启动检查意义不是太大。启动检查时服务是可用的,但真正使用服务还是不能保证它仍然是可用的,网络服务本身它就不稳定,在调用它之前实际上没有办法确认它到底可用还是不可用,相对有效的处理方法是事后容错。

3、容错机制

可以想办法降低错误发生的概率,但是无论如何,错误的发生是不可避免的,所谓容错机制,就是错误发生后怎么办,如何把这个错误尽量修正过来。

实现容错的核心是多实例部署并组成集群,一个实例出错,就让另一个实例顶上去。

cluster

上图来自Dubbo官网,大概是它容错机制的概念图。首先要有一个基本的认识,Dubbo中的容错机制主要运行在Consumer端。Provider端可以提供多个服务实例(注册到Registry中),当服务实例的个数或者配置有变化时,Registry可以主动推送消息通知Consumer端。至于Consumer端如何使用多个实例、如何容错、如何负载均衡那就是Consumer端自己的事情。所以上图中的概念主要是Consumer端的概念。

各节点含义如下:

  • Invoker:一个Invoker代表一个可调用的服务实例,它内部包括服务的地址、接口。有了这两个东西,Consumer端就知道如何定位这个服务实例了。在多实例部署中,将包含多个这样的Invoker。
  • Directory:这个东西代表多个Invoker实例的列表。Registry推送一些变更消息,因此Consumer端的这个东西是动态变化的。
  • Cluster将Directory中的多个物理的Invoker伪装成一个逻辑上的接口,容错逻辑主要在Cluster中实现。我们在写Consumer端代码时会调用某个接口,其实这个接口是一个Invoker集群。
  • Router:路由器,根据各种条件对Cluster进行一次过滤。简单的例子,如果某个VIP用户在调用服务,Router可以识别到这是VIP用户,应当尽量从Cluster中选择专用的、高性能的Invoker为用户服务。反之如果是普通用户,那就选择普通的Invoker为用户服务。大概理解就是条件过滤。
  • LoadBalance:负载均衡算法,从Router过滤后的多个Invoker中选一个出来并调用之。

3.1、Failover Cluster

默认容错策略,简单说就是Cluster中有至少一个可用Provider,如果一个失败,就换一个。一般多应用于读操作场景,因为多次读操作不会产生副作用。

Dubbo默认实现的重试机制比较简单,只要Rpc调用抛出RpcException异常,那么就会发起重试。实际上RpcException有很多具体类型,它的触发原因很多,有网络拥堵超时、网络被管理员禁止、Provider端代码异常、Consumer端本地异常等。细一点的话应该是根据不同的触发原因,采用不同的重试算法(是否有必要重试?重试间隔是否需要逐渐拉大等),应该根据具体业务扩展一下Dubbo。如果使用不当,重试机制可能会白白浪费资源,甚至在高负载时进步引起性能恶化。

Dubbo会记住失败的Invoker,并在重试时尽量将失败的Invoker排除在外。如果在重试时发现除了失败的invoker外,没有其它Invoker了,那么也就只好再一次调用失败过的Invoker了。

重试次数可以配置,默认是2,第一次不算重试。也就是说如果重试次数是2,那么总共可以发起三次调用。如果是小于等于0,那就是只调用一次,不进行重试。重试次数可以在多个地方配置,优先级由低到高:

<dubbo:service retries="2" />

这个是provider端配置。

<dubbo:reference retries="2" />

这个是Consumer端在引用服务时的配置。

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

这个是在Consumer端为某个服务中的具体方法配置重试次数,相对优先级最高。

总结:失败重试模式是最常用、最重要的容错机制。

3.2、Failfast Cluster

只调用一次,不重试,如果调用失败则立即抛出异常,多用于非幂等的写操作。配置如下:

<dubbo:service cluster="failfast" />
<dubbo:reference cluster="failfast" />

如果一个Service中幂等与非幂等操作都有,有的需要重试、有的不需要,Failfast Cluster似乎无法应对这种情况,不如Failover Cluster灵活,因为后者可以将不能重试的非幂等操作的重试次数设置成0,这样就相当于Failfast Cluster效果。

总结:不够灵活,完全可用Failover Cluster代替。这真是个多余的玩意,就是不重试、重试次数为零,起了一个“快速失败”的名而已。

3.3、Failsafe Cluster

配置与Failfast Cluster类似,只需把前者中的"failfast"替换成"failsafe”即可,本质上就是忽略Rpc调用过程中发生的异常,多用于能够容忍失败的操作中,如审计日志的提交。这个名字起的也很奇怪,就是忽略异常,为什么要叫成“失败安全”,叫Failignore好像更直观。

总结:没什么用处。假如在实际中遇到审计日志提交这种不十分在意失败的操作,直接用Failover Cluster,将方法的重试次数设置成0。然后在调用方法时自己捕获Rpc异常,如果在代码中忽略异常,那就相当于是Failsafe Cluster模式,当然也可以选择不忽略异常,灵活得多。

3.4、Failback Cluster

如果调用失败,后台自动按固定间隔重试,调用者无需等待重试成功,一般多用于非实时性的消息传递。配置与Failfast相似,只需把"Failfast"按成"Failback"。

总结:又一个没什么用处的玩意,对于消息传递,多采用专业的消息中间件,功能强大的多。

3.5、Forking Cluster

同时对多个Provider发起调用,没有出错、成功返回最快的那个Provider生效,其它忽略。其实这种模式与容错关系不是太大,主要用于提供操作的实时性,但是会浪费资源。同样,配置的时候就是换个单词。

总结:没什么用处,用这种方式提高响应速度效率太低,只不过是简单的资源堆叠的懒汉方式。

4、负载均衡

Dubo提供多种负载均衡策略,并可自定义扩展,可在多处配置,以下优先级由低到高。一般应该由Provider端决定负载均衡策略,Consumer只管使用。

Provider端服务:

<dubbo:service interface="..." loadbalance="roundrobin" />

Consumer端服务:

<dubbo:reference interface="..." loadbalance="roundrobin" />

Provider端方法:

<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

Consumer端方法:

<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

4.1、Random LoadBalance

默认策略,根据权重值随机选择服务实例,权重越高,则被选中的概率越大。如A\B\C三个服务实例,都实现同一接口。权重分别为10、20、30,则总的权重值为60,三个实例被选中的概率分别为1/6、2/6、3/6。权重值配置如下:

<dubbo:service interface="..." weight="10" loadbalance="roundrobin" />

优点:此策略简单实用,当调用次数很多时,调用会按服务实例的权重值分布。可灵活调整权重值,改变服务实例被调用的次数。

缺点:短时间内容易发生碰撞。比如,上例中A实例被调用的概率是1/6,最低,但是接下来的60次调用,极端情况下,可能是A连续10次,B连续20次,C连续30次。总体上按权重分布了,但在短时间内却集中到了单个服务实例上,这就是碰撞。

4.2、RoundRobin LoadBalance

RoundRobin用来解决Random的碰撞问题。还是以上例说明,它总体上保证调用按权重分布,同时又可避免碰撞,对某个服务实例的调用就是不连续。

4.3、LeastActive LoadBalance

以上两种方式中,负载策略是一成不变的。还是刚才的例子,A\B\C三个服务实例,权重分别为10、20、30,那么一定的,C实例被调用的次数会最多。但是一个服务实例的响应能力并非固定不变,因为某些原因,比如说网络,导致它在某个时间段内的响应能力变差,不如A与B,但是按上边两种负载均衡策略,仍然会有最多的调用发往C。

LeastActive也就是最少活跃数则用来解决此问题,与上述两种方式都不一样。首先Consumer端发起调用时会计数调用次数,结束调用时会计数结束次数。还是A\B\C三个服务实例,Consumer端对它们分别发起来了10、15、25次调用(计数器计下来),当调用完成后也会计下来,结束的次数分别是6、10、23次,则LeastActive数据分别为10-6=4、15-10=5、25-23=2,则C的活跃数最少,为2,那么接下来的调用还是会发往C。如果其它两个实例的最少活跃数也是2,那么就按Random方式调用。

4.4、ConsistentHash LoadBalance

哈希一致性负载均衡策略,基本原理就是调用Provider端服务的方法时,计算参数的Hash值,由计算出来的值决定调用那一个服务实例。参与Hash值计算的参数可以设置,默认是第一个参数,如:

<dubbo:parameter key="hash.arguments" value="0,1" />

这样的话就是前两个参数参与计算。此策略一般与分布式缓存有关,以前发往那个服务实例,现在应该还是发往这个服务实例,因为上边有缓存。

Hash一致性算法有一个虚拟节点的概念,主要目的是为了防止发生调用倾斜,Dubbo默认虚拟节点的个数是160,配置方法如下:

<dubbo:parameter key="hash.nodes" value="320" />

假如现在有3个真实的服务实例A\B\C,有12个虚拟节点,则虚拟节点与物理节点之间的对应关系可能是:
(1~4):A
(5~8):B
(9~12):C

当调用发生时,计算出的Hash值属于1~4时就调用A,同理调用B或者C。假如现在少了一个节点C,它变得不可用了,则对应关系可能会变成下边这样:
(1~4,9~10):A
(5~8,11~12):B

本来发往C的由A与B均分,A与B原来缓存的数据仍然有效,C损失的缓存由A与B分摊重建。

同理如果过了一段时间C又上线了,则又会恢复成:
(1~4):A
(5~8):B
(9~12):C

则A上的9、10缓存与B上的11、12失效,每人都损失一小部分,损失的部分重新由C重建。

总之Hash一致性策略往往与分布式缓存有关,缓存的节点是会变化的,有可能增多有可能减少。算法的目的就是保证数据均匀分布,当节点个数变化时尽量减少损失,过渡尽量平滑。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值