Dubbo相关面试题
一、Dubbo服务注册和发现的流程?
1、容器启动;
2、服务提供者连接注册中心,将接口信息保存到注册中心中;
3、服务消费者从注册中心订阅所需要的服务并缓存本地,
4、服务提供方有变更时,注册中心将提供一份新的列表给消费者并缓存在消费者端;
5、消费者根据接口地址调用服务提供者;
6、监听器定时监听接口的调用次数和调用时长;
二、dubbo支持哪些协议?
- dubbo:dubbo默认推荐的协议,单一长连接和NIO异步通讯,适用于高并发小数据量的请求,以及消费者远大于提供者,tcp传输协议,Hessian序列化;
- rmi:基于java标准的rmi协议实现,传输参数和返回参数都需要实现Serializable接口,使用java标准序列化方式,采用阻塞式短连接,tcp传输协议,可传文件;
- webservice:基于webservice的远程调用协议,多个短连接,基于Http传输,同步传输;
- http:基于http表单提交的远程调用协议,使用spring的HttpInvoke实现,多个短连接,传输协议http;
- hession:集成Hession服务,基于Http通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器时的默认实现,多个短连接,同步Http传输,Hessian序列化,传入参数较大,可传文件;
- memcache:基于memcache实现的RPC协议;
- redis:基于redis实现的RPC协议
三、Dubbo有哪些注册中心?
- zookeeper:基于zookeeper的watch机制实现数据变更,官方推荐使用
- redis:基于redis的订阅/发布形式实现数据变更
- multicast;
- simple;
四、Dubbo的注册中心挂掉,消费者还能调用吗?
可以,Dubbo启动时,消费者会从注册中心拉取一份接口的信息缓存在本地,每次调用时都会根据本地缓存的地址进行调用;
五、Dubbo提供了哪些负载均衡策略?
- Random LoadBanance:随机选取提供者策略,当调用越频繁,分布越平均
- RoundRobin LoadBanance:随机轮训提供者策略,平均分布,但是存在请求积累问题;
- LeastActive LoadBanance:最少活跃调用策略,活跃数是指调用前后计数差,使慢机器调用最少;比如同时有A和B两个服务,两个服务内部都维护了一个计数器,当A处理一个请求的时候,计数器+1,此时还未处理完,等处理完请求之后,计数器-1,若此时B服务响应较快,很快就处理完请求了,此时A的计数器为1,B的计数器为0,那么下一个请求过来的时候,会发到B服务器上;
- ConstantHash LoadBanance:一致性hash策略,使相同参数请求总是发送到统一提供者,当有一台宕机了后,可以基于虚拟节点,分发至其他提供者上;
六、Dubbo的集群容错方法有哪些?
- Failover Cluster:失败自动切换,当出现失败重试其他服务器,通常用于读操作,默认重试2次;
- Failfast Cluster:快速失败,只发起一次调用,当出现失败的时候,立即报错,通常用于非幂等性的写操作,比如新增记录;
- Failsafe Cluster:失败安全,当出现异常时,直接忽略,通常应用于写入日志操作;
- Failback Cluster:失败自动恢复,当出现失败时,后台记录请求,定时重发。通常用于消息通知等操作;
- Forking Cluster:并行调用多个服务器,只要有一个成功则返回,适用于实时性很高的读操作,但是会对服务器造成很大压力,可以通过"forks = 2"来设置最大并行数;
- Broadcast Cluster:广播调用所有提供者,只要有一个失败则都失败,通常应用于通知所有的提供者更新本地缓存等操作;
七、Dubbo提供了哪些序列化方式?
默认使用Hessian序列化,还提供了fastjson、Dubbo以及java自带序列化;
八、Dubbo超时时间设置
dubbo推荐在服务提供者端设置超时时间,因为服务提供者更能清楚的知道自己服务的特性,若是在消费者端设置了超时时间,则以消费者端的设置为准,因为消费者端设置更加灵活;
当超时之后,dubbo默认会重试两次;
九、服务上线如何兼容旧版本?
通过版本号version来进行过渡,不同版本号的服务注册到注册中心,版本号不同的服务相互之间不引用;
十、当一个服务有多个不同的实现时如何操作?
通过对不同的实现通过group分组来区分,不同的分组有不同的响应,服务的提供者和消费者指向同一个分组即可
十一、当开发过程中,多个开发人员同时将接口注册到同一个注册中心时,会发生什么,如何解决?
可能会出现调用的服务在对方电脑上,有以下两个方式解决:
1、每个开发人员的接口设置各自的分组,项目之间调用互不影响;
2、可以通过设置点对点直连的方式,在消费者端指定到本地的服务地址;
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20880" />十二、Dubbo服务之间的调用是阻塞的吗?
Dubbo默认是同步等待结果阻塞的,支持异步调用;
Dubbo是基于NIO的非阻塞式实现并行调用,调用过程如下:
1、工作线程调用findUser方法;
2、IO线程负责调用对象的服务端;
3、IO线程setFuture到RpcContent中
4、工作线程读取Future的值,若为空,则调用wait方法等待;
6、服务端响应结果到调用者;
7、IO线程将返回结果set到future中并唤醒工作线程;
8、工作线程读取future并返回;
十三、Dubbo分布式事务?
分布式事务的实现主要有以下五种方案:
1、XA方案;
2、TCC方案;
3、本地消息表;
4、可靠消息最终一致性方案;
5、最大努力通知方案
基于XA协议的两阶段提交:
XA分为两部分,事务管理器和本地资源管理器,本地资源管理器由各个数据库实现,而事务管理器作为一个管理者,管理着所有本地资源管理器的提交和回滚;
所有的参与者准备执行事务并锁住资源,参与者ready时,向事务管理器发送就绪的指令,当所有的参与者都达到就绪时,事务管理器向所有的参与者发送commit命令;
缺点:效率低下,准备节点的成本很大,性能差;提交前,发生故障难以恢复;
TCC方案:
TCC方案分为Try和Confirm、Cancel,一般在try里面锁住资源,资源校验;Confirm里面确认执行业务操作;Cancel里面取消执行业务操作;
github上面有基于tcc的java实现:tcc-transaction
可靠消息最终一致性方案:
基于RocketMQ实现分布式事务,解决最终事务一致性方案:
为什么不采用kafka等消息中间件呢,因为考虑到发送消息和处理业务没办法保证同时成功,所以基于RocketMQ的预发消息和确认消息来实现;
以转账为例:A转账给B,所以A需要减100元,B需要加100元,
1、先发送B+100元的预发消息到RocketMQ中,此时该条消息只存在commitLog中,在consumeQueue中不可见,也就无法被消费;
2、在执行扣款逻辑,若扣款成功则发送确认消息到RocketMQ中;
3、若扣款失败,则预发消息不会被确认,也就无法被消费;
4、若扣款成功、发送确认消息失败,则通过状态回查的方式来确定消息状态,也就是RocketMQ会定时遍历commitlog中的消息,来确认该消息最终是commit还是rollback,如果发现本地业务为执行失败则rollback,如果执行成功就commit;
5、如何进行状态回查:可以在本地设计一个事务状态表,记录transactional事务的状态,若业务执行成功则为success,为业务执行失败则为fail;
十四、Dubbo提供哪些可供配置的参数?
1、<dubbo:service/> 服务提供者暴露服务配置,定义服务的元信息。 2、<dubbo:reference/> 服务消费者引用服务配置。 3、<dubbo:protocol/> 服务提供者协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。如果需要支持多协议,可以声明多个 <dubbo:protocol> 标签,并在 <dubbo:service> 中通过 protocol 属性指定使用的协议。 4、<dubbo:application/> 应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者;其中name属性是必填项,当前应用名称,用于计算当前注册中心应用之间的依赖关系,注意消费者和提供者的应用名称不能一样。 5、<dubbo:module/> 模块配置,用于配置当前模块信息,可选;其中name属性是必填项,表示模块名称,用于当前注册中心计算模块间的依赖关系。 6、<dubbo:registry/> 注册中心配置,用于配置连接注册中心相关信息。 7、<dubbo:monitor/> 监控中心配置,用于配置连接监控中心相关信息,可选。 8、<dubbo:provider/> 提供方的缺省值,当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值,可选。 9、<dubbo:consumer/> 消费方缺省配置,当ReferenceConfig某属性没有配置时,采用此缺省值,可选。 10、<dubbo:method/> 方法配置,用于ServiceConfig和ReferenceConfig指定方法级的配置信息。该标签作为<dubbo:server>或<dubbo:reference>的子标签,可以设置一些参数,比如:loadbalance、retries、timeout; 11、<dubbo:argument/> 用于指定方法参数配置。十五、dubbo的SPI机制
例子,基于dubbo的spi机制扩展负载均衡策略。
整个过程可以概括为三个核心步骤:实现接口、配置 SPI 文件、引用使用。
核心步骤详解
- 编写自定义实现类
首先,你需要创建一个 Java 类来实现 Dubbo 的 LoadBalance 接口。通常我们会继承 AbstractLoadBalance 抽象类,因为它帮我们处理了很多公共逻辑(如权重计算),我们只需要实现核心的 doSelect 方法。
场景举例: 假设我们需要一个策略,总是优先选择 IP 地址最小的那个服务提供者(常用于测试环境强制指定机器)。
packagecom.example.dubbo.extend;importorg.apache.dubbo.common.URL;importorg.apache.dubbo.rpc.Invocation;importorg.apache.dubbo.rpc.Invoker;importorg.apache.dubbo.rpc.RpcException;importorg.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance;importjava.util.List;// 1. 继承 AbstractLoadBalancepublicclassMinIpLoadBalanceextendsAbstractLoadBalance{@Overrideprotected<T>Invoker<T>doSelect(List<Invoker<T>>invokers,URLurl,Invocationinvocation)throwsRpcException{// 2. 实现你的核心算法逻辑// 这里演示:找出 IP 地址字符串排序最小的 Invokerreturninvokers.stream().min((i1,i2)->{Stringip1=i1.getUrl().getHost();Stringip2=i2.getUrl().getHost();returnip1.compareTo(ip2);}).orElse(invokers.get(0));// 如果为空,返回第一个兜底}}- 创建 SPI 配置文件 (关键步骤)
这是实现“热插拔”的关键。你需要告诉 Dubbo 框架:“我有一个新的负载均衡算法,它的名字叫 minip,对应的类是 MinIpLoadBalance”。
文件路径:在项目的资源目录 src/main/resources 下创建文件夹 META-INF/dubbo/。
文件名:必须是接口的全限定名,即 org.apache.dubbo.rpc.cluster.LoadBalance。
文件内容:采用 key=value 格式。key 是你将来在配置中使用的名字,value 是类的全限定名。
文件内容示例:
# 文件路径:src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalanceminip=com.example.dubbo.extend.MinIpLoadBalance注意:minip 这个名字是你自己定义的,后面配置时就要用这个名字。
3. 配置并使用自定义策略
完成上述两步后,你的自定义算法就已经被 Dubbo 识别了。接下来只需要在服务引用时指定使用它。
方式一:XML 配置
在消费者的 dubbo:reference 标签中指定 loadbalance 属性:
<dubbo:reference id="demoService"interface="com.example.DemoService"loadbalance="minip"/>方式二:注解配置
如果你使用的是 Spring Boot 或注解开发,可以直接在 @DubboReference 或 @Reference 注解中指定:
@DubboReference(loadbalance="minip")privateDemoServicedemoService;进阶:更复杂的场景(如机房优先)
除了简单的 IP 排序,你可能需要根据业务元数据(如机房信息)来路由。Dubbo 的 URL 参数机制非常强大,你可以在实现类中读取这些参数。
场景: 消费者优先调用同机房的提供者,如果同机房不可用,再降级调用其他机房。
publicclassZoneAwareLoadBalanceextendsAbstractLoadBalance{@Overrideprotected<T>Invoker<T>doSelect(List<Invoker<T>>invokers,URLurl,Invocationinvocation){// 1. 获取当前消费者所在的机房(假设通过 URL 参数传入 zone=shanghai)StringconsumerZone=url.getParameter("zone","default");// 2. 筛选出同机房的提供者List<Invoker<T>>sameZoneInvokers=invokers.stream().filter(invoker->consumerZone.equals(invoker.getUrl().getParameter("zone"))).collect(Collectors.toList());// 3. 如果同机房有机器,就在同机房里随机选(或轮询);否则降级到所有机器里选if(!sameZoneInvokers.isEmpty()){returndoRandom(sameZoneInvokers);}returndoRandom(invokers);}// ... 省略 doRandom 具体实现}配置使用:
// 在引用服务时,透传当前机房的 zone 参数@DubboReference(loadbalance="zoneAware",parameters=@Parameter(key="zone",value="shanghai"))privateUserServiceuserService;总结
要在 Dubbo 下使用自定义负载均衡:
写代码:实现 LoadBalance 接口。
配文件:在 META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance 文件中注册别名。
改配置:在 @Reference 或 XML 中设置 loadbalance=“你的别名”。
这种方式完全解耦,你甚至可以打包成一个独立的 jar 包,引入到项目中即可生效,无需修改 Dubbo 框架本身的任何代码。
