dubbo-基本使用和高级特性

由于dubbo社区较为活跃,版本更新快,在spring-cloud-alibaba之后,spring-boot-dubbo的使用有一些改动,本文代码示例基于dubbo2.7.7版本

1. 基本使用

创建项目结构:api、provider、consumer

1.1 管理项目依赖

api之定义服务提供者消费者之间通信的业务接口,无需添加dubbo相关依赖。

provider

基于springboot构建添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

首先添加dubbo依赖:

1
2
3
4
5
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>

需要实现api的接口并将实现以服务的形式发布到注册中心,需要添加注册中心的依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.7</version>
</dependency>

后面会演示多注册中心支持,因此添加两种注册中心依赖,dubbo支持很多中间件作为注册中心,除以上两种之外还有dubbo-registry-redisdubbo-registry-multicast

consumer依赖与provider一致

1.2 配置

provider 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: spring-boot-dubbo-provider

dubbo:
application:
name: ${spring.application.name}
protocol:
id: dubbo
port: -1 # 表示自动生成端口号

registries: # 多注册中心
nacos:
name: nacos
address: nacos://10.0.12.76:8848
zookeeper:
name: zookeeper
address: zookeeper://10.0.12.74:2181

在需要发布的接口实现类上注解@DubboService,注解中也能进行配置,此处配置优先级高于配置文件的全局配置:

1
2
3
4
@DubboService(registry = {"nacos"},  // 注册到nacos,可以配置多个,必须是配置文件配置的子集
loadbalance = "roundrobin", // 服务端提供的负载算法,客户端不配置的话就用这个
cluster = "failover", // 容错机制
retries = 2) // 重试

在SpringBoot启动类上注解@DubboCompontScan(basePackages={"xxx.xxx.xxx"}),启动,查看nacos和zookeeper可以看到服务发布成功。

consumer配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: spring-boot-dubbo-consumer

dubbo:
registries:
nacos:
name: nacos
address: nacos://10.0.12.76:8848
register: false # 配置消费者不注册到注册中心
zookeeper:
name: zookeeper
address: zookeeper://10.0.12.74:2181
register: false # 默认是true

在需要调用远程服务的类中使用@DubboReference注解注入bean,注解中也可以进行相应的配置

1
2
3
@DubboReference(protocol = "dubbo",    // 使用dubbo协议,默认
mock = "com.pd.moke.ISayHelloMoke", // 服务降级,自定义的降级类
check = false) // 关闭启动检查,这样服务提供者没启动不会影响消费者的启动

配置完成启动consumer,调用远程服务的方法,调用成功返回远程结果或者调用失败返回moke中的处理结果

2. 多协议支持

dubbo一个重要的特性就是支持多协议,那么有哪些协议呢:

  • dubbo
  • hessian
  • thrift
  • grpc http2.0 / protobuff rest
  • rest
  • rmi

下面来演示一下使用rest和duubo两种协议来发布服务。

dubbo 中的rest协议使用restEasy提供的rest服务,在api中添加以下依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.13.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.13.0.Final</version>
</dependency>

此外rest接口需要部署到servlet容器中,这里使用jetty,api中添加依赖:

1
2
3
4
5
6
7
8
9
10
11
<!--jetty-->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.19.v20190610</version>
</dependency>

使用restEasy改写api接口,令其能支持rest协议:

1
2
3
4
5
6
@Path("/")
public interface ISayHello {
@GET
@Path("/say")
String sayHello(String msg);
}

修改provider的配置文件

1
2
3
4
5
6
7
8
9
dubbo:
protocols:
dubbo:
name: dubbo
port: -1 # 默认生成,从20880开始往后加
rest:
name: rest
port: -1 # 从80开始往后加
server: jetty

启动服务,查看nacos发现该服务有两个实例,一个是dubbo协议,一个是rest协议,在浏览器访问:http://localhost/say,直接返回结果。

此外在服务实现类注解上也可以指定使用的协议类型,必须是配置文件指定的子集:

1
2
3
4
5
@DubboService(registry = {"nacos","zookeeper"},
protocol = {"rest","dubbo"}, // 指定协议发布
loadbalance = "roundrobin",
cluster = "failover",
retries = 2)

3. 客户端负载均衡

当服务提供者部署了多个实例,客户端调用服务时会进行负载均衡,默认的负载均衡算法是随机算法。

3.1 Random(默认)

该随机其实是加权随机算法

它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于 服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数, 然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大 的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被 选中的次数比例接近其权重比例。

3.2 roundrobin (轮询)

所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请 求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给 服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能 相近的场景下。但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给 性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服 务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的 2次请求,服务器 C 则收到其中的1次请求

3.3 一致性hash 负载

dubbo, 根据参数进行hash取模。 默认是根据 {第一个参数}。 数据分片

{hash(parameter) % 3} =0,1,2 {hash(parameter) % 6} =0,1,2,3,4,5

3.4 最小活跃度

根据目标集群服务器列表,处理性能最高的,权重也越高。处理性能较低的,权重也比较低 根据请求处理的吞吐量 -> 发起一次请求(开始),计数器+1、 一次请求处理完成,计数器-1

3.5 shortestreponse loadbalance

最短响应时间负载均衡算法,

筛选成功调用响应时间最短的调用程序的数量,并计算这些调用程序的权重和数量。然后根据响应时间 的长短来分配目标服务的路由权重。

4. 集群容错

容忍错误的能力

客户端发起了一次请求, 报错了?超时了?

三态: 成功|失败|未知

4.1failover cluster(默认)

失败自动重试(重试其他服务器),失败自动切换

1
@DubboService(cluster="failover",retires=2)

注意[幂等] ,必须要保证成功。

4.2 failfast cluster

快速失败,立马报错。

4.3 failsafe cluster

失败安全, 出现异常,直接吞掉。

4.4 failback cluster

失败自动恢复,记录失败请求,定时重发。

4.5 forking cluster

并行调用多个服务节点,只要其中一个成功返回,那么就直接返回结果。

4.6 broadcast cluster

广播调用,一个请求调用所有的服务提供者。只要其中一个节点报错,那么就认为这个请求失败。

5. 服务降级

客户端注解设置moke属性,上文已提到了。

6. Dubbo泛华

在没有提供api的情况下消费者如何调用服务提供者提供的接口呢?这时就要用到dubbo的泛化功能了。

dubbo泛化就是使用一个GenericService接收注入的服务实例,因为此时不知道具体服务的类型呀,然后使用反射的方式调用GenericService中的方法。

演示如下:

provider

1
2
3
4
5
6
7
8
9
10
public interface IDemoService {
String getMsg();
}

@DubboService(protocol = {"dubbo"})
public class DemoService implements IDemoService{
public String getMsg(){
return "泛化功能演示";
}
}

consumer

1
2
3
4
5
6
7
@DubboReference(interfaceName = "com.pd.service.IDemoService",generic = true,check = false)
GenericService demoService;

@GetMapping("/generic")
public String generic(){
return demoService.$invoke("getMsg",new String[0],null).toString();
}

这样就可以通过泛化调用到远程服务了。

7. 配置的优先级

在服务提供端,@DubboService注解中的配置高于配置文件

服务端配置的信息,都会装载到url上

客户端拿到url之后能够获取服务端的配置,如果客户端没有重新配置 ,那么就用服务端的配置。

客户端有配置时,覆盖服务端配置。

在客户端,注解上的配置优先级高于配置文件