关于 Dubbo 以前也用过几次,都是浅度学习,也没做笔记,今天看了下官网竟然改版了,手册更新了,借这个契机来复习下,并且做下笔记,内容大部分来自官方手册。
官网:https://dubbo.incubator.apache.org
旧版的用户使用手册:https://www.gitbook.com/book/dubbo/dubbo-user-book
官方文档开始的背景介绍写的挺不错的,可以去参考下,曾经的 ORM、MVC,到现在的分布式 RPC 和 SOA。
架构
个人感觉 Dubbo 的架构设计是很不错的,官网的文档也解释的很详细:
| 节点 | 角色说明 |
|---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
调用关系说明
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。
我认为 Dubbo 适用于比较大、复杂的服务调用需求的系统,一般的中小型使用通过配置服务的 URL 地址进行调用,通过 F5 等硬件进行负载均衡(或者通过 RMI 或 Hessian 等工具,简单的暴露和引用远程服务 )这样就足够了。
连通性
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
- 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
- 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
- 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
- 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
- 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
- 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性
- 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
- 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
快速入门
仿照官方文档,最简单的代码示例放在了我 Github 上的 Java_lean 仓库里,自取…..配置了 xml 版和注解版(推荐),Dubbo 采用全 Spring 配置方式,能基于 Spring 的 Schema 扩展进行加载,Dubbo 宣称能与 Spring(SpringBoot) 无缝整合,不能浪费。
就是添加了 Dubbo 的依赖,根据传递性,就自动导入了 Spring 相关依赖,所以测试的话只需要一个 Dubbo、zookeeper 、zkclient 和你定义的接口就可以了!
另外就是 Dubbo 底层用的是 netty 做通讯,所以效率会较高。
在搭建测试的时候终究还是踩了不少坑,记录在这里附上了解决方案。在使用注解方式时,尤其注意 <dubbo:annotation> 在 2.5.8 之后的版本不再支持了!请使用 @Configuration 大法解决。
XML配置
关于 XML 的基本配置,主要是服务的提供方和消费方,配置非常类似:
服务提供方(remote-provider.xml):
1 | xml version="1.0" encoding="UTF-8" |
服务消费方(remote-consumer.xml):
1 | <!-- 不是匹配条件,不要与提供方一样 --> |
以上就是最简单的纯 XML 方式使用 Dubbo,用的不多了吧,毕竟 XML 太繁琐了。
另外就是所有标签都支持自定义参数(通过子标签 <dubbo:parameter key="queue" value="your_queue" />),用于不同扩展点实现的特殊配置 。
引用缺省是延迟初始化的,只有引用被注入到其它 Bean,或被 getBean() 获取,才会初始化,如需立即实例化,可配置:<dubbo:reference ... init="true" />
对于相同的属性,例如 timeout 查找(优先级)顺序是: referenceMethod --> serviceMethod --> reference --> service --> consumer --> provider
注解配置
需要 2.5.7 及以上版本支持,使用注解的方式配置:
使用 javaconfig 形式配置公共模块:
1 |
|
指定 Dubbo 的扫描路径,就是自动扫描,提供方和消费方都是一样的配置:
1 |
|
再次强调,2.5.8 之后 <dubbo:annotation> 不再可用!
服务提供方:
1 | (timeout = 5000) |
相当于 XML 中的 <dubbo:service /> 用来暴露服务。
服务消费方:
1 | public class AnnotationConsumeService { |
properties配置
在比较简单的情况下,例如没有多注册中心,多协议等情况,或者想多个 Spring 容器想共享配置,Dubbo 将自动加载 classpath 根目录下的 dubbo.properties,可以通过 JVM 启动参数 -Ddubbo.properties.file=xxx.properties 改变缺省配置位置。
将 XML 配置的标签名,加属性名,用点分隔,多个属性拆成多行:
- 比如:
dubbo.application.name=foo等价于<dubbo:application name="foo" /> - 比如:
dubbo.registry.address=10.20.153.10:9090等价于<dubbo:registry address="10.20.153.10:9090" />
如果 XML 有多行同名标签配置,可用 id 号区分,如果没有 id 号将对所有同名标签生效:
- 比如:
dubbo.protocol.rmi.port=1234等价于<dubbo:protocol id="rmi" name="rmi" port="1099" /> - 比如:
dubbo.registry.china.address=10.20.153.10:9090等价于<dubbo:registry id="china" address="10.20.153.10:9090" />
下面是 dubbo.properties 的一个典型配置:
1 | dubbo.application.name=foo |
关于优先级,JVM 启动参数 -D 最优先,其次是 XML 的配置,最后是 properties 文件。
启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成 ,默认 check="true"。 所以说,启动是有顺序的,默认下必须要服务方先启动。
check 属性可以加在 dubbo:reference 、dubbo:consumer 、dubbo:registry 标签中,分别控制的是:
某个服务的启动时检查(没有提供者时报错 )、所有服务的启动时检查、注册中心启动时检查(注册订阅失败时报错)。
优先级是 reference > consumer ,还可以设置全局参数,例如:dubbo.reference.check=false 、 dubbo.consumer.check=false 等
集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
根据上面的架构图,理下各节点关系:
- 这里的
Invoker是Provider(服务提供方)的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息。 Directory代表多个Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。Cluster(集群)将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。
容错模式
Dubbo 提供的容错模式有:
- Failover Cluster
默认的故障转移模式,就是失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。
可通过retries属性来指定重试的次数(不含第一次) - Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 - Failsafe Cluster
失败安全(指不会抛出异常),出现异常时,直接忽略。通常用于写入审计日志等操作。 - Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 - Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数。 - Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群配置模式(以 XML 为例):
1 | <!-- 服务提供方 --> |
负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。可以自行扩展负载均衡策略。
负载均衡策略
- Random LoadBalance
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 - RoundRobin LoadBalance
轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 - LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 - ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
缺省只对第一个参数 Hash (由hash.arguments参数控制)。
缺省用 160 份虚拟节点(由hash.nodes参数控制)。
配置
示例为基于 XML 的配置:
1 | <!-- 服务端服务级别 --> |
直连提供者
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
通过 XML 来配置直接的提供者,在 <dubbo:reference> 中配置 url 指向提供者,将绕过注册中心,多个地址用分号隔开:<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
除外,还可以通过 -D 参数来指定:java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
配置的服务如果较多,可采用文件(properties)映射。
只订阅&注册
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。
可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。
配置方式:
1 | <dubbo:registry address="10.20.153.10:9090" register="false" /> |
对于只注册的情况,例如让服务提供者方只注册服务到某一注册中心,而消费方不从另外的注册中心订阅服务。
1 | <dubbo:registry id="hzRegistry" address="10.20.153.10:9090" /> |
多协议
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。
不同服务不同协议
不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议
1 | <dubbo:application name="world" /> |
多协议暴露服务
需要与 http 客户端互操作
1 | <dubbo:application name="world" /> |
当然,Dubbo 也支持多注册中心。
Dubbo协议
默认协议,也是推荐的协议,这里就只说它,关于 dubbo:// 的基本介绍:
Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
特性:
- 连接个数:单连接
- 连接方式:长连接
- 传输协议:TCP
- 传输方式:NIO 异步传输
- 序列化:Hessian 二进制序列化
- 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。
- 适用场景:常规远程服务方法调用
接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署。输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署。
输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。
总结:服务器端和客户端对领域对象并不需要完全一致,而是按照最大匹配原则。
服务分组&多版本
当一个接口有多种实现时,可以用 group 区分。
XML 配置示例:
1 | <!-- 服务 --> |
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。 XML 配置示例:
1 | <!-- 服务提供者 --> |
官方给的版本迁移建议:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
结果缓存
用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量 。
缓存类型
lru
基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。threadlocal
当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。jcache
与 JSR107 集成,可以桥接各种缓存实现。
XML 配置示例:
1 | <dubbo:reference interface="com.foo.BarService" cache="lru" /> |
异步调用
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
首先,在 XML 中开启异步支持:
1 | <dubbo:reference id="fooService" interface="com.alibaba.foo.FooService"> |
代码中进行异步调用:
1 | // 此调用会立即返回null |
是否等待消息发出(配置在 dubbo:method 中即可):
sent="true"等待消息发出,消息发送失败将抛出异常。sent="false"不等待消息发出,将消息放入 IO 队列,即刻返回。
如果你只是想异步,完全忽略返回值,可以配置 return="false",以减少 Future 对象的创建和管理成本
其中,异步方式总是不等待返回。
并发&连接控制
通过在 dubbo:service 标签中设置 executes 属性来控制服务器端并发执行(或占用线程池线程数)不能超过的个数。
通过设置 actives 属性(可在 dubbo:reference/service 中设置)来控制每客户端并发执行(或占用连接的请求数)不能超过的个数。
如果 <dubbo:service> 和 <dubbo:reference> 都配了actives,<dubbo:reference> 优先。
Load Balance 均衡
配置服务的客户端的 loadbalance 属性为 leastactive,此 Loadbalance 会调用并发数最小的 Provider(Consumer端并发数)。 可在 dubbo:reference/service 中设置。
连接控制分为服务端的连接控制和客户端的连接控制,XML 配置示例:
1 | <!-- 限制服务器端接受的连接不能超过 10 个,两种方式 --> |
如果 <dubbo:service> 和 <dubbo:reference> 都配了 connections,<dubbo:reference> 优先.
其他
列举下没用过,但是可能会用到的,所以这些并不全,最全的还要去官网的示例看:
- 分组聚合
接口一样,但有多种实现,用 group 区分,现在消费方需从每种 group 中调用一次返回结果,合并结果返回。 - 回声测试
回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。
所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService,即可调用$echo("OK")方法看看是否返回 OK - 上下文信息
上下文中存放的是当前调用过程中所需的环境信息(获取提供方 IP、判断是否为消费端等)。所有配置信息都将转换为 URL 的参数;
RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。 - 隐式参数
通过 RpcContext 上的setAttachment和getAttachment在服务消费方和提供方之间进行参数的隐式传递。
例如:RpcContext.getContext().set/getAttachment() - 本地调用
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。 - 参数回调&事件通知
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
在调用之前、调用之后、出现异常时,会触发 oninvoke、onreturn、onthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法。 - 延迟连接
延迟连接用于减少长连接数。当有调用发起时,再创建长连接。
推荐用法
在 Provider(提供方) 上尽量多配置 Consumer(消费方) 端属性:
- 作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
- 在 Provider 配置后,Consumer 不配置则会使用 Provider 的配置值,即 Provider 配置可以作为 Consumer 的缺省值。否则,Consumer 会使用 Consumer 端的全局设置,这对于 Provider 不可控的,并且往往是不合理的。
覆盖规则:Consumer 端配置优于 Provider 配置,优于全局配置
这样可以让 Provider 实现者一开始就思考 Provider 服务特点、服务质量的问题。
常见的配置有:
- timeout 方法调用超时
- retries 失败重试次数,缺省是 2(会调用 3 次)
- loadbalance 负载均衡算法,缺省是随机 random。还可以有轮询 roundrobin、最不活跃优先 leastactive
- actives 消费者端,最大并发调用限制,即当 Consumer 对一个服务的并发调用到上限后,新调用会 Wait 直到超时 在方法上配置
dubbo:method则并发限制针对方法,在接口上配置dubbo:service,则并发限制针对服务
Provider 上可以配置的 Provider 端属性有:
threads服务线程池大小executes一个服务提供者并行执行请求上限,即当 Provider 对一个服务的并发调用到上限后,新调用会 Wait,这个时候 Consumer可能会超时。在方法上配置dubbo:method则并发限制针对方法,在接口上配置dubbo:service,则并发限制针对服务。
XML 配置示例:
1 | <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" |
配置管理信息:
目前有负责人信息和组织信息用于区分站点。有问题时便于的找到服务的负责人,至少写两个人以便备份。负责人和组织的信息可以在注册中心的上看到,例如:
1 | <!-- 应用配置负责人、组织 --> |
配置 Dubbo 缓存文件(在提供者方设置列表缓存文件):
1 | <dubbo:registry file=”${user.home}/output/dubbo.cache” /> |
文件的路径,应用可以根据需要调整,保证这个文件不会在发布过程中被清除。
如果有多个应用进程注意不要使用同一个文件,避免内容被覆盖。
这个文件会缓存注册中心的列表和服务提供者列表。
对于监控,推荐使用固定端口暴露服务,而不要使用随机端口,这样在注册中心推送有延迟的情况下,消费者通过缓存列表也能调用到原地址,保证调用成功。
最后就是不要使用 dubbo.properties 文件配置,推荐使用对应 XML 配置
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~