应用架构
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用流程
服务容器负责启动,加载,运行服务提供者
。服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送- -次统计 数据到监控中心。
配置
配置的覆盖关系
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
服务提供方配置,通过 URL 经由注册中心传递给消费方
建议由服务提供方设置超时,因为一个方法需要执行多长时间,
服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置
1. 启动时检查
在启动时检查依赖的服务是否可用
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。
可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。
关闭某个服务的启动时检查 (没有提供者时报错):
关闭所有服务的启动时检查 (没有提供者时报错):
2. 重试次数配置
Dubbo 服务在尝试调用一次之后,如出现非业务异常(服务突然不可用、超时等),Dubbo 默认会进行额外的最多2次重试.
配置方式1:通过注解/xml进行固定配置
配置方式2:通过上下文进行运行时动态配置
通过RpcContext进行运行时动态配置,优先级高于注解/xml进行的固定配置(两者都配置的情况下,以RpcContext配置为准).
// dubbo服务调用前,通过RpcContext动态设置本次调用的重试次数
RpcContext rpcContext = RpcContext.getContext();
rpcContext.setAttachment("retries", 5);
3. 集群容错
集群调用失败时,Dubbo 提供的容错方案
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
4. 负载均衡
Dubbo 提供的集群负载均衡策略
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用
Random LoadBalance
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
轮询,按公约后的权重设置轮询比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
- 一致性 Hash,相同参数的请求总是发到同一提供者。
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
算法参见:http://en.wikipedia.org/wiki/Consistent_hashing - 缺省只对第一个参数 Hash,如果要修改,请配置
- 缺省用 160 份虚拟节点,如果要修改,请配置
配置方式
//客户端服务级别
// 客户端方法级别
5. 线程模型
如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。
但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。
如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
配置
Dispatcher
all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
ThreadPool
- fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
- cached 缓存线程池,空闲一分钟自动删除,需要时重建。
- limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
- eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)
6. 直连提供者
Dubbo 中点对点的直连方式
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
配置
7. 只订阅
只订阅不注册
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。
可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。
禁用注册配置
或者
8. 多协议
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议
不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议
9. 多注册中心
Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的
多注册中心配置
向多个注册中心注册
不同服务引用不同注册中心
10. 服务分组
使用服务分组区分服务接口的不同实现
当一个接口有多种实现时,可以用 group 区分。
服务
引用
任意组:
11. 静态服务
将 Dubbo 服务标识为非动态管理模式
有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式。
或者
12. 多版本
在 Dubbo 中为同一个服务配置多个版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
新版本服务提供者配置:
老版本服务消费者配置:
新版本服务消费者配置:
如果不需要区分版本,可以按照以下的方式配置
13. 分组聚合
通过分组对结果进行聚合并返回聚合后的结果
通过分组对结果进行聚合并返回聚合后的结果,
比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。
配置
合并指定分组
指定方法合并结果,其它未指定的方法,将只调用一个 Group
某个方法不合并结果,其它都合并结果
14. 参数验证
在 Dubbo 中进行参数验证
参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
maven依赖
javax.validation
validation-api
1.0.0.GA
org.hibernate
hibernate-validator
4.2.0.Final
配置
15. 结果缓存
通过缓存结果加速访问速度
结果缓存,用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量。
缓存类型
- lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
- threadlocal 当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。
- jcache 与 JSR107 集成,可以桥接各种缓存实现。
配置
16. 使用泛化调用
实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现
泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示
通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。
在 Spring 配置申明 generic="true"
在 Java 代码获取 barService 并开始泛化调用
GenericService barService = (GenericService) applicationContext.getBean("barService");
Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });
17. 上下文信息
通过上下文存放当前调用过程中所需的环境信息
RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。
服务消费方
// 远程调用
xxxService.xxx();
本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();
18. 隐式参数
通过 Dubbo 中的 Attachment 在服务消费方和提供方之间隐式传递参数
可以通过 RpcContext 上的 setAttachment 和 getAttachment 在服务消费方和提供方之间进行参数的隐式传递。
在服务消费方端设置隐式参数
RpcContext.getContext().setAttachment("index", "1");
隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
在服务提供方端获取隐式参数
String index = RpcContext.getContext().getAttachment("index");
19. 异步执行
Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无益于节省资源或提升RPC响应性能,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。
20. 本地调用
在 Dubbo 中进行本地调用
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。
配置
从 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务。如果希望引用远程服务可以使用一下配置强制引用远程服务。
21. 参数回调
通过参数回调从服务器端调用客户端逻辑
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
服务提供者配置示例
服务消费者配置示例
22. 事件通知
在调用之前、调用之后、出现异常时,会触发 oninvoke、onreturn、onthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法
服务消费者 Callback 配置
23. 本地存根
在 Dubbo 中利用本地存根在客户端执行部分逻辑
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy
@Reference(check = false, stub = "com.xiangxue.jack.stub.LocalStubProxy")
StubService stubService;
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public BarServiceStub(BarService barService){
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
24. 本地伪装
如何在 Dubbo 中利用本地伪装实现服务降级
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
配置
return
- empty: 代表空,基本类型的默认值,或者集合类的- 空值
- null: null
- true: true
- false: false
- JSON 格式: 反序列化 JSON 所得到的对象
throw
使用 throw 来返回一个 Exception 对象,作为 Mock 的返回值。
当调用出错时,抛出一个默认的 RPCException:
force 和 fail
在 2.6.6 以上的版本,可以开始在 Spring XML 配置文件中使用 fail: 和 force:。force: 代表强制使用 Mock 行为,在这种情况下不会走远程调用。fail: 与默认行为一致,只有当远程调用发生错误时才使用 Mock 行为。force: 和 fail: 都支持与 throw 或者 return 组合使用
25. 延迟暴露
延迟暴露 Dubbo 服务
如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。我们在 Dubbo 2.6.5 版本中对服务延迟暴露逻辑进行了细微的调整,将需要延迟暴露(delay > 0)服务的倒计时动作推迟到了 Spring 初始化完成后进行。你在使用 Dubbo 的过程中,并不会感知到此变化,因此请放心使用。
延迟到 Spring 初始化完成后,再暴露服务
延迟 5 秒暴露服务
26. 延迟暴露
Dubbo 中的并发控制
限制 com.foo.BarService 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个
限制 com.foo.BarService 的 sayHello 方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个
限制 com.foo.BarService 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个
限制 com.foo.BarService 的 sayHello 方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个
reference一样
27. 连接控制
Dubbo 中服务端和客户端的连接控制
限制服务器端接受的连接不能超过 10 个
限制客户端服务使用连接不能超过 10 个
connections 表示该服务对每个提供者建立的长连接数
28. 延迟连接
在 Dubbo 中配置延迟连接
延迟连接用于减少长连接数。当有调用发起时,再创建长连接
该配置只对使用长连接的 dubbo 协议生效
29. 粘滞连接
为有状态服务配置粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启延迟连接,以减少长连接数。
ubbo 支持方法级别的粘滞连接,如果你想进行更细粒度的控制,还可以这样配置。
30. 令牌验证
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者
可以全局设置开启令牌验证:
也可在服务级别设置:
31. 配置规则
向注册中心写入动态配置覆盖规则。该功能通常由监控中心或治理中心的页面完成。
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&timeout=1000"));
其中:
- override:// 表示数据采用覆盖方式,支持 override 和 absent,可扩展,必填。
- 0.0.0.0 表示对所有 IP 地址生效,如果只想覆盖某个 IP 的数据,请填入具体 IP,必填。
- com.foo.BarService 表示只对指定服务生效,必填。
- category=configurators 表示该数据为动态配置类型,必填。
- dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
- enabled=true 覆盖规则是否生效,可不填,缺省生效。
- application=foo 表示只对指定应用生效,可不填,表示对所有应用生效。
- timeout=1000 表示将满足以上条件的 timeout 参数的值覆盖为 1000。如果想覆盖其它参数,直接加在 override 的 URL 参数上。
示例:
1、禁用提供者:(通常用于临时踢除某台提供者机器,相似的,禁止消费者访问请使用路由规则)
override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true
2、调整权重:(通常用于容量评估,缺省权重为 100)
override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&weight=200
3、调整负载均衡策略:(缺省负载均衡策略为 random)
override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&loadbalance=leastactive
4、服务降级:(通常用于临时屏蔽某个出错的非关键服务)
override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null
32. 优雅停机
让 Dubbo 服务完成优雅停机
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。
原理
服务提供方
停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。
然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。
服务消费方
停止时,不再发起新的调用请求,所有新的调用在客户端即报错。
然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。
设置方式
设置优雅停机超时时间,缺省超时时间是 10 秒,如果超时则强制关闭
dubbo.properties
dubbo.service.shutdown.wait=15000
如果 ShutdownHook 不能生效,可以自行调用 :
DubboShutdownHook.destroyAll();
33. 主机绑定
缺省主机 IP 查找顺序:
- 通过 LocalHost.getLocalHost() 获取本机地址。
- 如果是 127.* 等 loopback 地址,则扫描各网卡,获取网卡 IP。
主机配置
注册的地址如果获取不正确,比如需要注册公网地址,可以
在 dubbo.xml 中加入主机地址的配置:
或在 dubbo.properties 中加入主机地址的配置:
dubbo.protocol.host=205.182.23.201
设置端口
dubbo.protocol.dubbo.port=20880