1.简介
为了避免单点故障,现在的应用通常至少会部署在两台服务器上。对于一些负载比较高的服务,会部署更多的服务器。这样,在同一环境下的服务提供者数量会大于1。对于服务消费者来说,同一环境下出现了多个服务提供者。这时会出现一个问题,服务消费者需要决定选择哪个服务提供者进行调用。另外服务调用失败时的处理措施也是需要考虑的,是重试呢,还是抛出异常,亦或是只打印异常等。为了处理这些问题,Dubbo 定义了集群接口 Cluster 以及 Cluster Invoker。集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。这样一来,服务消费者只需通过这个 Invoker 进行远程调用即可,至于具体调用哪个服务提供者,以及调用失败后如何处理等问题,现在都交给集群模块去处理。集群模块是服务提供者和服务消费者的中间层,为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜。比如发请求,接受服务提供者返回的数据等。这就是集群的作用。
2.集群容错
这里有必要先来介绍一下集群容错的所有组件。包含 Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等。

集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker,可简单类比为 List<Invoker>。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoker 方法,进行真正的远程调用。
以上就是集群工作的整个流程。
简而言之: 容错即“耐故障”或“容许故障”的意思。对于组成系统的元器件发生不可避免的故障时,采取响应的措施,仍能使系统维持正常工作状态。
容错模式:是解决容错问题的一系列解决方案。dubbo框架为服务集群容错提供了一系列好的解决方案,在此称为dubbo服务集群容错模式。
Dubbo 主要提供了这样几种容错方式:
Failover Cluster - 失败自动切换(默认)
Failfast Cluster - 快速失败
Failsafe Cluster - 失败安全
Failback Cluster - 失败自动恢复
Forking Cluster - 并行调用多个服务提供者
Dubbo 提供了多种集群实现,包含但不限于 Failover Cluster、Failfast Cluster 和 Failsafe Cluster 等。每种集群实现类的用途不同,接下来会一一进行分析。
3.搭建基于Apache dubbo 的集群环境
- 创建父工程dubbodemo 并引入依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.创建子工程dubbo-service 依赖于dubbodemo工程(服务提供者)
创建接口:
package com.gf.service;
public interface DemoService {
String sayHello(String name);
}
创建接口的实现类:
package com.gf.service.impl;
import com.gf.service.DemoService;
public class DemoServiceImpl implements DemoService {
public String sayHello(String name) {
System.err.println("service执行了===========");
return "hello : "+name;
}
}
编写provider.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="dubbo-service" />
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.gf.service.DemoService" ref="demoService" />
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.gf.service.impl.DemoServiceImpl" />
</beans>
编写Provider.java启动类:
package com.gf.provider;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class Provider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.in.read();
}
}
3.创建子工程dubbo-web 依赖于dubbodemo工程(消费者)
声明接口:(注意:此接口必须和服务提供者接口同包同名)
package com.gf.service;
public interface DemoService {
String sayHello(String name);
}
编写consumer.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="dubbo-web" />
<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" interface="com.gf.service.DemoService" />
</beans>
编写Consumer.java启动类:
package com.gf.consumer;
import com.gf.service.DemoService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml");
context.start();
DemoService demoService = context.getBean("demoService", DemoService.class);
String s = demoService.sayHello("dubbo..");
System.err.println(s);
System.in.read();
}
}
接着我们复制一份dubbo-service改名为dubbo-service1,并修改端口号即可实现模拟多个服务提供者实例即模拟的集群环境.
1.Failover Cluster
当出现失败时,重试其他服务器,为缺省值。通常用于读操作,但重试会带来更长的延迟。可通过retries="2"来设置重试次数(不含第一次)。
幂等(设置重试次数)【查询】、非幂等(不能设置重试次数)【新增】
代码:
<dubbo:service interface="com.gf.service.DemoService" ref="demoService" retries="2" />
2.Failfast Cluster
只发起一次调用,失败立即报错,并且停止当前消费,通常用于非幂等性的写操作,比如:新增记录。
代码:
<dubbo:service interface="com.gf.service.DemoService" ref="demoService" cluster="failfast" />
3.Failsafe Cluster
出现异常时,直接忽略。当前消费者不会停止,待提供者服务恢复,即可正常调用。通常用于写入审计日志等操作。
代码:
<dubbo:service interface="com.gf.service.DemoService" ref="demoService" cluster="failsafe" />
4.Failback Cluster
后台记录失败请求,定时重发(消费者不会停止。一段时间后重新再调用,直到调用成功)。通常用于消息通知操作。
代码:
<dubbo:service interface="com.gf.service.DemoService" ref="demoService" cluster="failback" />
5.Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求比较高的读操作,但需要浪费更多的服务资源。
可通过forks="2"来设置最大并行数。
<dubbo:service interface="com.gf.service.DemoService" ref="demoService" cluster="forking" forks="2"/>
4.配置原则
dubbo推荐在Provider上尽量多配置Consumer端属性:【非常重要】
1、作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
2、在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作为Consumer的缺省值。否则,Consumer会使用Consumer端的全局设置,这对于 Provider不可控的,并且往往是不合理的
3、方法级配置别优于接口级别,即小Scope优先
5.多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
在低压力时间段,先升级一半提供者为新版本
再将所有消费者升级为新版本
然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.gf.service.DemoService" version="1.0.0" />
新版本服务提供者配置:
<dubbo:service interface="com.gf.service.DemoService" version="2.0.0" />
老版本服务消费者配置:
<dubbo:reference id="demoService" interface="com.gf.service.DemoService" version="1.0.0" />
新版本服务消费者配置:
<dubbo:reference id="demoService" interface="com.gf.service.DemoService" version="2.0.0" />
如果不需要区分版本,可以按照以下的方式配置 :
<dubbo:reference id="demoService" interface="com.gf.service.DemoService" version="*" />