# Dubbo面试:高频面试题汇总

# Dubbo服务注册和发现的流程
各种角色如下
- Provider:暴露服务的服务提供方
- Consumer:调用远程服务消费方
- Registry:服务注册与发现注册中心
- Monitor:监控中心和访问调用统计
- Container:服务运行容器
Dubbo服务注册和发现的流程如下
- 服务容器Container负责启动,加载,运行服务提供者。
- 服务提供者Provider在启动时,向注册中心注册自己提供的服务。
- 服务消费者Consumer在启动时,向注册中心订阅自己所需的服务。
- 注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者Consumer,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者Consumer和提供者Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。
# Java SPI和Dubbo SPI的区别
Java SPI:
JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源。
Dubbo SPI
- 对 Dubbo 进行扩展,不需要改动 Dubbo 的源码
- 延迟加载,可以一次只加载自己想要加载的扩展实现。
- 增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
- Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。
# Dubbo服务导出流程
服务导出和导入的流程在之前的文章详细分析过了,所以放一下流程图,然后简单汇总一下。整体流程分为3个部分
Dubbo服务提供者在容器启动时会被包装为ServiceBean,容器启动过程中接收到ContextRefreshedEvent事件,开始服务导出。
此时会根据配置决定导出方式,有如下三种,1 导出本地服务,2导出远程服务,3不导出服务。
按照导出远程服务接着分析,此时会调用JavassistProxyFactory生成Server Stub即AbstractProxyInvoker对象。接着会执行到RegistryProtocol#refer方法,启动Netty服务,获取注册中心,向注册中心注册服务
根据配置的协议,导出为具体的Invoker,如DubboInvoker,InjvmInvoker。
由于Dubbo SPI的自动包装特性,中间会执行3个包装类的方法。

我们以DubboInvoker导出为例子,主要流程就是启动NettyServer

# Dubbo服务提供者接收请求并返回的流程
网络通信我们一般情况下选择Netty,当收到消息的时候会回调NettyServerHandler#channelRead方法,传入收到的消息,然后消息会经过一系列ChannelHandler的处理,最终会调用到DubboProtocol中匿名内部类ExchangeHandlerAdapter#received方法,调用信息找到对应的Invoker(Server Stub)
从服务导出的时候我们可以看到Server Stub对象为AbstractInvoker,然后通过AbstractInvoker调用本地方法并返回结果
接收请求和发送响应的过程会经过一些列的ChannelHandler,ChannelHandler的作用如下。
| ChannelHandler | 作用 |
|---|---|
| NettyServerHandler | 处理Netty服务端事件,如连接,断开,读取,写入,发生异常 |
| MultiMessageHandler | 多消息报文批处理 |
| HeartbeatHandler | 心跳处理 |
| AllChannelHandler | 将Netty的所有请求放到业务线程池中去处理 |
| DecodeHandler | 对消息进行解码 |
| HeaderExchangeHandler | 封装处理 Request/Reponse,和 telnet请求 |
| ExchangeHandlerAdapter | 查找服务方法并调用 |
ChannelHandler的调用次序就是如下图的装饰顺序 
# Dubbo服务导入流程
Dubbo服务客户端在服务启动的时候会被包装为ReferenceBean对象 ReferenceBean实现了FactoryBean接口,当想获取这个Bean时,会调用FactoryBean#getObject方法返回生产的Bean,FactoryBean#getObject方法里面有服务导入的逻辑。
服务导入的时机有2个时间点
- 懒汉式,当服务被注入其它类时,才会启动引入流程,即用到才会引入
- 饿汉式,Bean生命周期属性赋值阶段,执行InitializingBean#afterPropertiesSet方法,手动调用FactoryBean#getObject方法,开始引入
public class ReferenceBean<T> extends ReferenceConfig<T> implements
FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
导入的过程中会生成Client Sub,即ReferenceConfig#createProxy方法。追着这个生成代理对象的方法看就行了。假设我们只配置了一个注册中心,此时会执行到RegistryProtocol#refer方法。
RegistryProtocol从RegistryDirectory(服务目录,你可以认为是一个动态的Invoker集合)获取Invoker,然后对这个Invoker根据集群策略包装一下,最终用这个Invoker和引入服务的接口生成最终的Client Stub
服务目录会监听注册中心服务节点的变化,当服务节点发生变化时,会触发响应的事件,回调RegistryDirectory#notify方法,重新生成Invoker。
根据引入的协议生成不同的Invoker,这里假设生成的是DubboInvoker。
创建DubboInvoker的时候会创建NettyClient并启动,并发起网络调用

# Dubbo消费端发起调用并接收请求的过程
Client Stub调用任务方法都会经过InvokerInvocationHandler#invoke方法,然后通过服务引入过程包装的DubboInvoker发起调用,中间会通过装饰类执行服务降级和集群容错的逻辑,最终DubboInvoker发起网络调用
网络调用的过程会经过一系列的ChannelHandler,和服务端的套路基本一致
| ChannelHandler | 作用 |
|---|---|
| NettyClientHandler | 处理Netty客户端事件,如连接,断开,读取,写入,发生异常 |
| NettyServerHandler | 处理Netty服务端事件,如连接,断开,读取,写入,发生异常 |
| MultiMessageHandler | 多消息报文批处理 |
| HeartbeatHandler | 心跳处理 |
| AllChannelHandler | 将Netty的所有请求放到业务线程池中去处理 |
| DecodeHandler | 对消息进行解码 |
| HeaderExchangeHandler | 封装处理 Request/Reponse,和 telnet请求 |
| ExchangeHandlerAdapter | 查找服务方法并调用 |

# Dubbo常见的注册中心有哪些?
Multicas注册中心:Multicast 注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现,基于网络中组播传输实现。 Zookeeper注册中心:基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更。 Redis注册中心:基于 Redis 实现,采用 key/map 存储,key 存储服务名和类型,map 中 key 存储服务 url,value 服务过期时间。基于 Redis 的发布/订阅模式通知数据变更。 Simple注册中心:本身就是一个Dubbo服务
推荐使用 Zookeeper 作为注册中心
# Dubbo常见的集群容错策略有哪些?
| 实现类 | 解释 |
|---|---|
| AvailableCluster | 找到一个可用的节点,直接发起调用 |
| FailoverCluster | 失败重试(默认) |
| FailfastCluster | 快速失败 |
| FailsafeCluster | 安全失败 |
| FailbackCluster | 失败自动恢复 |
| ForkingCluster | 并行调用 |
| BroadcastCluster | 广播调用 |
Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。 Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。 Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
读操作建议使用 Failover 失败自动切换,默认重试两次其他服务器。写操作建议使用 Failfast 快速失败,发一次调用失败就立即报错。
# Dubbo常见的负载均衡策略有哪些?
| 实现类 | 解释 |
|---|---|
| RandomLoadBalance | 随机策略(默认) |
| RoundRobinLoadBalance | 轮询策略 |
| LeastActiveLoadBalance | 最少活跃调用数 |
| ConsistentHashLoadBalance | 一致性hash策略 |
# Dubbo常见的序列化方式有哪些?
| 方式 | 解释 |
|---|---|
| dubbo序列化 | 阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它 |
| hessian2序列化 | hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式 |
| json序列化 | 目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化 |
| java序列化 | 主要是采用JDK自带的Java序列化实现,性能很不理想 |
| Kryo | 性能较高,后续可能被设置为默认实现 |
| FST | 性能较高,后续可能被设置为默认实现 |
# Dubbo 支持哪些协议,每种协议的应用场景,优缺点?
Dubbo: 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议 TCP,异步 Hessian 序列化。Dubbo推荐使用dubbo协议。
RMI: 采用 JDK 标准的 RMI 协议实现,传输参数和返回参数对象需要实现 Serializable 接口,使用 Java 标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议 TCP。 多个短连接 TCP 协议传输,同步传输,适用常规的远程服务调用和 RMI 互操作。在依赖低版本的 Common-Collections 包,Java 序列化存在安全漏洞。
WebService:基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用。
HTTP: 基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实现。多个短连接,传输协议 HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用。
Hessian:集成 Hessian 服务,基于 HTTP 通讯,采用 Servlet 暴露服务,Dubbo 内嵌 Jetty 作为服务器时默认实现,提供与 Hession 服务互操作。多个短连接,同步 HTTP 传输,Hessian 序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件。
Memcache:基于 Memcache实现的 RPC 协议。
Redis:基于 Redis 实现的RPC协议。
# Dubbo为什么不适合大数据量的服务,如传文件,传视频等?
因 dubbo 协议默认采用单一长连接(一个客户端和一个服务端只建立一个长连接),如果每次请求的数据包大小为 500KByte,假设网络为千兆网卡,每条连接最大 7MByte(不同的环境可能不一样,供参考)。 单个服务提供者的 TPS(每秒处理事务数)最大为:128MByte / 500KByte = 262。 单个消费者调用单个服务提供者的 TPS(每秒处理事务数)最大为:7MByte / 500KByte = 14。如果能接受,可以考虑使用,否则网络将成为瓶颈