当前位置: 首页>编程语言>正文

dubbo Service Handle dubboservicehandler

主要内容 :

  • 讲解 ChannelHandler,及其如何贯穿 Protocol -> Exchange -> Transport 层
  • Telnet 调用原理 ;

我们会讲解 dubbo 如何通过对 channelChandler 的包装,完成 Protocol、 Exchange 、 Transport 三层不同的业务逻辑, 同时对本地 Telnet 调用展开分析。

1 ChannelHandler

如果读者熟悉 Netty 框架 , 那么很容易理解 Dubbo 内部使用的 ChannlHandler 组件的原理,Dubbo 框架内部使用大量 Handler 组成类似链表 , 依次处理具体逻辑 , 比如编解码 、 心跳时间戳和方法调用 Handler 等 。 因为 Netty 每次创建 Handler 都会经过 ChannelPipeline , 大量的事件经过很多 Pipeline 会有较多的开销 , 因此 Dubbo 会将多个 Handler 聚合为一个 Handler 。在详细讲解 ChannelHandler 之前 , 我们先弄清楚 Dubbo 有哪些常用的 Handler, 它们之前是如何关联及如何协作的 。

1.1 核心 Handler 和线程模型

在讲解核心 Handler 之前 , 我们先看一下 Dubbo 中 Handler (ChannelHandler)的 5 种状态

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_方法调用,第1张

Dubbo 针对每个特性都会实现对应的 ChannelHandler, 在讲解 Handler 的职责前 , 我们先快速浏览巳经支持的 Handler

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_客户端_02,第2张

Dubbo 中提供了大量的 Handler 去承载特性和扩展 , 这些 Handler 最终会和底层通信框架做关联 ,比如 Netty 等 。 一次完整的 RPC 调用贯穿了一系列的 Handler, 如果直接挂载到底层通信框架 (Netty ) , 因为整个链路比较长 , 则需要触发大量链式查找和事件 , 不仅低效 , 而且浪费资源 。

下图展示了同时具有入站和出站 ChannelHandler 的布局 , 如果有一个入站事件被触发 ,比如连接数据读取 , 那么它会从 ChannelPipeline 头部开始一直传播到 Channelpipeline 的尾端 。 出站的 I/O 件将从 ChannelPipeline 最右边开始 , 然后向左传播 。 当然 , 在 ChannelPipeline传播事件时 , 它会测试入站是否实现了 ChannellnboundHandler 接口 , 如果没有实现则会自动跳过 , 出站时会监测是否实现 ChannelOutboundHandler, 如果没有实现 , 那么也会自动跳过 。 在 Dubbo 框架中实现的这两个接口类主要是 NettyServerHandler 和 NettyClientHandler

Dubbo 通过装饰者模式层包装 Handler, 从而不需要将每个 Handler 都追加到 Pipeline 中 。 在NettyServer 和 NettyClient 中最多有 3 个 Handler, 分别是编码 、 解码和 NettyServerHandler或 NettyClientHandler

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_客户端_03,第3张

对于 Dubbo 仅有一个 Exchanger 的实现,就是 HeaderExchanger,并且在 ExchangerServer/ExchangerClient 里基本都是直接调用的 Transorter 层返回的 server/client 的方法。多出一层 exchange 的核心目的就是为了传递 Exchange 层的 Handler(即传递给 Transaport 层,然后 Transaport 层再将这些 handler 进行包装去构造 Server),之所以这样做是因为 Exchange 层的 handler 处理的是 request 和 resopne,还包括 DefaultFuture 对象里线程的阻塞唤醒, 而 Transport 层主要就是编解码,然后传递完整的 request/respone 到内部包装的 Exchange 层的 handler。

  • 对于 DubboProtocol#refer 进行引用时会调用 connect 方法返回 ExchangerClient
  • 对于 DubboProtocol#export 进行暴露时会调用 bind 方法返回 ExchangerServer(流程如下图)

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_方法调用_04,第4张

对于构造 ExchangeServer 时传入的 Transportors.bind 会返回真正的用于传输的 Server ,并且绑定上面说的编解码器

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_方法调用_05,第5张

讲解完 Handler 的流转机制后 , 我们再来探讨 RPC 调用服务方处理 Handler 的逻辑 , 在DubboProtocol 中通过内部类继承自 ExchangeHandlerAdapter, 完成服务提供方 Invoker 实例的查找并进行服务的真实调用

服务方 ExchangeHandler 内部类实现

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_服务端_06,第6张

代码清单中给出的 Handler 实现是触发业务方法调用的关键 , 在服务暴露时服务端已经按照特定规则(端口 、 接口名 、 接口版本和接口分组)把实例 Invoker 存储到 HashMap 中 ,客户端调用过来时必须携带相同信息构造的 key, 找到对应 Exporter 然后调用 。 在①中查找当前已经暴露的服务 , 后面会继续分析这个方法实现 。 在②中主要包含实例的 Filter 和真实业务对象 , 当触发 invoker#invoke 方法时 , 就会执行具体的业务逻辑 。 在 DubboProtocol 中 , 我们继续跟踪 getlnvoker 调用 , 会发现在服务端唯一标识的服务是由 4 部分组成的 : 端口 、 接口名 、接口版本和接口分组 。服务端 Invoker 查找

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_java_07,第7张

为了理解关键原理 , 特意移除了异步参数回调逻辑 , 这部分内容会单独在高级特性中探讨 。 在①中主要获取协议暴露的端口 , 比如 Dubbo 协议默认的端口为 20880 。 在②中获取客户端传递过来的接口名称(大部分场景都是接口名) 。 在③中主要根据服务端口 、 接口名 、 接口分组和接口版本构造唯一的 key 。 ④ : 简单从 HashMap 中取出对应的 Exporter 并调用 Invoker属性值 。 分析到这里 , 读者应该能理解 RPC 调用在服务端处理的逻辑了 。

Dubbo 为了编织这些 Handler, 适应不同的场景 , 提供了一套可以定制的线程模型 。 为了使概念更清晰 , 我们描述的 I/O 线程是指底层直接负责读写报文 , 比如 Netty 线程池 。 Dubbo 中提供的线程池负责业务方法调用 , 我们称为业务线程 。 如果一些事件逻辑可以很快执行完成 , 比如做个标记而已 , 则可以直接在 I/O 线程中处理 。 如果事件处理耗时或阻塞 , 比如读写数据库操作等 , 则应该将耗时或阻塞的任务转到业务线程池执行 。 因为 I/O 线程用于接收请求 , 如果I/O 线程饱和 , 则不会接收新的请求 。

我们先看一下 Dubbo 中是如何实现线程派发的

  • 在构造 NettyServer 或者 NettyClient 时都会调用 Channel.wrap,即 Transport 层对 ChannelHandler 的包装

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_方法调用_08,第8张

在图中 , Dispatcher 就是线程池派发器 。 这里需要注意的是 , Dispatcher 真实的职责是创建具有线程派发能力的 ChannelHandler , 比如 AllChannelHandler > MessageOnlyChannelHandler 和 ExecutionChannelHandler 等 , 其本身并不具备线程派发能力 。Dispatcher 属于 Dubbo 中的扩展点 , 这个扩展点用来动态产生 Handler, 以满足不同的场景 。 目前 Dubbo 支持以下 6 种策略调用 , 如表所示

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_客户端_09,第9张

具体业务方需要根据使用场景启用不同的策略 。 建议使用默认策略即可 , 如果在 TCP 连接中需要做安全加密或校验 , 则可以使用 ConnectionOrderedDispatcher 策略 。 如果引入新的线程池 , 则不可避免地导致额外的线程切换 , 用户可在 Dubbo 配置中指定 dispatcher 属性让具体策略生效 。

1.2 Dubbo 请求响应 Handler

在 Dubbo 框架内部 , 所有方法调用会被抽象成 Request/Response, 每次调用 ( 一次会话 ) 都会创建一个请求 Request, 如果是方法调用则会返回一个 Response 对象 。 HeaderExchangeHandler用来处理这种场景 , 它主要负责以下 4 种事情 。

  • (1) 更新发送和读取请求时间戳 。
  • (2) 判断请求格式或编解码是否有错 , 并响应客户端失则的具体原因 。
  • (3) 处理 Request 请求和 Response 正常响应 。
  • (4) 支持 Telnet 调用 。

我们首先看一下 HeaderExchangeHandler#received 实现

  • HeaderExchangeHandler 的下一个 channelHandler 就是上面看的 DubboProtocol 用匿名内部类实现的 ExchangeChannelHandler

请求响应 Handler 实现

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_客户端_10,第10张

① : 负责响应读取时间并更新时间戳 , 在 Dubbo 心跳处理中会使用当前值并判断是否超过空闲时间 。 ② : 主要处理事件类型 , 目前主要处理 readonly 事件 , 用于 Dubbo 优雅停机 。 当注册中心反注册元数据时 , 因为网络原因 , 客户端不能及时感知注册中心事件 , 服务端会发送readonly 报文告知下线 。 ④ : 处理收到的 Response 响应 , 告知业务调用方 。 ⑤ : 校验客户端不支持 Telnet 调用 , 因为只有服务提供方暴露服务才有意义 。 这里有个小改进 , 因为客户端支持异步参数回调 , 但为什么这里不能支持 Telnet 调用呢 ? 异步参数回调客户端实际上也会暴露一个服务 , 因此针对这种场景 Telnet 应该是允许调用的 。 ⑥ : 触发 Telnet 调用 , 并将字符串返回给 Telnet 客户端(在下面的 2 节进行讲解) 。

接下来我们继续分析如何处理请求和响应 ( HeaderExchangeHandler#handleRequest、
HeaderExchangeHandler#handleResponse),

处理请求报文

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_方法调用_11,第11张

在处理请求时 , 因为在编解码层报错会透传到 Handler, 所以在①中首先会判断是否是因为请求报文不正确 , 如果发生错误 , 则服务端会将具体异常包装成字符串返回 , 如果直接使用异常对象 , 则可能造成无法序列化的错误 。 在②中触发 Dubbo 协议方法调用 , 并且把方法调用返回值发送给客户端 。 如果调用发生未知错误 , 则会通过③做容错并返回 。 当发送请求时 , 会在DefaultFuture 中保存请求对象并阻塞请求线程 , 在④中会唤醒阻塞线程并将 Response 中的结果通知调用方(即对于 Respone 的处理不经过 ChannelHandler,因为再不用进行网络传输)

1.3 Dubbo 心跳 Handler

Dubbo 默认客户端和服务端都会发送心跳报文 , 用来保持 TCP 长连接状态 。 在客户端和服务端 , Dubbo 内部开启一个线程循环扫描并检测连接是否超时 , 在服务端如果发现超时则会主动关闭客户端连接 , 在客户端发现超时则会主动重新创建连接 。 默认心跳检测时间是 60 秒 , 具体应用可以通过 heartbeat 进行配置

Dubbo 在服务端和客户端都复用心跳实现代码 , 抽象成 HeartBeatTask 任务进行处理

Dubbo 心跳逻辑处理

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_服务端_12,第12张

① : 遍历所有的 Channel, 在服务端对应的是所有客户端连接 , 在客户端对应的是服务端连接 。 ② : 主要忽略已经关闭的 Socket 连接 。 ® : 判断当前 TCP 连接是否空闲 , 如果空闲就发送心跳报文 。 目前判断是否是空闲的 , 根据 Channel 是否有读或写来决定 , 比如 1 分钟内没有读或写就发送心跳报文 。 ④ : 处理客户端超时重新建立 TCP 连接 , 目前的策略是检查是否在 3 分钟内(用户可以设置)都没有成功接收或发送报文 。 如果在服务端监测则会通过⑤主动关闭远程客户端连接 。

2 Telnet 调用原理

有了编解码器实现的基础 , 再理解 Telnet 处理就容易多了 。 编解码器处理有三种场景 : 请求 、 响应和 Telent 调用 。 理解 Telnet 调用并不难 , 编解码器主要把 Telnet 当作明文字符串处理 ,按照 Dubbo 的调用规范 , 解析成调用命令格式 , 然后查找对应的 Invoker, 发起方法调用即可 。

2.1 Telnet 指令解析原理

为了支持未来更多的 Telnet 命令和扩展性, Telnet 指令解析被设置成了扩展点 TelnetHandler,每个 Telnet 指令都会实现这个扩展点 。 我们首先查看这个扩展点的定义

TelnetHandler 定义

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_java_13,第13张

通过这个扩展点的定义 , 能够解决扩展更多命令的诉求 。 message 包含处理命令之外的所有字符串参数 , 具体如何使用这些参数及这些参数的定义全部交给命令实现者决定 。

完成 Telnet 指令转发的核心实现类是 TelnetHandlerAdapter, 它的实现非常简单 , 首先将用户输入的指令识别成 commandc 比如 invoke 、 Is 和 status, 然后将剩余的内容解析成 message,message 会交给命令实现者去处理 。 实现代码类在 TelnetHandlerAdapter#telnet 中

Telnet 转发解析

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_客户端_14,第14张

理解编解码后 , 可以更好地理解上层的实现和原理 , 在①中提取 Telnet 一行消息的首个字符串作为命令 , 如果命令行有空格 , 则将后面的内容作为字符串 , 再通过②提取并存储到 message中 。 在③中判断并加载是否有对应的扩展点 , 如果存在对应的 Telnet 扩展点 , 则会通过④加载具体的扩展点并调用其 telnet 方法 , 最后连同返回结果并追加消息结束符(在⑤中处理)返回给调用方 。

在讲解完 Telnet 调用转发后 , 我们对常用命令 Invoke 调用进行探讨 , 在 InvokeTelnetHandler中本地实现 Telnet 类调用

Telnet 本地方法调用

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_服务端_15,第15张

当本地没有客户端 , 想测试服务端提供的方法时 , 可以使用 Telnet 登录到远程服务器(Telnet IP port), 根据 invoke 指令执行方法调用来获得结果 。 当用户输入 invoke 指令时 ,会被转发到代码清单 对应的 Handler。 在①中提取方法调用信息(去除参数信息) , 在②中会提取调用括号内的信息作为参数值 。 在③中提取方法调用的接口信息 , 在④中提取接口调用的方法名称 。 在⑤中会将传递的 JSON 参数值转换成 fastjson 对象,然后在⑥中根据接口名称 、方法和参数值查找对应的方法和 Invoker 对象 。 在真正方法调用前 , 需要通过⑦把 fastjson 对象转换成 Java 对象 , 在⑧中触发方法调用并返回结果值 。

2.2 Telnet 实现健康监测

Telnet 提供了健康检查的命令 , 可以在 Telnet 连接成功后执行 status -1 查看线程池 、 内存和注册中心等状态信息 。 为了完成线程池监控 、 内存和注册中心监控等诉求 , Telnet 提供了新的扩展点 Statuschecker,

健康检查扩展点

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_方法调用_16,第16张

当执行 status 命令时会触发 StatusTelnetHandler#telnet 调用 , 这个方法的实现也比较简单 , 它会加载所有实现 Statuschecker 扩展点的类 , 然后调用所有扩展点的 check 方法 。 因为这类扩展点的具体实现并不复杂 , 所以不会一一讲解 , 表 6.4 列出了健康监测对应的实现和作用

dubbo Service Handle dubboservicehandler,dubbo Service Handle dubboservicehandler_客户端_17,第17张

小结

我们对 Dubbo 中比较重要的 Handler, 比如 Request/Response 模型 Handler 和心跳Handler 等做了详细的解析 , 同时对 Dubbo 的线程模型做了剖析 。

我们也对本地 Telnet 调用的设计和实现原理做了说明 。 在实际开发过程中 , 不熟悉 Dubbo 开发的人员也能快速通过 fastjson 方式测试和验证服务 , 在 Telnet 健康检查方面我们也做了进一步的说明 。



https://www.xamrdz.com/lan/5e31963270.html

相关文章: