上篇《Dubbo技术知识总结之四——Dubbo集群容错》的七个步骤中,前四个步骤是 Cluster 层的工作。远程调用是后续步骤 5, 6, 7 的内容,同时也是 Cluster 层以下的工作。该部分对 Dubbo 远程调用的基础与实现进行总结,包括 Dubbo 协议,编解码器,Dubbo 线程模型。
Dubbo 协议设计参考了 TCP/IP 协议,每次 RPC 调用,报文都会包括协议头和协议体两个部分。
协议报文头部共 16 字节,携带信息有:
注:Dubbo 协议属于 Dubbo 框架的 Protocol 层。
编解码器有三种场景,请求、响应、Telnet 调用。主要对请求和响应场景进行总结。
注:编解码属于 Dubbo 框架的 Exchange, Transport 层。
Dubbo 编码器主要是将 Java 对象编码成字节流,返回给客户端。所有的编解码层实现类都应该继承于 ExchangeCodec
类。
Dubbo 协议请求的编码方法 ExchangeCodec#encodeRequest()
中,按照 Dubbo 协议的内容编码成字节流,其中关键方法 ExchangeCodec#encodeRequestData()
将请求内容序列化。在该方法中对接口、方法、方法参数类型、方法参数进行编码,并写入字节流中。
Dubbo 协议响应的编码方法 ExchangeCodec#encodeResponse()
基本类似,核心内容在于序列化响应调用方法 encodeResponseData(),以及编码异常处理部分。
ExchangeCodec#encodeResponseData
方法编码思路比较简单,编码内容可以分为正常 Java 类与异常信息两类,分别对其进行序列化操作。ExchangeCodec#encodeResponse()
方法中,出现编码异常处理的情况,首先将 ChannelBuffer 复位,避免造成缓冲区中的数据错乱;然后将异常信息通过 Channel 发送给客户端,防止客户端只有等到超时才感知到服务调用返回。解码相较于编码比较复杂,因为在解码过程中涉及粘包和半包问题。Dubbo 是基于 TCP 协议进行数据传输的,粘包和半包问题就是 TCP 流协议的典型问题。
流就像是河里的流水,是连成一片的,中间并没有分界线,TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分。所以在业务上,一个完整的包可能会被 TCP 拆分成多个包发送,这种情况会导致半包;也可能把多个小包封装成一个大的数据包发送,这种情况会导致粘包。
由于底层 TCP 无法理解上层的业务数据,所以底层是无法保证数据包不被拆分和重组。这个问题只能通过上层应用设计协议的方式来解决。业界主流协议解决方案如下:
Dubbo 使用的就是第四种方案,自行规定应用层的协议,即上面 6.1 章节总结的内容。Dubbo 中 ExchangerCodec
将消息解析为请求 Request 与响应 Response 的角色。
解码过程可以分为两步,第一步是报文头部解码,第二步是报文体解码,并将报文体转换成 RpcInvocation。解码过程在 ExchangeCodec#decode()
方法中,第一步解码报文头部的过程如下:
NEED_MORE_INPUT
;NEED_MORE_INPUT
;第二步报文体解码的方法在 DubboCodec
进行了重写,即方法 DubboCodec#decodeBody()
。步骤如下:
FLAG_REQUEST
标志位,判断这次消息是请求还是响应;编解码器将 Telnet 当做明文字符串处理,根据 Dubbo 的调用规范,解析成调用命令格式,查找对应的 Invoker,发起方法调用。
Dubbo 默认底层网络通信使用 Netty 框架。服务提供方 NettyServer 提供两级线程池,其中 EventLoopGroup(boss) 用来接受客户端的连接请求,并将接受的请求分发 (Dispatch) 给 EventLoopGroup(worker) 来处理。可以将 boss 和 worker 线程组称为 IO 线程,它的特点是不会发起新的 IO 请求,逻辑处理能迅速完成。有的包括查询数据库等操作的复杂操作处理慢,需要将这些复杂操作放到 Dubbo 线程池中(又称业务线程池)。根据请求消息被 IO 线程处理,还是被业务线程处理,Dubbo 提供了几种线程模型,不同线程模型实现不同的线程分发策略,同时各自实现了 Dispatcher 可扩展 SPI 接口。
Dispatcher 是线程派发器,真正的职责是创建具有线程派发能力的 ChannelHandler,比如 AllChannelHandler, MessageOnlyChannelHandler 等。
all 策略,分发实现类 AllDispatcher,将所有消息都派发到 Dubbo 线程池,包括请求、响应、连接事件、断开事件、心跳等,是 Dispatcher 的默认实现。
connection 策略,分发实现类 ConnectionOrderedDispatcher,只将连接断开事件放到线程池中有序执行,其他线程派发到 Dubbo 线程池处理。
direct 策略,分发实现类 DirectDispatcher,所有方法调用和事件处理都在 IO 线程池中。不推荐该策略。
execution 策略,分发实现类 ExecutionDispatcher,只将请求类派发到 Dubbo 线程池处理,其他类型的 IO 事件在 IO 线程池中。
message 策略,分发实现类 MessageOnlyChannelHandler,只在 Dubbo 线程池中处理请求和响应事件,其他事件在 IO 线程池中处理。
mock 策略,分发实现类 MockDispatcher,默认返回 null。
扩展接口 ThreadPool 的 SPI 实现有如下几种: