持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
今天继续跟着小羽一起看看这个微服务框架之一的 Dubbo 的详细解读吧,没看第一章节的可以点击这里,第二章节的可以点击这里,鉴于文章篇幅过长,我会在这里分为三个章节,以下是关于第三章节的图文详解内容。
集群容错方案
- 配置说明,方案配置方式,优先使用消费端配置
<!--服务端配置-->
<dubbo:service cluster="failover"/>
<!--消费端配置-->
<dubbo:reference cluster="failover"/>
- 尽量在只在服务端进行配置
- cluster类型均为小写
- 默认为FailoverCluster失败切换方案
集群容错方案support
FailoverCluster(默认):失败切换
- 场景:调用失败后切换其他服务
- 配置:
<!--
retries:重试次数,不包括第一次,默认2次
-->
<dubbo:service cluster="failover" retries="3"/>
-
代码实现逻辑:
1. 根据负载均衡策略选出需要调用的服务实例,排除已调用的
2. 执行选出的实例,并将其保存到已调用列表中
3. 执行实例成功即返回
4. 执行实例不成功,为到最大重试次数则执行第一步,否则抛出RpcException异常
FailbackCluster:失败重试
- 场景:调用失败时记录失败请求,定时重发
- 配置:
<!--
retries:重试次数,不包括第一次,默认3次
failbacktasks:定时器中最大挂起任务数,默认100
-->
<dubbo:service cluster="failback" retries="5" failbacktasks="200"/>
-
代码实现逻辑
1. 根据负载均衡策略选出需要调用的服务实例
2. 执行选出的实例
3. 执行实例成功即返回
4. 执行异常则创建延时5秒的定时任务,并加入时间轮定时器,第一次需要进行定时器初始化,分为32个时间片,每1秒滚动一次,最大挂起任务默认100个,超出最大任务数时抛出RejectedExecutionException异常。
5. 重试执行定时任务,次数超出最大执行次数停止,并输出error日志,默认为3次。
FailfastCluster:快速失败
- 场景:调用失败立即报错
- 配置:
<dubbo:service cluster="failfast"/>
-
代码实现逻辑
1. 根据负载均衡策略选出需要调用的服务实例
2. 执行选出的实例
3. 执行实例成功即返回,失败抛出RpcException异常
FailsafeCluster:安全失败
- 场景:调用失败后忽略
- 配置:
<dubbo:service cluster="failsafe"/>
-
代码实现逻辑
1. 根据负载均衡策略选出需要调用的服务实例
2. 执行选出的实例
3. 执行实例成功即返回,失败输出error日志,并返RpcResult,视为忽略。
ForkingCluster:并发处理
- 场景:并发调用指定数量的服务,一个成功则返回,对实时性要求高的场景,要求快速返回,需要使用更多服务器资源。
- 配置:
<!--
forks:最大并发数,默认2
timeout:并发返回超时时间,默认1000ms
-->
<dubbo:service cluster="forking" forks="3" timeout="500"/>
-
代码实现逻辑
1. 根据负载均衡策略选出几个不同的服务实例
2. 并发执行选出的几个实例,并将返回结果放入堵塞队列中
3. 返回堵塞队列中的第一个值,如规定时间内未获取到队列中的值或获取到异常值则返回RPC异常。
BroadcastCluster:广播
- 场景:广播方式逐个调用服务提供者,有一个报错则返回错误,多用于通知服务提供者更新本地资源信息,如缓存,日志等。
- 配置:
<dubbo:service cluster="broadcast"/>
-
代码实现逻辑
1. 循环逐个执行所有服务实例信息
2. 保存一份返回结果和异常信息
3. 执行完全部实例后,如异常信息不为空,则抛出异常信息,否则返回最后一个实例的结果。
AvailableCluster:可用服务
- 场景:调用第一个可用服务
- 配置:
<dubbo:service cluster="available"/>
-
代码实现逻辑
1. 循环所有服务实例信息
2. 执行第一个可用的实例,并返回结果
3. 如无可用实例则返回RpcException异常
MergeableCluster:合并处理
- 场景:返回合并或叠加处理结果
- 配置:
<!--
merger:合并发放名
timeout:调用服务超时时间,默认1000ms
-->
<dubbo:service cluster="mergeable" merger="true" timeout="500"/>
-
代码实现逻辑
1. 判断merger,为空、null、0、false、N/A是执行第一个可用服务并返回结果,无可用则执行第一个实例,并返回结果。
2. 获取方法实例的返回类型
3. 异步调用所有实例,并将异步结果Result存储到结果集中,返回异常输出error日志
4. 结果集为空返回 RpcException,大小为 1时返回第一个Result
5. 当merger的第一个字符为“.”时,判断当 merger 实例返回类型不为void,且返回类型必须是结果集中第一个返回类型的父类型或相同类型时,循环执行merger实例,每一次都传入上一次的返回结果,最终返回获取最后一次结果,非上述情况时循环执行merger实例,返回结果集中的第一个结果。
6. 当merger为true或default时使用Dubbo默认合并器,否则使用自定义merger合并器,合并后返回
RegistryAwareCluster:默认标识、注册标识
- 场景:调用注册默认标识的服务
- 配置:
<!--
default:默认标识
-->
<dubbo:registry address="zookeeper://xxx..." default="true"/>
<dubbo:service cluster="registryaware"/>
-
代码实现逻辑
1.8 循环所有服务实例信息
2. 执行第一个可用的实例且default为true的实例
3. 无默认实例则执行第一个可用的实例
4. 无可用的实例则抛出RpcException异常
主要配置
配置应用信息:
<dubbo:application name=“appName-provider” />
配置注册中心相关信息:
<dubbo:registryid=“zk” protocol=“zookeeper” address=“127.0.0.1:2181” />
配置服务协议:
<dubbo:protocol name=“dubbo” port=“20880” threadpool=“cached” threads=“80” />
配置所有暴露服务缺省值:
<dubbo:provider registry=“zk” protocol=“dubbo” retries=“0” version=“1.0.0” timeout=“3000” threadpool=“cached” threads=“4”/>
配置暴露服务:
<dubbo:service interface=“com.orgname.app.serviceX” ref=“serviceX” />
配置所有引用服务缺省值:
<dubbo:consumer check=“false” timeout=“1000” version=“1.0” retries=“0” async=“false” />
注解配置:
com.alibaba.dubbo.config.annotation.Service 配置暴露服务
com.alibaba.dubbo.config.annotation.Reference配置引用服务
超时设置
Dubbo消费端
全局超时配置
<dubbo:consumer timeout="5000" />
指定接口以及特定方法超时配置
<dubbo:reference interface="com.foo.BarService" timeout="2000">
<dubbo:method name="sayHello" timeout="3000" />
</dubbo:reference>
Dubbo服务端
全局超时配置
<dubbo:provider timeout="5000" />
指定接口以及特定方法超时配置
<dubbo:provider interface="com.foo.BarService" timeout="2000">
<dubbo:method name="sayHello" timeout="3000" />
</dubbo:provider>
支持协议
1、 Dubbo 协议(官方推荐协议)
优点:采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用)
缺点:大文件上传时,可能出现问题(不使用 Dubbo 文件上传)
2、 RMI(Remote Method Invocation)协议
优点:JDK 自带的能力。可与原生 RMI 互操作,基于 TCP 协议
缺点:偶尔连接失败.
3、 Hessian协议
优点:可与原生 Hessian 互操作,基于 HTTP 协议
缺点:需 hessian.jar 支持,http 短连接的**开销大*8
常用设计模式
Dubbo 框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。
工厂模式
Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig 中有个字段:
private static final Protocol protocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDK SPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
装饰器模式
Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
ExceptionFilter
更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。
观察者模式
Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。
动态代理模式
Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类的主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key
工作流程
整体流程:
第一步:provider 向注册中心去注册
第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
第三步:consumer 调用 provider
第四步:consumer 和 provider 都异步通知监控中心
编辑
流程图
总结
最后用一张图来形象的模拟 Dubbo 的使用:
编辑
使用
以上只是我总结的一些关于 dubbo 最基础的原理及使用介绍,至于代码编写过程的 bug 处理经验,环境搭建、项目布局等等问题,需要我们在平时开发中,将系统知识与实战经验相结合去总结,这样才能真正的去掌握这项技术点。
Dubbo 目前是我用到过的最多的分布式框架,写出来的内容也是最多的,不过由于Dubbo用的太多,而 SpringCloud 难度比 Dubbo 要小很多,现在大部分项目都即将开始转投到了 SpringCloud 上面,后面也会出更多的 SpringCloud 相关的文章。