插播面试题
看源码我比较喜欢带着目的性,带着问题看源码
- dubbo中"读接口"和"写接口"有什么区别?
- 谈谈dubbo中的不同容错机制及特点?
集群容错架构设计
首先上一张Dubbo官网的集群容错的架构设计图
其中有几个关键角色
- Directory:其用途是保存 Invoker,可简单类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化
- Router:可以简单理解为过滤器(路由),用户可以配置自己的路由规则,从而过滤掉不符合规则的Invoker
- LoadBalance:LoadBalance是负载均衡的抽象接口,负责从Router路由规则过滤后的Invoker列表中选择一个最终调用的Invoker
- Invoker:这里的 Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
还有一个Cluster没介绍,Cluster又是什么作用呢?
集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。这样一来,服务消费者只需通过这个 Invoker 进行远程调用即可,至于具体调用哪个服务提供者,以及调用失败后如何处理等问题,现在都交给集群模块去处理
Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
既然Cluter中包含了集群容错的场景,那么就从接口层面入手,看下Cluster到底有几种容错机制
可以看到Cluster接口的默认实现是FailoverCluster(失败重试),容错机制共有10种,从经常使用的开始介绍
FailoverCluster
失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)
FailoverClusterInvoker#doInvoke方法实现如下
FailfastCluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
FailfastClusterInvoker#doInvoke方法如下
看到这里,大家可能就会联想到”面试官问Dubbo中读接口、写接口“这个问题的答案了
- 读接口天然具有幂等性,因此失败重试是较好的选择,可以选择使用FailOver失败重试容错机制
- 写接口如果没有代码层面实现幂等,使用FailOver失败重试时可能会产生并发写入问题,因此写操作最好配置为FailfastCluster快速失败容错机制
FailbackCluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
FailbackClusterInvoker#doInvoke方法实现如下
addFailed实现如下
RetryTimerTask重试任务的run方法如下,可以看到,失败时会不断reput,不断进行重试调用
FailsafeCluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
FailsafeClusterInvoker#doInvoke实现如下,比较简单,直接忽略异常,返回一个RpcResult
BroadcastCluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
BroadcastClusterInvoker#doInvoke实现如下
AvailableCluster
遍历提供者列表,找到第一个第一个可用的服务,直接调用,不做负载均衡操作
AvailableClusterInvoker实现如下
ForkingCluster
ForkingClusterInvoker 会在运行时通过线程池创建多个线程,并发调用多个服务提供者。只要有一个服务提供者成功返回了结果,doInvoke 方法就会立即结束运行
ForkingClusterInvoker 的应用场景是在一些对实时性要求比较高读操作(注意是读操作,并行写操作可能不安全)下使用
ForkingClusterInvoker#doInvoke代码如下,核心就是线程池+阻塞队列的使用,通过BlockingQueue#poll
方法来阻塞等待一个返回的rpc调用结果
MergeableCluster
将集群中的调用结果聚合起来返回结果。比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项
该种策略使用较少,不做分析
MockClusterWrapper
服务降级的Mock的实现,在Rpc调用异常,服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略
先看看怎么都有几种使用方式
<dubbo:reference id="demoService" interface="xxx.xxx.DemoService">
<!-- 调用异常时,抛出MockException -->
<dubbo:method name="testService2" mock="throw com.foo.MockException"></dubbo:method>
<!-- 强制返回null,不会走远程调用 -->
<dubbo:method name="testService3" mock="force:return null"></dubbo:method>
<!-- 远程调用异常时返回null -->
<dubbo:method name="testService3" mock="fail:return null"></dubbo:method>
<!-- 通过BarServiceMock构造返回数据 -->
<dubbo:method name="testService3" mock="com.foo.BarServiceMock"></dubbo:method>
</dubbo:reference>
mock="throw com.foo.MockException"
表示调用失败时抛出MockException异常mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响,主要用于测试mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响mock="com.foo.BarServiceMock"
表示调用失败时通过BarServiceMock类来自定义Mock返回数据
更多细节可以看Dubbo官网介绍本地伪装
再看下代码是怎么实现的,MockClusterInvoker#invoke中代码如下
在看下具体的mock返回值方法doMockInvoke的实现逻辑
其核心就是MockInvoker.invoke的实现
看到这里应该对面试题中第二个问题:dubbo中的不同容错机制及特点?有了答案
下面给出总结
容错机制 | 名称 | 特点及应用场景 |
---|---|---|
FailoverCluster | 失败自动切换 | 失败自动切换,当出现失败,重试其它服务器 。通常用于读操作,但重试会带来更长延迟 |
FailfastCluster | 快速失败 | 只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录 |
FailbackCluster | 失败自动恢复 | 失败时,后台记录失败请求,定时重发。通常用于消息通知操作 |
FailsafeCluster | 失败安全 | 出现异常时,直接忽略。通常用于写入审计日志等容忍失败的操作 |
BroadcastCluster | 广播调用多个服务提供者 | 调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息 |
AvailableCluster | 调用一个可用的提供者 | 找到第一个第一个可用的服务,直接调用,不做负载均衡操作 |
ForkingCluster | 并行调用多个服务提供者 | 通过线程池创建多个线程,并发调用多个服务提供者,只要有一个服务提供者成功返回了结果,适用于对实时性要求比较高的场景 |
MergeableCluster | 分组聚合 | 合并返回多个分组的调用结果 |
MockClusterWrapper | Mock服务降级(本地伪装) | 服务降级的Mock的实现,在Rpc调用异常,服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略 |