Dubbo概述--调用过程

Dubbo概述–调用过程

写在前面

本文参考了Dubbo官方手册结合Dubbo2.6.1版本源码分析。推荐先阅读官方手册

鉴于个人水平有限,如有不正确的地方请指出,欢迎一起讨论,谢谢!

调用过程

本过程的分析是基于最简单的Demo进行分析的,主要的目的是介绍架构和流程。

Consumer请求

Dubbo-Consumer-Request

  1. Consumer调用ProxyFactory扩展类生成的代理类:ProxyN中的方法。

    • ProxyFactory扩展点的默认类为JavassistProxyFactory
    • ProxyN中的N代表本次启动创建的第N个代理,从0开始的AtomicLong静态变量
  2. ProxyN类内部将调用方法转化为调用InvokerInvocationHandler提供的通用方法:invoke(obj, methodName, args)

  3. InvokerInvocationHandler将传入的methodName、args构造为RPCInvocation实例,并调用MockClusterInvoker的invoke(Invocation)方法。

    • RPCInvocation实例可以理解为整个传输过程中的数据载体类,存储调用方法、参数类型、参数等
  4. MockClusterInvoker根据Directory的URL确定调用方法时是否需要、怎样调用Mock,如果不是强制使用Mock则调用FailoverClusterInvoker的invoke方法。

    • MockClusterInvoker是通过服务扩展功能中自动添加包装类(Cluster的包装类MockClusterWrapper)的机制添加进来的
    • 没有methodName.mock参数则直接向下调用
    • methodName.mock参数值以force开始时,直接使用mock而不会调用Provider
    • methodName.mock参数值为其他时,只有调用Provider抛异常时才使用mock
    • 相关的MockInvokersSelector是通过硬编码AbstractDirectory.setRouters添加到Directory的
  5. FailoverClusterInvoker根据自身逻辑以及Directory、Router、LoadBalance的实现类确定具体调用Invoker。本例中为InvokerDelegate。关于Cluster、Direcotry、Router、LoadBalance请参见集群相关。

    • FailoverClusterInvoker是Cluster扩展点的默认类
  6. InvokerDelegate存储provider的URL,然后调用ProtocolFilterWrapper
  7. ProtocolFilterWrapper将Filter扩展点的实现类中@Activate注解的group值为consumer的Filter排序后组成过滤链,并逐个调用Filter,然后调用ListenerInvokerWrapper。
    • Filter链中的Filter根据@Activate注解的before、after、order属性排序的
  8. ListenerInvokerWrapper中通知InvokerWrapper的监听器。然后调用DubboInvoker。
  9. DubboInvoker根据URL中的参数决定是同步、异步还是通知调用,进而调用ReferenceCountExchangeClient发送请求。
    • DubboInvoker在实例化时会根据URL创建与Provider的连接对象并连接Provider。
  10. ReferenceCountExchangeClient调用实际的Client:HeaderExchangeClient进行调用。
    • ReferenceCountExchangeClient实例是Consumer使用共享connection访问多个Provider时的默认Client,用于一会用计数。
  11. HeaderExchangeClient是默认的客户端,会向Provider发送心跳。并调用HeaderExchangeChannel发送调用信息(Invocation)。
  12. HeaderExchangeChannel调用内部的Client接口的实现类发送调用,同时将发送的信息保存到DefaultFuture中,供后续接收响应时根据对应关系返回给Consumer上次调用
  13. 默认使用NettyClient(非最新的netty4下边的包)作为Client发送调用,Client使用自身持有的Channel来发送数据。
    • 这里的Client接口是根据Transporter扩展接口指定传输器
    • Client接口相关的编码器为Codec2扩展接口指定
    • Code2中的序列化方式使用Serialization扩展接口指定
  14. 为了对多种传输方式扩展进行统一的抽象,这里的Channel是对Dubbo自身的Channel,对各种具体(例如Netty的Channel)进行封装。最终调用Netty中的channel.write发送。
  15. 虽然请求是也会触发一系列的handler,但是这些handler不会控制发送数据,只是调用。这点不确定是否正确

Provider响应

Dubbo-Provier-Req&Res

  1. Netty低层接收到字节,并通过InternalEncoder类将字节解码为Java对象(Request),然后通过NettyHandler调用NettyServer的received方法。

    • InternalEncoder以及InternalDecoder会根据编码器接口Codec2进行编码
    • Code2中的序列化方式使用Serialization扩展接口指定
  2. NettyServer的received方法存在于父类AbstractPeer中,AbstractPeer是Server、Client的公共父类,用于调用内部的handler(MultiMessageHandler)进行后续处理。

    • NettyServer(Provider)和NettyClient(Consumer)都会使用MultiMessageHandler->HeartbeatHandler->从Dispatcher扩展点获取的ChannelHandler。
  3. MultiMessageHandler判断解码后的调用信息是否是MultiMessage的实例,根据判断结果调用内部的handler(HeartbeatHandler)。
    • 如果是MultiMessage,则迭代调用其内部的消息。否则直接调用消息。
  4. HeartbeatHandler用于专门用于处理心跳请求,非心跳请求才会继续调用后续的handler(dispatcherHandler)。
    • 对于心跳请求,设置读时间戳,并根据请求中的twoway属性确定是否需要返回响应
    • 对于心跳响应,记录debug日志
  5. dispatcherHandler指由Dispatcher扩展点指定的handler,默认为AllChannelHandler。用于控制请求的线程分发逻辑。请参见请求分发。
  6. dispatcherHandler会根据自身逻辑将请求分发到特定的线程
  7. 线程池或者IO线程会调用ChannelEventRunnable的run方法,根据状态调用后续handler(DecodeHandler)的相应方法。
  8. DecodeHandler将接收到的Invocation或Response内的数据进行进一步解码,解码完成后调用后续的handler(HeaderExchangeHandler)处理。此处指代上图中的7、8两步。
  9. HeaderExchangeHandler会继续调用进行后续的handler调用,但是如果此次调用需要返回值,那么由此处的received方法中将后续handler返回值写入到channel中返回
    • 后续的handler(DubboProtocol.requestHandler)调用的是reply方法,并不是received方法。
  10. DubboProtocol.requestHandler匿名内部类中调用后续Invoker,实现Provider的调用
  11. ProtocolFilterWrapper将Filter扩展点的实现类中@Activate注解的group值为consumer的Filter排序后组成过滤链,并逐个调用Filter,然后调用ListenerInvokerWrapper。
    • Filter链中的Filter根据@Activate注解的before、after、order属性排序的
  12. 后续通过多层调用,调用到通过ProxyFactory扩展类动态生成的包装类:WrapperN。
    • WrapperN中的N代表本次启动创建的第N个包装类,从0开始的AtomicLong静态变量
  13. 通过WrapperN中的invokeMethod方法以及方法传入参数调用实际的服务类相应方法。

Consumer接收响应

Consumer接收响应的过程与Provider响应过程前9步类似,即到HeaderExchangeHandler的received方法之前都一样。下面是整体概述:

  1. HeaderExchangeHandler中received方法中,由于解码出的对象是Response,所以将其直接传入DefaultFuture的received方法中。
  2. DefaultFuture.received()根据response的ID,获得之前Consumer请求时第12步的DefaultFuture对象。
  3. 根据DefaultFuture内部的并发编程逻辑,返回响应的response。
    • Consumer请求时的request线程会阻塞在DefaultFuture的get方法内的done.await上
    • response线程接收返回后,获取和request相同的锁。这个是必须的,且由于get是await的,所以可以获取到锁
    • response在获取到锁后,触发done.signal唤醒阻塞在done上的线程并在finally中释放锁
    • request线程被唤醒,返回response给Consumer顶层调用。
    • 以上是一个经典的等待-通知结构,也可以使用JDK自身的await、notify实现两个线程都必须在获得锁后在可以wait或notify,且notify后需要释放锁,此逻辑也可以实现简单的线程池。

总结

  1. Dubbo内部大量使用了装饰器模式(invoker、handler)、外观模式等设计模式
  2. Dubbo最经典的还是服务扩展方面的设计(个人看法)
  3. Consumer的异步调用和同步调用本质上都是异步的
  4. Consumer和Provider在接收RPC时前置处理完全一致,只是由于消息的类型进行不同的处理