启动服务检查

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

  • dubbo:reference
    • check属性
      • true
        • 表示启动的时候检查依赖的服务是否正常
      • false
        • 关闭检查
        • 当服务出现循环依赖时
        • 当有些服务不关心时

所有服务的启动时检查提供者

  • dubbo:consumer
    • check属性
      • true
      • false
        • 没有服务提供者时报错

所有服务的启动时检查消费者

  • dubbo:provider
    • check属性
      • false
        • 没有服务消费者失败报错

注册中心启动时检查注册订阅是否失败

  • dubbo:registry
    • check属性
      • false
        • 注册订阅失败报错

多协议支持

  • dobbo支持的协议
    • dubbo://(TCP)
      • 介绍
        • Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
        • 反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
        • 缺省协议,使用基于 mina 1.1.7 和 hessian 3.2.1 的 tbremoting 交互。
      • 约束
        • 参数及返回值需实现 Serializable 接口
        • 参数及返回值不能自定义实现 List, Map, Number, Date, Calendar 等接口,只能用 JDK 自带的实现,因为 hessian 会做特殊处理,自定义实现类中的属性值都会丢失。
      • 常见问题
        • 为什么消费者比提供者个数多
          • 因dubbo协议采用单一长连接,假设网络为千兆网卡,根据测试经验数据每条连接最多只能压满7MByte(不同的环境可能不一样,供参考),理论上1个服务提供者需要20个服务消费者才能压满网卡。
        • 为什么不能传大包
          • 因dubbo协议采用单一长连接,如果每次请求的数据包大小为500KByte,假设网络为千兆网卡,每条连接最大7MByte(不同环境可能不一样,供参考),单个服务提供者的TPS(每秒处理事务数)最大为:128MByte/500KByte=262。单个消费者调用单个服务提供的TPS(每秒处理事务数)最大为7MByte/500KByte=14。如果能接受,可以考虑使用,否则网络将成为瓶颈。
        • 为什么采用异步单一长连接
          • 因为服务的现状大都是服务提供者少,通常只有机台机器,而服务的消费者多,可能整个网站都在访问该服务,比如Morgan的提供者只有6台,却有上百台消费者,每天有1.5亿次调用,如果采用常规的hessian服务,服务提供者很容易就被压垮,通过单一长连接,保证单一消费者不会压死提供者,长连接,减少连接握手验证等,并使用异步IO,复用线程池,防止C10K问题(C10K 就是 Client 10000 问题,即「在同时连接到服务器的客户端数量超过 10000 个的环境中,即便硬件性能足够, 依然无法正常提供服务」,简而言之,就是单机1万个并发连接问题。)。
    • rmi://(TCP)
      • 介绍
        • RMI 协议采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标准序列化方式。
        • 注意:如果正在使用 RMI 提供服务给外部访问 1,同时应用里依赖了老的 common-collections 包 2 的情况下,存在反序列化安全风险 3
      • 约束
        • 数及返回值需实现Serializable接口
        • dubbo配置中的超时事件对RMI无效,需使用java启动参数设置:-Dsun.rmi.transport.tcp.responseTimeout=3000
    • hessian://(http)
      • 介绍
        • Hessian 1 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
        • Dubbo 的 Hessian 协议可以和原生 Hessian 服务互操作,即:
          • 提供者用 Dubbo 的 Hessian 协议暴露服务,消费者直接用标准 Hessian 接口调用
          • 或者提供方用标准 Hessian 暴露服务,消费方用 Dubbo 的 Hessian 协议调用。
    • webservice://(http)
    • http://(http)
    • thrift://
    • memcached://
    • redis://
  • 改造协议
  • 支持多协议

协议对比

协议 连接个数 连接方式 传输协议 传输方式 序列化 适用范围 适用场景
dubbo:// 单连接 长连接 TCP NIO异步传输 Hessian二进制序列化 数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,不要传输大文件或超大字符串 常规远程服务方法调用
rmi:// 多连接 短连接 TCP 同步传输 Java标准二进制序列化 数据包大小混合,消费者与提供者个数差不多,可传文件
hessian:// 多连接 短连接 HTTP 同步传输 Hessian二进制序列化 数据包较大,提供者比消费者个数多,提供者压力较大,可传文件 页面传输,文件传输,或与原生hessian服务互操作
webservice:// 多连接 短连接 HTTP 同步传输 SOAP文本序列化 系统集成,跨语言调用
http:// 多连接 短连接 HTTP 同步传输 表单序列化 数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件 需同时给应用程序和浏览器JS使用的服务

代码演示(hessian)

提供方增加maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.7</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.26</version>
</dependency>

提供方修改配置

1
<dubbo:protocol name="hessian" port="8090" server="jetty"/>

提供方运行

控制台会输出

1
[com.alibaba.dubbo.config.AbstractConfig] -  [DUBBO] Export dubbo service com.bai.dubbo.order.IOrderService to url hessian://...

消费方修改配置

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

或者

1
<dubbo:reference ...  protocol="hessian"/>

消费方运行

会提示成功

Ps:Maven的SNAPSHOT版本deploy后会直接替换仓库中的版本。release则不会。

多注册中心

1
2
<dubbo:registry  id="zkOne" protocol="zookeeper" address=""/>
<dubbo:registry id="zkTwo" protocol="zookeeper" address=""/>

1
<dubbo:service registry="zkOne" ... />

dubbo目前只支持Java

多版本支持

对接口做版本控制,如一个接口升级后需要兼容。

1
2
<!--提供方-->
<dubbo:service version="2.0" ... />
1
2
<!--消费方-->
<dubbo:reference version="2.0" ... />

发布服务时生成的URL中如没有版本号,那就是未采用版本控制。

异步调用

修改配置文件

1
2
<!--消费方-->
<dubbo:reference async="true" ... />

hessian协议不支持,支持dubbo协议

消费方改造

1
2
3
4
import java.util.concurrent;
service.doOrder(request);
Future<DoOrderResponse> response=RpcContext.getContext().getFuture();
System.out.println(response.get(););//阻塞

主机绑定

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* package com.alibaba.dubbo.config;
*/
/**
* 主机绑定的过程(取主机的地址)
* 地址只能绑定一个
* 1.通过<dubbo:protocol host=""/>配置的地址去找
* 2.通过InetAddress.getLocalHost().getHostAddress();
* 3.通过socket连接到注册中心的地址。再获取连接以后的本地地址
* 4.getLocalHost() 网络接口
*/
// 如果没通过环境变量设置bind ip,则继续按优先级查找
if (hostToBind == null || hostToBind.length() == 0) {
//<dubbo:protocol host=""/>配置的host内容
hostToBind = protocolConfig.getHost();
if (provider != null && (hostToBind == null || hostToBind.length() == 0)) {
hostToBind = provider.getHost();
}
//如果是否为合法的host
if (isInvalidLocalHost(hostToBind)) {
anyhost = true;
try {
//换种方式拿host
hostToBind = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
//再次判断拿到的host是否合法
if (isInvalidLocalHost(hostToBind)) {
//注册中心的URL
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
//通过socket去连注册中心的地址
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
//拿到socket连接的本地地址作为URL
hostToBind = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {
}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
//上述操作都没有拿到
if (isInvalidLocalHost(hostToBind)) {
hostToBind = getLocalHost();
}
}
}
}

dubbo服务只订阅

所有的服务都只共用一个注册中心,其中一个服务正在开发,有问题,但注册到注册中心了。就会影响消费者不能正常运行。

需要连注册中心使用服务,但又不能注册该服务。

只订阅服务,不提供服务。

1
<dubbo:registry register="false" .../>

dubbo服务只注册

有两个注册中心,服务只在其中一个注册中心部署,另外一个注册中心没有部署时,两个注册中心的其它服务都要依赖此服务。就需要把服务注册到两个注册中心。

只提供服务,不订阅服务。

1
<dubbo:registry subscribe="false" .../>

负载均衡

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

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

Random LoadBalance

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

RoundRobin LoadBalance

  • 轮循,按公约后的权重设置轮循比率。
  • 存在慢的提供者累积请求的问题。
    • 第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机。
    • 对于响应时间比较短的服务会优先
    • 活跃数指调用前后计数差
    • 使慢的提供者收到更少请求
      • 越慢的提供者的调用前后计数差会越大

ConsistentHash LoadBalance

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

连接超时Timeout

1
2
3
<!--单位为毫秒(常规5秒左右)-->
<!--处理时间过长,调用链会出现问题-->
<dubbo:interface timeout="2000"/>

如果消费方和提供方都配置timeout属性,以消费方优先级最高,其次为提供方

集群容错

1
<dubbo:reference cluster="failfast" .../>

failover cluster(默认)

失败的时候自动切换并重试其它服务器。

通过retries=2来设置重试次数

failfast cluster

快速失败,只发起一次调用。如果失败了,就失败了,不会重试。

写操作(例如新增记录等非幂等操作)如果重试会造成数据重复。

failfafe cluster

失败安全。出现异常时,直接忽略异常(不会报异常)。

写日志时。无需保证日志一定写成功,但需保证主程序正常运行。

failback cluster

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

消息通知。

forking cluster

并行调用多个服务器,只要一个成功就返回。

只能应用在读请求。

浪费服务器资源。

broadcast cluster

广播调用所有提供者,逐个调用。其中一台报错就会返回异常。

配置优先级

消费方 > 提供方

reference method > service method > reference > service > consumer > provider