Dubbo3.0Triple协议源码分析


  Triple协议基于Http2协议,也就是在使用Triple协议发送数据时,会按HTTP2协议的格式发送,而HTTP2协议相较于HTTP1协议,是HTTP1的升级版,完全兼容HTTP1,而且HTTP2协议从设计层面解决了HTTP1性能低的问题。

Triple的底层原理分析

  1. UNARY:服务端只有在接收完请求(包括所有HEADERS帧和DATA帧,通过调用onCompleted()发送最后一个DATA帧)之后,才会处理数据,客户端也只有接收完响应包括所有HEADERS帧和DATA帧之后,才会处理响应结果。
  2. SERVER_STREAM:服务端流,服务端在接收完请求(包括所有DATA帧)之后,才会处理数据。不过在处理数据过程中,可以多次发送响应DATA帧(第一个DATA帧发送之前会发送一个HEADERS帧),客户端每接收到一个响应DATA帧就可以直接处理该响应DATA 帧。这个模式下,客户端只能发一次数据,但能多次处理响应DATA帧
  3. BI_STREAM:双端流,或者客户端流,客户端可以控制发送多个请求DATA帧(第一个DATA帧发送之前会发送一个HEADERS帧),服务端会不断的接收请求DATA帧并进行处理,并且及时把处理结果作为响应DATA帧发送给客户端(第一个DATA帧发送之前会发送一个HEADERS帧),而客户端每接收到一个响应结果DATA帧也会直接处理。这种模式下,客户端和服务端都在不断的接收和发送DATA帧并进行处理(注意请求HEADER帧和响应HEADERS帧都只发了一个)

服务消费端对Triple请求调用和响应处理逻辑

服务消费端请求发送逻辑

  服务提供者进行服务导出时,会按照协议以及对应的端口启动Server,比如Triple协议就会启动Netty并绑定指定的端口,等待Socket连接。服务消费端进行服务引入时,会生成TripleInvoker对象,在TripleInvoker对象的构造方法中,会利用ConnectionManager创建一个Connection对象,而Connection对象中包含了一个Bootstrap对象(Netty中用来建立Socket连接的)。不过以上都只是创建对象,并不会真正和服务去建立Socket连接,所以在生成TripleInvoker对象过程中不会真正去创建Socket连接,那什么时候创建的呢?

当我们在服务消费端执行以下代码时
在这里插入图片描述
  demoService是一个代理对象,在执行方法的过程中,最终会调用TripleInvoker的doInvoke()方法。在doInvoke()方法中,会利用Connection对象来判断Socket连接是否可用,如果不可用并且没有初始化,那就会创建Socket连接。
在这里插入图片描述
  一个Connection对象表示一个Socket连接,在TripleInvoker对象中也只有一个Connection对象,也就是一个TripleInvoker对象只对应一个Socket连接,这个和DubboInvoker不太一样,一个DubboInvoker中可以有多个ExchangeClient,每个ExchangeClient都会与服务端创建一个Socket连接,所以一个DubboInvoker可以对应多个Socket连接,当然多个Socket连接的目的就是提高并发,不过在TripleInvoker对象中就不需要这么来设计了,因为可以Stream机制来提高并发。

  当我们利用服务接口的代理对象执行方法时就会创建一个Socket连接,就算这个代理对象再次执行方法时也不会再次创建Socket连接了,值得注意的是,有可能两个服务接口对应的是一个Socket连接,举个例子:
  比如服务提供者A,提供了DemoService和HelloService两个服务,服务消费者B引入了这两个服务,这个两个接口对应的代理对象对应不同的两个TripleInvoker,但是这两个TripleInvoker会共用一个Socket连接,因为ConnectionManager在创建Connection对象时会根据服务URL的address进行缓存,后续这两个代理对象在执行方法时使用的就是同一个Socket连接,但是不同的Stream。

  Socket连接创建好后,就需要发送Invocation对象给服务提供者,因为是基于的HTTP2,所以要先创建一个Stream,然后再通过Stream来发送数据。
在这里插入图片描述

  TripleInvoker用的是Netty,所以最终会利用Netty创建Stream,对应对象为Http2StreamChannel,消费端的TripleInvoker最终会利用Http2StreamChannel发送和接收数据帧,数据帧对应的对象为Http2Frame,它又分为Http2DataFrameHttp2HeadersFrame等具体类型。

  正常情况下,会每生成一个数据帧就发送,但是在Triple中有一个批量发送的优化,当发送一个数据帧时,会先把数据帧放入一个WriteQueue中,然后从线程池中拿到一个线程调用WriteQueue的flush方法,该方法的实现为

private void flush() {
    try {
        QueuedCommand cmd;
        int i = 0;
        boolean flushedOnce = false;
        // 只要队列中有元素就取出来,没有则退出while
        while ((cmd = queue.poll()) != null) {
            // 把数据帧添加到Http2StreamChannel中,添加完并不会立马发送,只有调用了flush才发送
            cmd.run(channel);
            i++;

            // DEQUE_CHUNK_SIZE=128
            // 连续从队列中取到了128个数据帧就flush一次
            if (i == DEQUE_CHUNK_SIZE) {
                i = 0;
                channel.flush();
                flushedOnce = true;
            }
        }

        // i != 0 表示从队列中取到了数据但是没满128个
        // 如果i=0,flushedOnce=false也flush一次
        if (i != 0 || !flushedOnce) {
            channel.flush();
        }
    } finally {
        scheduled.set(false);

        // 如果队列中又有数据了,则继续会递归调用flush
        if (!queue.isEmpty()) {
            scheduleFlush();
        }
    }
}

  向WriteQueue添加一个数据帧后,就会尝试开启一个线程,要不要开启线程要看CAS。比如现在有10个线程同时向WriteQueue添加一个数据帧,那么这10个线程中的某一个会CAS成功,其他线程CAS失败。CAS成功的线程会负责从线程池中获取另外一个线程执行上面的flush方法,从而获取WriteQueue中的数据帧然后发送出去。

  在TripleInvoker的doInvoke()源码中,在创建完成Socket连接后,就会:

  1. 基于Socket连接先构造一个ClientCall对象
  2. 根据当前调用的方法信息构造一个RequestMetadata对象,用来标识当前调用的是哪个接口的哪个方法,并且记录了所配置的序列化方式,压缩方式,超时时间等
  3. 构造一个ClientCall.Listener,用来处理响应结果,针对不同的流式调用类型,会构造出不同的ClientCall.Listener:
    在这里插入图片描述
  • UNARY:构造一个UnaryClientCallListener,内部包含一个DeadlineFuture,DeadlineFuture用来控制线程等待结果的timeout
  • SERVER_STREAM:构造一个ObserverToClientCallListenerAdapter,内部包含了调用方法时传入进来的StreamObserver对象,最终就是由这个StreamObserver对象来处理响应结果
  • BI_STREAM:和SERVER_STREAM作用类似,也会构造一个ObserverToClientCallListenerAdapter
  1. 调用ClientCall对象start方法创建一个Stream,并且返回一个StreamObserver对象
  2. 得到了StreamObserver对象后,根据不同的流式调用类型使用这个StreamObserver对象
  • UNARY:直接调用StreamObserver对象的onNext()方法发送方法参数,然后调用onCompleted方法,返回一个new AsyncRpcResult(CompletableFuture.completedFuture(new AppResponse()), invocation),后续不会同步,并且返回null给业务方法。
  • SERVER_STREAM:直接调用StreamObserver对象的onNext()方法来发送方法参数,然后调用onCompleted方法,返回一个new AsyncRpcResult(CompletableFuture.completedFuture(new AppResponse()), invocation),后续不会同步,并且返回null给业务方法。
  • BI_STREAM:直接返回new AsyncRpcResult( CompletableFuture.completedFuture(new AppResponse(requestObserver)), invocation),不等待响应结果,直接把requestObserver对象返回给业务方法。

  所以我们可以发现,不管是哪种流式调用类型,都会先创建一个Stream,得到对应的StreamObserver对象,然后调用StreamObserver对象的onNext方法来发送数据,比如发送服务接口方法的入参值,比如一个User对象:

  1. 在发送User对象之前,会先发送请求头,请求头中包含了当前调用的是哪个接口、哪个方法、版本号、序列化方式、压缩方式等信息(注意请求头中会包含一些gRPC相关的key,主要就是为了兼容gRPC)
  2. 发送请求体
  3. 对User对象进行序列化,得到字节数组
  4. 压缩字节数组
  5. 把压缩之后的字节数组以及是否压缩标记生成一个DataQueueCommand对象,把这个对象添加到writeQueue中,执行scheduleFlush(),该方法会开启一个线程从writeQueue中获取数据发送。发送时触发DataQueueCommand对象的doSend方法进行发送,该方法会构造一个DefaultHttp2DataFrame对象,该对象有两个属性:endStream(表示是不是Stream中的最后一帧),content(表示帧携带的核心数据),该数据格式为:
    • 第一个字节记录请求体是否被压缩
    • 紧接着的四个字节记录字节数组的长度
    • 后面就真正的字节数据

  以上是TripleInvoker发送数据的流程,接下来是TripleInvoker接收响应数据的流程。

服务消费端响应处理逻辑

  ClientCall.Listener用来监听是否接收到响应数据,不同的流式调用方式会对应不同的ClientCall.Listener:

  • UNARY:UnaryClientCallListener,内部包含了一个DeadlineFuture,DeadlineFuture用来控制线程等待结果的timeout
  • SERVER_STREAM:ObserverToClientCallListenerAdapter,内部包含了调用方法时传入进来的StreamObserver对象,最终由这个StreamObserver对象处理响应结果
  • BI_STREAM:和SERVER_STREAM一样,也会构造出来一个ObserverToClientCallListenerAdapter

  要监听某个Stream中是否有响应数据,这个由Netty处理,在之前创建Stream时,会向Http2StreamChannel绑定一个TripleHttp2ClientResponseHandler,这个Handler用来处理接收到的响应数据。
在这里插入图片描述
  在TripleHttp2ClientResponseHandler的channelRead0方法中,每接收一个响应数据就会判断是Http2HeadersFrame还是Http2DataFrame,然后调用ClientTransportListener中对应的onHeader方法和onData方法:

  1. onHeader方法通过处理响应头,生成一个TriDecoder,用来解压并处理响应体
  2. onData方法利用TriDecoder的deframe()方法来处理响应体

  TriDecoder的deframe()方法在处理响应体数据时,会分为两个步骤:
3. 先解析前5个字节,第1个字节确定该响应体是否压缩,后续4个字节确定响应体内容的字节长度
4. 再取出该长度的字节作为响应体数据,如果压缩了就解压,把解压后的字节数组传给ClientStreamListenerImpl的onMessage(),该方法会按对应序列化方式进行反序列化得到最终对象,再调用UnaryClientCallListener或者ObserverToClientCallListenerAdapter的onMessage()方法。
    

UnaryClientCallListener,构造时传入一个DeadlineFuture对象:
在这里插入图片描述

  1. onMessage()接收到响应结果对象后,会把结果对象赋值给appResponse属性
  2. onClose()会取出appResponse属性记录的结果对象构造出来一个AppResponse对象,然后调用DeadlineFuture的received方法,从而将方法调用线程接阻塞,并得到响应结果对象。

ObserverToClientCallListenerAdapter,构造时传入一个StreamObserver对象:
在这里插入图片描述

  1. onMessage()接收到响应结果对象后,会调用StreamObserver对象的onNext(),并把结果对象传给onNext()方法,触发自定义的onNext()方法逻辑。
  2. onClose()会调用StreamObserver对象的onCompleted(),或者调用onError()方法

  另外如果服务提供者调用onCompleted方法,会向客户端响应一个请求头,endStream为true,表示响应结束,也会触发执行onHeader方法,调用TriDecoder的close()方法。TriDecoder close()最终也会调用UnaryClientCallListener或者ObserverToClientCallListenerAdapter的close()方法。

服务提供端对Triple请求处理和响应结果发送逻辑

  这部分内容和服务消费者对发送请求和处理响应逻辑类似,就是把视角从消费端切换到服务端,前面分析的是消费端发送和接收数据,现在分析的是服务端接收和发送数据。
  消费端在创建一个Stream后,会生成一个对应的StreamObserver对象用来发送数据和一个ClientCall.Listener用来接收响应数据。对于服务端其实也一样,在接收到消费端创建Stream命令后,也需要生成一个对应的StreamObserver对象用来响应数据以及一个ServerCall.Listener用来接收请求数据。
  在服务导出时,TripleProtocol的export方法会开启一个ServerBootstrap,并绑定指定端口,并且最重要的是,Netty会负责接收创建Stream的信息,一旦就收到这个信号,就会生成一个ChannelPipeline,并给ChannelPipeline绑定一个TripleHttp2FrameServerHandler,用来处理Http2HeadersFrameHttp2DataFrame

  比如在接收到请求头后,会构造一个ServerStream对象,该对象有一个ServerTransportObserver对象,ServerTransportObserver对象处理请求头和请求体

  1. onHeader()方法,处理请求头
  • 比如从请求头中得到当前请求调用的是哪个服务接口,哪个方法
  • 构造一个TriDecoder对象,TriDecoder对象用来处理请求体
  • 构造一个ReflectionServerCall对象并调用doStartCall()方法,生成不同的ServerCall.Listener
    • UNARY:UnaryServerCallListener
    • SERVER_STREAM:ServerStreamServerCallListener
    • BI_STREAM:BiStreamServerCallListener
    • 在构造这些ServerCall.Listener时会把ReflectionServerCall对象传入进去,ReflectionServerCall对象向客户端发送数据
  1. onData()方法,处理请求体,调用TriDecoder对象deframe方法处理请求体,如果是endStream,那还会调用TriDecoder对象close方法

TriDecoder

  1. deframe():先解析请求体前5个字节,然后解压请求体,反序列化得到请求参数对象,调用不同的ServerCall.Listener中的onMessage()
  2. close():当客户端调用onCompleted方法时,表示发送完毕,此时会发送一个DefaultHttp2DataFrame并且endStream为true,触发服务端TriDecoder对象的close()方法,调用不同ServerCall.Listener中的onComplete()

UnaryServerCallListener

  1. 在接收到请求头时,构造UnaryServerCallListener对象
  2. 在接收到请求体时,请求体中的数据就是调用接口方法的入参值,比如User对象,就会调用UnaryServerCallListener的onMessage()方法,在这个方法中把User对象设置到invocation对象中
  3. 当消费端调用onCompleted()方法,表示请求体数据发送完毕,从而触发UnaryServerCallListener的onComplete()方法,在该方法中调用invoke()方法,从而执行服务方法,并得到结果。得到结果后,会调用UnaryServerCallListener的onReturn()方法,把结果通过responseObserver发送给消费端,并调用responseObserver的onCompleted()方法,表示响应数据发送完毕。responseObserver是ReflectionServerCall对象的一个StreamObserver适配对象(ServerCallToObserverAdapter)。

ServerStreamServerCallListener

  1. 接收到请求头时,会构造ServerStreamServerCallListener对象
  2. 接收到请求体时,请求体中的数据就是调用接口方法的入参值,比如User对象,就会调用ServerStreamServerCallListener的onMessage()方法,把User对象以及responseObserver对象设置到invocation对象中,这是和UnaryServerCallListener不同的地方,因为服务端流需要这个responseObserver对象,服务方法拿到这个对象后就可以自己控制如何发送响应体,什么时候调用onCompleted()方法。
  3. 当消费端调用onCompleted()方法,表示请求体数据发送完毕,触发ServerStreamServerCallListener的onComplete()方法,在该方法中调用invoke()方法,执行服务方法,通过responseObserver对象来发送数据
  4. 方法执行完,仍然会调用ServerStreamServerCallListener的onReturn()方法,但是个空方法

BiStreamServerCallListener

  1. 接收到请求头时,会构造BiStreamServerCallListener对象,这里比较特殊,会把responseObserver设置给invocation并执行invoke()方法,执行服务方法,并执行onReturn()方法,onReturn()方法中会把服务方法的执行结果(也是一个StreamObserver对象),赋值给BiStreamServerCallListener对象的requestObserver属性。
  2. 接收到请求头时,服务方法就开始执行,得到了一个requestObserver,它是我们自定义逻辑用来处理请求体,另外responseObserver用来发送响应体。
  3. 接收到请求体时,触发onMessage()方法,该方法调用requestObserver的onNext()方法。服务端实时接收消费端每次发送数据,并且进行处理。处理过程中,如果需要响应就可以利用responseObserver进行响应
  4. 一旦消费端调用了onCompleted()方法,触发BiStreamServerCallListener的onComplete方法,该方法调用requestObserver的onCompleted(),主要就触发我们自定义的StreamObserver对象中的onCompleted(),并没有针对底层的Stream做什么事情。

总结

不管是Unary,还是ServerStream,还是BiStream,底层客户端和服务端之前都只有一个Stream,它们三者的区别在于:

  1. Unary:通过流,将方法入参值作为请求体发送出去,而且只发送一次,服务端这边接收到请求体之后,会执行服务方法,得到结果,把结果返回给客户端,也只响应一次。
  2. ServerStream:通过流,将方法入参值作为请求体发送出去,而且只发送一次,服务端接收到请求体后,会执行服务方法,并且把当前流对应的StreamObserver对象也传给服务方法,由服务方法自己控制如何响应,响应几次,响应什么数据,什么时候响应结束,都由服务方法自己控制。
  3. BiStream,通过流,客户端和服务端,都可以发送和响应多次。

Triple协议源码流程图

https://www.processon.com/view/link/64ae24c3504ef15cbb98c4fb

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

paopaodog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值