另外,再说一下返回的类型,讲到后面的时候需要知道这个点。主要依据这个类里面定义的字段: org.apache.dubbo.rpc.protocol.dubbo.DubboCodec
对应的代码逻辑如下: org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#encodeResponseData(org.apache.dubbo.remoting.Channel, org.apache.dubbo.common.serialize.ObjectOutput, java.lang.Object, java.lang.String)
这个方法从名称也知道,是对响应数据做解码操作的。
标号为①的地方是判断当前版本是否支持上下文信息传递。
标号为②的地方是判断是否是异常返回。
标号为③的地方表明不是异常返回,则判断返回值是否为 null。
标号为④的地方表明是正常返回,根据是否支持上下文信息传递,从而判断是只返回响应结果的还是既有响应结果,也有上下文信息的返回类型。
标号为⑤的地方表明是异常返回,根据是否支持上下文信息传递,从而判断是只返回异常结果的还是既有异常结果,也有上下文信息的返回类型。
好了,写到这里,协议就差不多说完了。其实不难发现这个协议就是一个偏理论的东西,这就是一个大家的约定。
所以我记起之前在一个分享大会上,一位嘉宾说的:
跨语言特性实际是RPC层的支持,本质是协议层面的支持。
我现在对这句话的理解更加深刻了。
跨语言,也就是服务异构的一种。
为什么我用 Java 发送 http 请求的时候可以不用关心对方使用的是什么开发语言?
因为大家都遵守了 http 协议,协议是可以跨语言的。
Dubbo 这种 rpc 调用的框架也一样。我发起远程调用之后,只要你能按照我们约定好的协议进行报文的解析,那你就能正常的处理我发过来的请求,我不管你的开发语言是什么。
反序列化操作到底在哪进行?
业务数据返回后,反序列化的操作到底是在哪个线程里面进行的?
是在 IO 线程里面直接解析,还是被派发到客户端线程池里面进行解析?
这个问题我们先试着在官网的线程模型介绍中去寻找答案。
http://dubbo.apache.org/zh-cn/docs/user/demos/thread-model.html
在线程模型的描述里面,是这样写的:
如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。
但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则IO 线程阻塞,将导致不能接收其它请求。
如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景。
本文不关心线程池配置,我们只看派发策略:
图片中的 Dispatch 就是派发策略发挥作用的地方。
所以我们能从这部分得出一个结论:在默认的情况下,客户端接收到响应后,由于 Dubbo 使用 all 的派发策略,会把响应请求派发到客户端线程池中去。
那我们可以推导:出响应的解析一定是在客户端线程池里面进行的吗?
不可以,推不出来的。
只能说响应会进入客户端线程池中去,但是这个响应可能是一个经过解析后的响应,也可能是一个没有经过解析的响应。
所以,这个响应有可能在进入线程池之前就被解析过了。被谁解析?
IO 线程。
如果 IO 线程没有解析,那就在客户端线程里面去解析。
根据上面这段话。我们能提炼出一个关键语句,或者说是需求:我们现在要实现响应报文可以在不同的地方进行解析的功能,请问你怎么做?
你用脚指头想也知道了。首先肯定是有一个 if 判断的,判断到底在哪(IO线程/客户端线程池)进行响应解析。而这个 if 判断的判断条件,按照 Dubbo 的尿性,肯定是可以配置的。
所以我们找到这个地方,问题就了然于心了。
我们去哪里找答案呢?
这个类里面,这个类是一个请求/响应解码的非常核心的类:
org.apache.dubbo.rpc.protocol.dubbo.DubboCodec
这个类的主要干了两件事,一个是对响应报文进行解码,一个是对请求报文进行解码。
接下来我们怎么搞?强撸源码吗?不可能的。直接撸肯定费劲。
还是要搞个 Demo 跑起来,然后 Debug。
我这里的 Demo 非常简单,服务端接口实现类如下: