Skip to content

Dubbo服务框架面试题库 ​

生成时间:2026-05-21 23:05 | 共10题


【面试题】1. Dubbo的URL总线设计是什么?为什么说URL是Dubbo的配置总线?举例说明URL在服务暴露和引用中的作用。 ​

【标准答案】URL是Dubbo里最核心的数据模型,贯穿整个调用链。它的格式是protocol://username:password@host:port/path?key=value&key=value,所有配置信息最终都会收敛到一个URL对象上。

为什么叫"配置总线"?因为在Dubbo里,不管你是服务暴露、服务引用、路由、负载均衡、还是协议选择,所有组件拿到的配置都从URL里取。比如服务暴露时,Provider会构造一个dubbo://192.168.1.10:20880/com.xxx.UserService?timeout=3000&retries=2这样的URL,注册到Registry;Consumer引用服务时,Registry推送过来的也是URL列表,Consumer根据URL里的参数做负载均衡和调用。

举个例子:自定义Filter里你想拿超时时间,不是从某个配置中心取,而是直接invoker.getUrl().getParameter("timeout")。Protocol层选择用什么序列化,也是从URL的serialization参数读。这就是"总线"的含义——所有组件通过URL通信,不依赖其他上下文对象,做到了松耦合。

面试时加一句:这种设计借鉴了操作系统的"文件即一切"思想,Dubbo是"URL即一切"。


【面试题】2. Dubbo SPI和Java SPI的核心区别是什么?@Adaptive注解的作用是什么?在源码层面是如何实现的? ​

【标准答案】三个核心区别:

第一,按需加载。Java SPI会一次性实例化所有实现类,Dubbo SPI支持按key获取指定实现,不会全量加载。配置文件格式也不同:Java SPI是每行一个全限定类名,Dubbo SPI是key=全限定类名的键值对格式。

第二,依赖注入。Dubbo SPI支持自动装配,实现类里如果有setter方法且参数类型是其他扩展点,SPI容器会自动注入自适应实现(Adaptive实例)。Java SPI不支持。

第三,自适应扩展。这是Dubbo SPI最精妙的设计。@Adaptive注解标注在接口方法上,运行时Dubbo会动态生成一个代理类,根据URL参数决定用哪个实现。比如Protocol接口的@Adaptive方法会根据URL的protocol参数选择对应的Protocol扩展。

源码层面:ExtensionLoader.getAdaptiveExtension()会先用@Adaptive标注的实现类,如果没有,就用ExtensionLoader.createAdaptiveExtensionClassCode()动态生成Java源码并编译。生成的代理类大概长这样:根据URL里的参数值,switch到对应扩展实现。这就是为什么@Adaptive加在方法上可以做到运行时动态选型。


【面试题】3. Dubbo服务暴露的完整流程是什么?本地暴露和远程暴露的区别和目的是什么? ​

【标准答案】服务暴露分两大步:本地暴露和远程暴露。

入口是ServiceConfig.doExport(),流程是:

  1. 检查配置合法性
  2. 构造URL(把所有配置项塞进URL参数)
  3. 本地暴露:调用proxyFactory.getInvoker(ref, interfaceClass, localUrl)创建Invoker,再用protocol.export(invoker)导出。这里用的是injvm协议,走JVM内部调用,不经过网络。
  4. 远程暴露:同样创建Invoker,但用配置的协议(默认dubbo)导出。流程是ProxyFactory.getInvoker() → Protocol.export() → 打开Server监听端口 → 注册到Registry。

为什么要本地暴露? 当Consumer和Provider在同一个JVM里时(比如一个应用既提供服务又调用自己),走本地暴露可以避免网络开销,直接走方法调用。Dubbo在服务引用时会优先判断本地是否有同接口的暴露,有就走injvm。

远程暴露的关键:ProtocolListenerWrapper和ProtocolFilterWrapper会包装原始Protocol,在export时构建Filter链。QosProtocolWrapper会启动QoS服务。

面试加分点:提到doExportUrls()方法里exportLocal(url)先于远程暴露执行,以及ServiceConfig是延迟初始化的(Spring容器启动后才触发)。


【面试题】4. Dubbo的Invoker是什么?它在服务端和消费端分别代表什么?ProxyFactory如何将Invoker转换成业务代理对象? ​

【标准答案】Invoker是Dubbo最核心的抽象模型,一句话概括:Invoker是可执行体的抽象。

在Provider端,Invoker包装了服务实现类(ref),调用invoker.invoke(invocation)最终会通过反射调用到实际业务方法。ProxyFactory.getInvoker(ref, interfaceClass, url)做的就是把业务对象包成Invoker。

在Consumer端,Invoker代表一个远程服务的可调用句柄,invoker.invoke(invocation)会发起网络请求。Consumer端的Invoker由Directory列表管理,经过Cluster、Router、LoadBalance筛选后选出最终调用的Invoker。

ProxyFactory有两个方向:

  • getInvoker():业务对象 → Invoker(Provider端用)
  • getProxy():Invoker → 业务代理对象(Consumer端用)

Consumer端服务引用时,ProxyFactory.getProxy(invoker)用动态代理(默认Javassist)生成接口的代理类,代理类的每个方法调用都会转成invoker.invoke(RpcInvocation),从而进入Dubbo调用链。

面试关键:理解了Invoker,就理解了Dubbo的骨架——所有Filter、Router、Cluster都是在Invoker层面做装饰和转发。


【面试题】5. Dubbo的集群容错策略有哪些?Failover和Failback的区别是什么?在实际项目中如何选型? ​

【标准答案】六种策略:

策略行为场景
Failover(默认)失败自动切换到其他Provider重试,默认重试2次读操作、幂等写操作
Failfast失败立即报错,不重试非幂等写操作(如下单、扣款)
Failsafe失败忽略,吃掉异常日志记录、监控上报等可容忍失败的操作
Failback失败后记录请求,定时重发消息通知类,最终一致性要求
Forking并行调用多个Provider,取最快返回的实时性要求高的读操作
Broadcast逐个调用所有Provider,任一报错则报错缓存更新、通知所有节点

Failover vs Failback的核心区别:Failover是"立即换一个重试",适合临时性故障(网络抖动);Failback是"先记下来后面补",适合不紧急但需要最终成功的场景。

选型实战:

  • 查询接口用Failover(retries=2)
  • 下单/支付用Failfast(cluster="failfast"),避免重复扣款
  • 发短信用Failback(cluster="failback"),失败后后台慢慢重试
  • 缓存同步用Broadcast(cluster="broadcast"),确保所有节点都更新

Mock降级也属于集群容错体系,配置mock="return null"可以在调用失败时返回兜底值,实现服务降级。


【面试题】6. Dubbo的Filter链是如何工作的?Consumer端和Provider端的Filter分别在哪个阶段执行?如何自定义一个Filter? ​

【标准答案】Filter基于责任链模式,在Invoker外层包装。

执行阶段:

  • Consumer端Filter:在ProtocolFilterWrapper.buildInvokerChain()时,筛选group=consumer或group=common的Filter,包裹在最外层。调用链:Consumer代理 → Consumer Filter链 → Cluster → 网络 → Provider Filter链 → 实际Invoker
  • Provider端Filter:在服务暴露时export阶段,筛选group=provider或group=common的Filter,包裹在Exporter的Invoker外层。

执行顺序:按@Activate的order值排序,order越小越先执行(Consumer端),Provider端同理。

内置Filter举例:ConsumerContextFilter设置RpcContext、AccessLogFilter记录访问日志、ActiveLimitFilter控制并发数、ExecuteLimitFilter控制执行数、TimeoutFilter警告超时调用、TokenFilter令牌验证、TpsLimitFilter限流。

自定义Filter三步:

  1. 实现Filter接口的invoke()方法
  2. 加@Activate注解指定group和order:@Activate(group = {CommonConstants.PROVIDER}, order = -9000)
  3. 在META-INF/dubbo/org.apache.dubbo.rpc.Filter文件里写myFilter=com.xxx.MyFilter

关键源码:ProtocolFilterWrapper.buildInvokerChain()遍历所有激活的Filter,用匿名Invoker逐层嵌套,形成链式调用。


【面试题】7. Dubbo3的应用级服务发现和传统接口级服务发现有什么区别?为什么要做这个升级?迁移过程中需要注意什么? ​

【标准答案】接口级服务发现(Dubbo2):每个接口×每个Provider注册一个URL到Registry,如果有100个接口、10个Provider实例,就注册1000个URL。

应用级服务发现(Dubbo3):每个应用实例只注册一条元数据,接口和方法的映射关系通过MetadataService暴露。10个Provider实例只注册10条,接口信息另外存。

为什么要升级? 核心问题是规模:

  1. 注册中心压力:接口级在大型集群(万级实例、千级接口)下,Registry上要存百万级URL,推送量大、存储大
  2. Consumer内存压力:每个Consumer要缓存所有Provider的URL,内存占用随接口数线性增长
  3. 与云原生对齐:Spring Cloud/K8s都是应用级模型,接口级无法直接对接Service Mesh

迁移注意点:

  1. Dubbo3默认双注册(接口级+应用级同时注册),Consumer优先用应用级,保证兼容
  2. 可以通过-Ddubbo.application.register-mode=instance切纯应用级
  3. MetadataService默认用内嵌的MetadataReport存储,需要确认元数据中心可用
  4. 服务端需要暴露MetadataService端口(默认20880复用)
  5. 如果用了自定义Router或Registry,需要确认是否适配应用级模型

面试加分:提到Dubbo3的Service Discovery是双模式平滑迁移,不是一刀切,这是设计上的亮点。


【面试题】8. Dubbo的服务预热(Warmup)机制是什么?权重动态调节在灰度发布中怎么用?新上线节点的流量是如何渐进式放大的? ​

【标准答案】服务预热是Dubbo对刚启动Provider的保护机制。核心思想:一个服务刚启动时,JIT编译还没完成、缓存还没热、连接池还没建好,不应该立刻承担全部流量。

源码实现:在AbstractLoadBalance的getWeight()方法里:

java
if (uptime < warmup) {
    weight = (int)(uptime * weight / warmup);
}

默认warmup=10分钟。如果一个Provider权重是100,刚启动1分钟时,实际权重=1×100/600=0.17(取整后很小),随时间线性增长到10分钟后达到100。

权重动态调节:通过Dubbo Admin或动态配置,可以实时修改某个Provider的权重:

override://192.168.1.10:20880/com.xxx.UserService?weight=200

权重越大,Random和RoundRobin策略分配到的流量越多。

灰度发布实战:

  1. 新版本部署后,先设weight=10,只引入少量流量
  2. 观察错误率、延迟指标,正常则逐步提到50、100
  3. 如果异常,直接设weight=0摘流量(等价于摘除)
  4. 全部验证通过后,老版本weight=0下线

注意:ConsistentHash策略不受权重影响,灰度时不要用ConsistentHash。


【面试题】9. Dubbo的泛化调用是什么?在什么场景下使用?GenericService和GenericImpl有什么区别? ​

【标准答案】泛化调用是Consumer端不需要依赖Provider的接口jar包就能发起调用的机制。

原理:正常调用需要Consumer端有接口class,泛化调用把方法名、参数类型、参数值全部用字符串和Map传递,不需要class文件。底层用GenericFilter在Provider端将泛化参数还原成真实类型再调用。

GenericService用法:

xml
<dubbo:reference id="genericService" interface="com.xxx.UserService" generic="true" />

调用时:

java
GenericService service = (GenericService) context.getBean("genericService");
Object result = service.$invoke("getUser", new String[]{"java.lang.Long"}, new Object[]{1L});

GenericImpl是泛化实现,Provider端不需要接口class就能暴露服务,用GenericService作为业务实现。场景较少,主要在网关等中间层。

典型场景:

  1. API网关:网关不依赖任何业务jar包,通过泛化调用转发请求到后端Dubbo服务
  2. 测试平台:动态输入接口名、方法名、参数进行测试
  3. 通用代理服务:做一个通用的Dubbo代理,路由到不同服务

注意事项:

  • 泛化调用的参数类型必须写全限定类名(如java.lang.Long不能写Long)
  • 复杂对象用Map表示,key是属性名,value是属性值
  • 泛化调用走GenericFilter做参数转换,有一定性能损耗
  • Dubbo3支持泛化调用与Triple协议结合,支持跨语言

【面试题】10. Dubbo连接控制(connections/accepts)和服务线程模型的五种Dispatcher分别是什么?生产环境如何选择线程模型? ​

【标准答案】连接控制:

  • connections:Consumer端配置,限制对每个Provider建立的长连接数,默认0(不限)。设为1表示只用一个连接,适合小流量高优先级服务;多连接适合大文件传输场景。
  • accepts:Provider端配置,限制同时接受的连接数上限,超出会拒绝连接。防止连接风暴打垮Provider。

五种Dispatcher线程模型:

DispatcherIO线程处理业务线程处理特点
All(默认)-请求、响应、连接断开、心跳所有消息都切到业务线程池,IO线程不阻塞
Direct请求、响应、连接断开、心跳-全在IO线程执行,零线程切换,但IO线程可能阻塞
MessageOnly连接断开、心跳请求、响应只把业务消息切业务线程,控制类走IO线程
Execution连接断开、心跳、响应请求只把请求切业务线程,响应走IO线程(不排队)
ConnectionOrdered心跳请求、响应连接断开放独立线程池(避免挤占业务线程),请求响应走业务线程

生产环境选型:

  • 默认All:最安全,IO线程不会被业务代码阻塞,适合绝大多数场景
  • MessageOnly:吞吐量更高,但IO线程处理连接事件时如果耗时长会拖慢整个连接
  • Execution:响应不排队直接发,适合对返回时效敏感的场景
  • Direct:极致低延迟,但业务代码绝不能有IO操作和慢逻辑,否则整个Server阻塞
  • ConnectionOrdered:适合连接频繁断开重建的环境(如容器漂移),避免连接断开影响业务

关键配置:

xml
<dubbo:protocol dispatcher="all" threadpool="fixed" threads="200" accepts="500" />

threadpool可选fixed/cached/limited/eager,生产环境推荐fixed(固定线程数,防OOM)。