dubbo 相关面试题整理

1、知道什么是 RPC 么?

答:RPC 就是 Remote Procedure Call,远程过程调用,它相对应的是本地过程调用。

2、那为什么要有 RPC,HTTP 不好么?

因为 RPC 和 HTTP 就不是一个层级的东西,所以严格意义上这两个没有可比性,也不应该来作比较,而题目问的就是把这两个作为比较了。

HTTP 只是传输协议,协议只是规范了一定的交流格式,而且 RPC 是早于 HTTP 的,所以真要问也是问有 RPC 为什么还要 HTTP。

RPC 对比的是本地过程调用,是用来作为分布式系统之间的通信,它可以用 HTTP 来传输,也可以基于 TCP 自定义协议传输。

所以你要先提出这两个不是一个层级的东西,没有可比性,然后再表现一下,可以说 HTTP 协议比较冗余,所以 RPC 大多都是基于 TCP 自定义协议,定制化的才是最适合自己的。

当然也有基于 HTTP 协议的 RPC 框架,毕竟 HTTP 是公开的协议,比较通用,像 HTTP2 已经做了相应的压缩了,而且系统之间的调用都在内网,所以说影响也不会很大。

3、Dubbo的核心功能?

主要就是如下3个核心功能:

  •  Remoting:网络通信框架,提供对多种NIO框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。
  •  Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  •  Registry:服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

4、Dubbo的核心组件

就简单的提一下现在这几个角色。

史上最强Dubbo面试28题答案详解:核心功能+服务治理+架构设计等

流程说明:

  •  Provider(提供者)绑定指定端口并启动服务
  •  指供者连接注册中心,并发本机IP、端口、应用信息和提供服务信息发送至注册中心存储
  •  Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心
  •  注册中心根据 消费 者所求服务信息匹配对应的提供者列表发送至Consumer 应用缓存。
  •  Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。
  •  Provider 状态变更会实时通知注册中心、在由注册中心实时推送至Consumer

5、Dubbo的架构设计?

Dubbo框架设计一共划分了下面层次:

服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。

配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心。

服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton。

服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心。

集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心。

监控层(Monitor):RPC调用次数和调用时间监控。

远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。

信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心。

网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心

6、Dubbo支持哪些协议,每种协议的应用场景,优缺点?

  •  dubbo: 单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP,异步,Hessian序列化
  •  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: 基于memcached实现的RPC协议
  •  redis: 基于redis实现的RPC协议

7、Dubbo有些哪些注册中心?

  •  Multicast注册中心: Multicast注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现。基于网络中组播传输实现;
  •  Zookeeper注册中心: 基于分布式协调系统Zookeeper实现,采用Zookeeper的watch机制实现数据变更;
  •  redis注册中心: 基于redis实现,采用key/Map存储,住key存储服务名和类型,Map中key存储服务URL,value服务过期时间。基于redis的发布/订阅模式通知数据变更;
  •  Simple注册中心

8、Dubbo集群提供了哪些负载均衡策略?

  •  Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀;
  •  RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题;
  •  LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求;
  •  ConstantHash LoadBalance: 一致性Hash策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动;

缺省时为Random随机调用

9、Dubbo的集群容错方案有哪些?

  1.  Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
  2.  Failfast Cluster: 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  3.  Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  4.  Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  5.  Forking Cluster: 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。
  6.  Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。

10、Dubbo支持哪些序列化方式?

默认使用Hessian序列化,还有Duddo、FastJson、Java自带序列化。

11、看过源码,那说下服务暴露的流程?

        Dubbo 会在 Spring 实例化完 bean 之后,在刷新容器最后一步发布 ContextRefreshEvent 事件的时候,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个 方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法

     会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用。

     会通过 proxyFactory.getInvoker,利用 javassist 来进行动态代理,封装真的实现类,然后再通过 URL 参数选择对应的协议来进行 protocol.export,默认是 Dubbo 协议。

     在第一次暴露的时候会调用 createServer 来创建 Server,默认是 NettyServer。

然后将 export 得到的 exporter 存入一个 Map 中,供之后的远程调用查找,然后会向注册中心注册提供者的信息。

    

12、看过源码,那说下服务引入的流程?

服务的引入时机有两种,第一种是饿汉式,第二种是懒汉式。

饿汉式就是加载完毕就会引入,懒汉式是只有当这个服务被注入到其他类中时启动引入流程,默认是懒汉式。

会先根据配置参数组装成 URL ,一般而言我们都会配置的注册中心,所以会构建 RegistryDirectory 向注册中心注册消费者的信息,并且订阅提供者、配置、路由等节点。

得知提供者的信息之后会进入 Dubbo 协议的引入,会创建 Invoker ,期间会包含 NettyClient,来进行远程通信,最后通过 Cluster 来包装 Invoker,默认是 FailoverCluster,最终返回代理类。

13、看过源码,那说下服务调用的流程?

调用某个接口的方法会调用之前生成的代理类,然后会从 cluster 中经过路由的过滤、负载均衡机制选择一个 invoker 发起远程调用,此时会记录此请求和请求的 ID 等待服务端的响应。

服务端接受请求之后会通过参数找到之前暴露存储的 map,得到相应的 exporter ,然后最终调用真正的实现类,再组装好结果返回,这个响应会带上之前请求的 ID。

消费者收到这个响应之后会通过 ID 去找之前记录的请求,然后找到请求之后将响应塞到对应的 Future 中,唤醒等待的线程,最后消费者得到响应,一个流程完毕。

关键的就是 cluster、路由、负载均衡,然后 Dubbo 默认是异步的,所以请求和响应是如何对应上的。

工作流涉及到服务提供者(Provider),注册中心(Registration),网络(Network)和服务消费者(Consumer):

  1. 服务提供者在启动的时候,会通过读取一些配置将服务实例化。
  2. Proxy 封装服务调用接口,方便调用者调用。客户端获取 Proxy 时,可以像调用本地服务一样,调用远程服务。
  3. Proxy 在封装时,需要调用 Protocol 定义协议格式,例如:Dubbo Protocol。
  4. 将 Proxy 封装成 Invoker,它是真实服务调用的实例。
  5. 将 Invoker 转化成 Exporter,Exporter 只是把 Invoker 包装了一层,是为了在注册中心中暴露自己,方便消费者使用。
  6. 将包装好的 Exporter 注册到注册中心。
  7. 服务消费者建立好实例,会到服务注册中心订阅服务提供者的元数据。元数据包括服务 IP 和端口以及调用方式(Proxy)。
  8. 消费者会通过获取的 Proxy 进行调用。通过服务提供方包装过程可以知道,Proxy 实际包装了 Invoker 实体,因此需要使用 Invoker 进行调用。
  9. 在 Invoker 调用之前,通过 Directory 获取服务提供者的 Invoker 列表。在分布式的服务中有可能出现同一个服务,分布在不同的节点上。
  10. 通过路由规则了解,服务需要从哪些节点获取。
  11. Invoker 调用过程中,通过 Cluster 进行容错,如果遇到失败策略进行重试。
  12. 调用中,由于多个服务可能会分布到不同的节点,就要通过 LoadBalance 来实现负载均衡。
  13. Invoker 调用之前还需要经过 Filter,它是一个过滤链,用来处理上下文,限流和计数的工作。
  14. 生成过滤以后的 Invoker。
  15. 用 Client 进行数据传输。
  16. Codec 会根据 Protocol 定义的协议,进行协议的构造。
  17. 构造完成的数据,通过序列化 Serialization 传输给服务提供者。
  18. Request 已经到达了服务提供者,它会被分配到线程池(ThreadPool)中进行处理。
  19. Server 拿到请求以后查找对应的 Exporter(包含有 Invoker)。
  20. 由于 Export 也会被 Filter 层层包裹
  21. 通过 Filter 以后获得 Invoker
  22. 最后,对服务提供者实体进行调用。

上面调用步骤经历了这么多过程,其中出现了 Proxy,Invoker,Exporter,Filter。

实际上都是调用实体在不同阶段的不同表现形式,本质是一样的,在不同的使用场景使用不同的实体。

例如 Proxy 是用来方便调用者调用的。Invoker 是在调用具体实体时使用的。Exporter 用来注册到注册中心的等等。

后面我们会对具体流程进行解析。如果时间不够无法阅读完全文,可以把上面的图保存。

知道什么是 SPI 嘛?

SPI 是 Service Provider Interface,主要用于框架中,框架定义好接口,不同的使用者有不同的需求,因此需要有不同的实现,而 SPI 就通过定义一个特定的位置,Java SPI 约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名

所以就可以通过接口找到对应的文件,获取具体的实现类然后加载即可,做到了灵活的替换具体的实现类。

为什么 Dubbo 不用 JDK 的 SPI,而是要自己实现?

答:因为 Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。

因此 Dubbo 就自己实现了一个 SPI,给每个实现类配了个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化,按需加载。