RPC服务-Dubbo

架构演进

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键

Dubbo概述

Dubbo是阿里巴巴公司开源的一个高性能优秀的分布式服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成.

Dubbo架构

Dubbo架构如下图, Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

节点角色说明

  • Provider : 暴露服务的服务提供方

  • Consumer : 调用远程服务的服务消费方

  • Registry : 服务注册与发现的注册中心

  • Monitor: 统计服务的调用次数和调用时间的监控中心

  • Container: 服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。

  2. 服务提供者在启动时,向注册中心注册自己提供的服务。

  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

连通性

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小

  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示

  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销

  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销

  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外

  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者

  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表

  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据

  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务

  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台

  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯

  • 服务提供者无状态,任意一台宕掉后,不影响使用

  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心

  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

升级性

当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。

Dubbo应用

Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入, 在本地服务的基础上只需做简单配置,即可完成服务化:

Dubbo应用示例

传统Spring服务扩展为Dubbo服务的步骤比较简单.

  1. 将传统服务中接口的实例化定义拆分为两部分: 服务提供方、服务消费方
  2. 将服务定义部分放在服务提供方 remote-provider.xml
  3. 将服务引用部分放在服务消费方 remote-consumer.xml
  4. 并在提供方增加暴露服务配置 dubbo:service,在消费方增加引用服务配置 dubbo:reference

假设我们有一个服务接口(DemoService)和接口实现(DemoServiceImpl), 对其做服务化.

服务提供方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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="hello-world-app" />

<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />

<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />

<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />

<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>

服务消费方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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="consumer-of-helloworld-app" />

<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />

<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" />
</beans>

在服务消费方我们就可以正常使用DemoService的实例化Bean demoService了.

在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连.

1
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" url="dubbo://localhost:20890"/>

Dubbo配置方式

Dubbo服务化配置提供了4种方式: 硬编码方式、基于XML配置、基于属性文件配置、基于注解配置.

配置示例

Dubbo模块特性

启动检查

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=”true”。

可以通过 check=”false” 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动.

说明: 如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check=”false”,总是会返回引用,当服务恢复时,能自动连上.

关闭特定服务

1
2
<!-- 没有提供者时报错 -->
<dubbo:reference interface="com.alibaba.dubbo.demo.DemoService" check="false" />

关闭所有服务

1
2
<!-- 没有提供者时报错 -->
<dubbo:consumer check="false" />

关不注册中心

1
2
<!-- 注册订阅失败时报错 -->
<dubbo:consumer check="false" />

集群容错

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为Failover Cluster重试策略.

  • Failover Cluster: 失败自动切换(默认策略),当出现失败,重试其它服务器, 通常用于读操作,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)

  • Failfast Cluster: 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

  • Failsafe Cluster: 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

  • Failback Cluster: 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

  • Forking Cluster: 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2” 来设置最大并行数.

  • Broadcast Cluster: 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

下面示例展示默认策略Failover Cluster的配置应用:

服务端示例

1
2
3
4
5
6
7
<!-- 针对接口 -->
<dubbo:service interface="..." retries="2" />

<!-- 针对特定方法 -->
<dubbo:service interface="...">
<dubbo:method name="..." retries="2" />
</dubbo:service>

消费端示例

1
2
3
4
5
6
7
<!-- 针对接口 -->
<dubbo:reference retries="2" />

<!-- 针对特定方法 -->
<dubbo:reference interface="...">
<dubbo:method name="..." retries="2" />
</dubbo:reference>

负载均衡

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用, 用户也可以自行扩展负载均衡策略.

随机 Random LoadBalance

  • 随机,按权重设置随机概率。

  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重

轮询 RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。

  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上

最少活跃调用数 LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

一致性哈希 ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。

  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

  • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing

  • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key=”hash.arguments” value=”0,1” />

  • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key=”hash.nodes” value=”320” />

配置示例

服务端配置

1
2
3
4
5
6
7
<!-- 针对接口 -->
<dubbo:service interface="..." loadbalance="roundrobin" />

<!-- 针对特定方法 -->
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

消费端配置

1
2
3
4
5
6
7
<!-- 针对接口 -->
<dubbo:reference interface="..." loadbalance="roundrobin" />

<!-- 针对特定方法 -->
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

线程模型

如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。

但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。

如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。

因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:

1
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />

Dispatcher

  • all: 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。

  • direct: 所有消息都不派发到线程池,全部在 IO 线程上直接执行。

  • message: 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

  • execution: 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

  • connection: 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

ThreadPool

  • fixed: 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)

  • cached: 缓存线程池,空闲一分钟自动删除,需要时重建。

  • limited: 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。

  • eager: 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)

静态服务

有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式。

1
<dubbo:registry address="10.20.141.150:9090" dynamic="false" />

或者

1
<dubbo:registry address="10.20.141.150:9090?dynamic=false" />

服务提供者初次注册时为禁用状态,需人工启用。断线时,将不会被自动删除,需人工禁用.

多协议

Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。

不同服务不同协议

不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" protocol="rmi" />
</beans>

多协议暴露服务

需要与 http 客户端互操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8080" />
<!-- 使用多个协议暴露服务 -->
<dubbo:service id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" protocol="dubbo,hessian" />
</beans>

多注册中心

Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的。

服务分组

当一个接口有多种实现时,可以用 group 区分.

服务方

1
2
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />

消费方

1
2
<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

任意组

1
<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  • 在低压力时间段,先升级一半提供者为新版本

  • 再将所有消费者升级为新版本

  • 然后将剩下的一半提供者升级为新版本

服务方

1
2
3
4
5
<!-- 老版本服务提供者配置 -->
<dubbo:service interface="com.foo.BarService" version="1.0.0" />

<!-- 新版本服务提供者配置-->
<dubbo:service interface="com.foo.BarService" version="2.0.0" />

消费方

1
2
3
4
5
<!-- 老版本服务提供者配置 -->
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

<!-- 新版本服务提供者配置-->
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

如果不需要区分版本,可如下配置

1
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

参数验证

参数验证功能是基于JSR303实现的, 用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter来实现验证.

Maven依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>

参数标注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Data 
public class ValidationParameter implements Serializable {
private static final long serialVersionUID = 7158911668568000392L;

@NotNull // 不允许为空
@Size(min = 1, max = 20) // 长度或大小范围
private String name;

@NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
@Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
private String email;

@Min(18) // 最小值
@Max(100) // 最大值
private int age;

@Past // 必须为一个过去的时间
private Date loginDate;

@Future // 必须为一个未来的时间
private Date expiryDate;

}

参数验证

1
2
3
4
5
6
7
8
9
10
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

public interface ValidationService {
// 验证参数不为空
void save(@NotNull ValidationParameter parameter);

// 直接对基本类型参数验证
void delete(@Min(1) int id);
}

配置示例

客户端验证

1
<dubbo:reference id="validationService" interface="com.alibaba.dubbo.examples.validation.api.ValidationService" validation="true" />

服务端验证

1
<dubbo:service interface="com.alibaba.dubbo.examples.validation.api.ValidationService" ref="validationService" validation="true" />

结果缓存

结果缓存,用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量.

缓存类型

  • lru: 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。

  • threadlocal: 当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。

  • jcache: 与 JSR107 集成,可以桥接各种缓存实现

配置示例

1
2
3
4
5
6
7
<!-- 针对接口 -->
<dubbo:reference interface="com.foo.BarService" cache="lru" />

<!-- 针对方法 -->
<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="findBar" cache="lru" />
</dubbo:reference>

隐式参数

可以通过 RpcContext 上的 setAttachment 和 getAttachment 在服务消费方和提供方之间进行参数的隐式传递。

说明: RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息

消费端设置隐式参数

setAttachment 设置的 KV 对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置。

1
2
3
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
// ...

服务端获取隐式参数

1
2
3
4
5
6
7
public class XxxServiceImpl implements XxxService {

public void xxx() {
// 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
String index = RpcContext.getContext().getAttachment("index");
}
}

异步调用

基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小.

消费端配置

1
2
3
4
5
6
<dubbo:reference id="fooService" interface="com.alibaba.foo.FooService">
<dubbo:method name="findFoo" async="true" />
</dubbo:reference>
<dubbo:reference id="barService" interface="com.alibaba.bar.BarService">
<dubbo:method name="findBar" async="true" />
</dubbo:reference>

代码调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 此调用会立即返回null
fooService.findFoo(fooId);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future<Foo> fooFuture = RpcContext.getContext().getFuture();

// 此调用会立即返回null
barService.findBar(barId);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future<Bar> barFuture = RpcContext.getContext().getFuture();

// 此时findFoo和findBar的请求同时在执行,客户端不需要启动多线程来支持并行,而是借助NIO的非阻塞完成

// 如果foo已返回,直接拿到返回值,否则线程wait住,等待foo返回后,线程会被notify唤醒
Foo foo = fooFuture.get();
// 同理等待bar返回
Bar bar = barFuture.get();

// 如果foo需要5秒返回,bar需要6秒返回,实际只需等6秒,即可获取到foo和bar,进行接下来的处理。

也可以设置是否等待消息发出:

  • sent=”true” 等待消息发出,消息发送失败将抛出异常。

  • sent=”false” 不等待消息发出,将消息放入IO队列,即刻返回.

1
<dubbo:method name="findFoo" async="true" sent="true" />

如果只是想异步,完全忽略返回值,可以配置 return=”false”,以减少 Future 对象的创建和管理成本

1
<dubbo:method name="findFoo" async="true" return="false" />

本地调用

本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链.

定义 injvm 协议

1
<dubbo:protocol name="injvm" />

设置默认协议

1
<dubbo:provider protocol="injvm" />

设置服务协议

1
<dubbo:service protocol="injvm" />

优先使用injvm

1
2
<dubbo:consumer injvm="true" .../>
<dubbo:provider injvm="true" .../>

1
2
<dubbo:reference injvm="true" .../>
<dubbo:service injvm="true" .../>

注意:服务暴露与服务引用都需要声明 injvm=”true”

从 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务。如果希望引用远程服务可以使用一下配置强制引用远程服务。

1
<dubbo:reference ... scope="remote" />

参数回调

事件通知

延迟连接

延迟连接用于减少长连接数。当有调用发起时,再创建长连接。

1
2
<!-- 该配置只对使用长连接的 dubbo 协议生效 -->
<dubbo:protocol name="dubbo" lazy="true" />

延迟暴露

如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。

1
2
3
4
5
<!-- 延迟 5 秒暴露服务 -->
<dubbo:service delay="5000" />

<!-- 延迟到 Spring 初始化完成后,再暴露服务 -->
<dubbo:service delay="-1" />

并发控制

连接控制

粘滞连接

粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。

粘滞连接将自动开启延迟连接,以减少长连接数.

1
<dubbo:protocol name="dubbo" sticky="true" />

令牌验证

线程栈自动Dump

当业务线程池满时,我们需要知道线程都在等待哪些资源、条件,以找到系统的瓶颈点或异常点。dubbo通过Jstack自动导出线程堆栈来保留现场,方便排查问题.

默认策略:

  • 导出路径,user.home标识的用户主目录

  • 导出间隔,最短间隔允许每隔10分钟导出一次

配置示例

1
2
3
<dubbo:application ...>
<dubbo:parameter key="dump.directory" value="/tmp" />
</dubbo:application>

优雅停机

Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在一下几种场景中被调用
* <p>
* 1. 程序正常退出
* 2. 使用System.exit()
* 3. 终端使用Ctrl+C触发的中断
* 4. 系统关闭
* 5. OutOfMemory宕机
* 6. 使用Kill pid命令干掉进程(注: 在使用kill -9 pid时, 是不会被调用的)
* <p>
* 建议: 同一个JVM最好只使用一个关闭钩子,而不是每个服务都使用一个不同的关闭钩子,使用多个关闭钩子可能会出现当前这个钩子所要依赖的服务可能已经被另外一个关闭钩子关闭了。为了避免这种情况,建议关闭操作在单个线程中串行执行,从而避免了再关闭操作之间出现竞态条件或者死锁等问题
*/
public static void JvmHookWith() {
System.out.println("The JVM is started");
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
// do something
System.out.println("Execute JVM Hook");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

参考文档

http://dubbo.apache.org/zh-cn/docs/user/quick-start.html