启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
。另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false"
,总是会返回引用,当服务恢复时,能自动连上。
集群容错模式
Failover Cluster 失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过 retries="2"
来设置重试次数(不含第一次)。
Failfast Cluster 快速失败,只发起一次调用,失败立即报错。用于非幂等性的写操作,比如新增记录。
Failsafe Cluster失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2"
来设置最大并行数。
Broadcast Cluster广播调用所有提供者,逐个调用,任意一台报错则报错 2。通常用于通知所有提供者更新缓存或日志等本地资源信息。
负载均衡
在集群负载均衡时,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" />
线程模型
通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:
<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
时直接抛出异常而不是将任务放入阻塞队列)
直连提供者
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
通过 XML 配置
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
通过 -D 参数指定
在 JVM 启动参数中加入-D参数映射服务地址 ,如:
java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
只订阅
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。
禁用注册配置
<dubbo:registry address="10.20.153.10:9090" register="false" />
或者
<dubbo:registry address="10.20.153.10:9090?register=false" />
只注册
如果有两个镜像环境,两个注册中心,有一个服务只在其中一个注册中心有部署,另一个注册中心还没来得及部署,而两个注册中心的其它应用都需要依赖此服务。这个时候,可以让服务提供者方只注册服务到另一注册中心,而不从另一注册中心订阅服务。
禁用订阅配置
<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />
<dubbo:registry id="qdRegistry" address="10.20.141.150:9090" subscribe="false" />
或者
<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />
<dubbo:registry id="qdRegistry" address="10.20.141.150:9090?subscribe=false" />
多协议
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。
不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议。
<!-- 多协议配置 -->
<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 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的 。
<!-- 多注册中心配置 -->
<dubbo:registry id="hangzhouRegistry" address="10.20.141.150:9090" />
<dubbo:registry id="qingdaoRegistry" address="10.20.141.151:9010" default="false" />
<!-- 向多个注册中心注册 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="hangzhouRegistry,qingdaoRegistry" />
服务分组
当一个接口有多种实现时,可以用 group 区分。
服务
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
引用
<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="*" />
多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
version控制
分组聚合
按组合并返回结果,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。
搜索所有分组
<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true" />
合并指定分组
<dubbo:reference interface="com.xxx.MenuService" group="aaa,bbb" merger="true" />
指定方法合并结果,其它未指定的方法,将只调用一个 Group
<dubbo:reference interface="com.xxx.MenuService" group="*">
<dubbo:method name="getMenuItems" merger="true" />
</dubbo:service>
某个方法不合并结果,其它都合并结果
<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true">
<dubbo:method name="getMenuItems" merger="false" />
</dubbo:service>
指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称 2
<dubbo:reference interface="com.xxx.MenuService" group="*">
<dubbo:method name="getMenuItems" merger="mymerge" />
</dubbo:service>
指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身
<dubbo:reference interface="com.xxx.MenuService" group="*">
<dubbo:method name="getMenuItems" merger=".addAll" />
</dubbo:service>
结果缓存
结果缓存,用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量。
缓存类型
-
lru
基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。 -
threadlocal
当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。 -
jcache
与 JSR107 集成,可以桥接各种缓存实现。
配置
<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
在服务消费方和提供方之间进行参数的隐式传递。
异步调用
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
在 consumer.xml 中配置:
<dubbo:reference id="fooService" interface="com.alibaba.foo.FooService">
<dubbo:method name="findFoo" async="true" />
</dubbo:reference>
设置是否等待消息发出:
-
sent="true"
等待消息发出,消息发送失败将抛出异常。 -
sent="false"
不等待消息发出,将消息放入 IO 队列,即刻返回。
<dubbo:method name="findFoo" async="true" sent="true" />
如果只是想异步,完全忽略返回值,可以配置 return="false"
,以减少 Future 对象的创建和管理成本:
<dubbo:method name="findFoo" async="true" return="false" />
本地调用
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。
配置
定义 injvm 协议
<dubbo:protocol name="injvm" />
设置默认协议
<dubbo:provider protocol="injvm" />
设置服务协议
<dubbo:service protocol="injvm" />
优先使用 injvm
<dubbo:consumer injvm="true" .../>
<dubbo:provider injvm="true" .../>
或
<dubbo:reference injvm="true" .../>
<dubbo:service injvm="true" .../>
注意:服务暴露与服务引用都需要声明 injvm="true"
事件通知
在调用之前、调用之后、出现异常时,会触发 oninvoke
、onreturn
、onthrow
三个事件,可以配置当事件发生时,通知哪个类的哪个方法 。
服务消费者 Callback 配置
<bean id ="demoCallback" class = "com.alibaba.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.callback.implicit.IDemoService" version="1.0.0" group="cn" >
<dubbo:method name="get" async="true" onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" />
</dubbo:reference>
callback
与 async
功能正交分解,async=true
表示结果是否马上返回,onreturn
表示是否需要回调。
两者叠加存在以下几种组合情况 :
- 异步回调模式:
async=true onreturn="xxx"
- 同步回调模式:
async=false onreturn="xxx"
- 异步无回调 :
async=true
- 同步无回调 :
async=false
本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
在 spring 配置文件中按以下方式配置:
<dubbo:service interface="com.foo.BarService" stub="true" />
或
<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
Demo
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public (BarService barService) {
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
本地伪装
本地伪装 1 通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
在 spring 配置文件中按以下方式配置:
<dubbo:reference interface="com.foo.BarService" mock="true" />
或
<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
Demo 在工程中提供 Mock 实现 :
package com.foo;
public class BarServiceMock implements BarService {
public String sayHello(String name) {
// 你可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
延迟暴露
如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。
延迟 5 秒暴露服务
<dubbo:service delay="5000" />
延迟到 Spring 初始化完成后,再暴露服务 1
<dubbo:service delay="-1" />
并发控制
务器端并发执行(或占用线程池线程数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService" executes="10" />
指定方法服务器端并发执行(或占用线程池线程数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" executes="10" />
</dubbo:service>
客户端并发执行(或占用连接的请求数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService" actives="10" />
<dubbo:reference interface="com.foo.BarService" actives="10" />
Load Balance 均衡
配置服务的客户端的 loadbalance
属性为 leastactive
,此 Loadbalance 会调用并发数最小的 Provider(Consumer端并发数)。
<dubbo:reference interface="com.foo.BarService" loadbalance="leastactive" />
<dubbo:service interface="com.foo.BarService" loadbalance="leastactive" />
连接控制
服务端连接控制
限制服务器端接受的连接不能超过 10 个 1:
<dubbo:provider protocol="dubbo" accepts="10" />
或
<dubbo:protocol name="dubbo" accepts="10" />
客户端连接控制
限制客户端服务使用连接不能超过 10 个 2:
<dubbo:reference interface="com.foo.BarService" connections="10" />
或
<dubbo:service interface="com.foo.BarService" connections="10" />
如果 <dubbo:service>
和 <dubbo:reference>
都配了 connections,<dubbo:reference>
优先
延迟连接
延迟连接用于减少长连接数。当有调用发起时,再创建长连接。1
<dubbo:protocol name="dubbo" lazy="true" />
1. 注意:该配置只对使用长连接的 dubbo 协议生效
粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启延迟连接,以减少长连接数。
<dubbo:protocol name="dubbo" sticky="true" />
令牌验证
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者
可以全局设置开启令牌验证:
<!--随机token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />
或
<!--固定token令牌,相当于密码-->
<dubbo:provider interface="com.foo.BarService" token="123456" />
还可在服务级别、协议级别设置:
优雅停机
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID
等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID
时,才会执行。
原理
服务提供方
- 停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。
- 然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。
服务消费方
- 停止时,不再发起新的调用请求,所有新的调用在客户端即报错。
- 然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。
设置方式
设置优雅停机超时时间,缺省超时时间是 10 秒,如果超时则强制关闭。
# dubbo.properties
dubbo.service.shutdown.wait=15000
如果 ShutdownHook 不能生效,可以自行调用,使用tomcat等容器部署的場景,建议通过扩展ContextListener等自行调用以下代码实现优雅停机:
ProtocolConfig.destroyAll();
访问日志
如果你想记录每一次请求信息,可开启访问日志,类似于apache的访问日志。注意:此日志量比较大,请注意磁盘容量。
将访问日志输出到当前应用的log4j日志:
<dubbo:protocol accesslog="true" />
将访问日志输出到指定文件:
<dubbo:protocol accesslog="http://10.20.160.198/wiki/display/dubbo/foo/bar.log" />
服务容器
服务容器是一个 standalone 的启动程序,因为后台服务不需要 Tomcat 或 JBoss 等 Web 容器的功能,如果硬要用 Web 容器去加载服务提供方,增加复杂性,也浪费资源。
服务容器只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务。
服务容器的加载内容可以扩展,内置了 spring, jetty, log4j 等加载,可通过容器扩展点进行扩展。配置配在 java 命令的 -D 参数或者 dubbo.properties
中。
容器类型
Spring Container
- 自动加载
META-INF/spring
目录下的所有 Spring 配置。 - 配置 spring 配置加载位置:
dubbo.spring.config=classpath*:META-INF/spring/*.xml
Jetty Container
- 启动一个内嵌 Jetty,用于汇报状态。
- 配置:
-
dubbo.jetty.port=8080
:配置 jetty 启动端口 -
dubbo.jetty.directory=/foo/bar
:配置可通过 jetty 直接访问的目录,用于存放静态文件 -
dubbo.jetty.page=log,status,system
:配置显示的页面,缺省加载所有页面
Log4j Container
- 自动配置 log4j 的配置,在多进程启动时,自动给日志文件按进程分目录。
- 配置:
-
dubbo.log4j.file=/foo/bar.log
:配置日志文件路径 -
dubbo.log4j.level=WARN
:配置日志级别 -
dubbo.log4j.subdirectory=20880
:配置日志子目录,用于多进程启动,避免冲突
容器启动
缺省只加载 spring
java com.alibaba.dubbo.container.Main
通过 main 函数参数传入要加载的容器
java com.alibaba.dubbo.container.Main spring jetty log4j
通过 JVM 启动参数传入要加载的容器
java com.alibaba.dubbo.container.Main -Ddubbo.container=spring,jetty,log4j
通过 classpath 下的 dubbo.properties
配置传入要加载的容器
dubbo.container=spring,jetty,log4j