Seata 高性能 RPC 通信的实现- 巧用 reactor 模式

本文深入探讨了 Reactor 模式在 Seata 服务器和客户端中的应用,详细介绍了 Seata Server 的 Netty 主从多线程模型,包括 Boss 线程组、Worker 线程组及其职责。Seata Server 和 Client 的线程池配置和消息处理机制也被详细阐述,展示了如何利用 Reactor 模式实现高性能的 RPC 通信。
摘要由CSDN通过智能技术生成

一、Reactor 模式

reactor 模式是一种事件驱动的应用层 I/O 处理模式,基于分而治之和事件驱动的思想,致力于构建一个高性能的可伸缩的 I/O 处理模式。维基百科对 Reactor pattern 的解释:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers

大致意思是说,reactor设计模式是一种事件处理模式,用于同时有一个或多个请求发送到事件处理器(service handler),这个事件处理器会采用多路分离(demultiplexes )的方式,同步的将这些请求分发到请求处理器(request handlers)。

不难看出,上边介绍的 reactor 模式是一种抽象;从实现角度说,reactor 模式有许多变种,不同编程语言中的实现也有差异。就 java 而言,大师 Doug Lea 在其【Scalable IO in Java】中就讲述了几个reactor模式的演进,如单线程版本多线程版 ,阅读此文后,笔者对大师所讲reactor模式演进的理解与网络中一些描述稍有差异。

reactor 单线程版中,只有一个reactor线程,线程中通过 select (I/O 多路复用接口) 监听所有 I/O 事件,收到 I/O 事件后通过 dispatch 进行分发给 Handlers 处理,此版本容易实现,也容易理解,但性能不高。为了适配多处理器,充分利用多核并行处理的优势,实现高性能的网络服务,可以采用分治策略,关键环节采用多线程模式,于是就出现了reactor多线程版本,而多线程的应用体现为worder线程和reactor线程,多线程应该被池化管理,这样才容易被调整和控制。线程池中的线程数会比客户端的数量少很多,实际数量可以根据程序本身是 CPU 密集型还是 I/O 密集型操作来进行合理的分配。

  • 多个 worder 线程(池化管理)

    • 属于网络 I/O 操作与业务处理的拆分,因为 reactors 监听到 I/O 事件后应该快速分发给 handlers 来处理程序;但如果 handler 中的非 I/O 操作慢了就会减慢 reactor 中的 I/O 事件响应速度,所以把非 I/O 操作从 reactors 的 I/O 线程转移到其他线程中,即由worker线程来分担非 I/O 逻辑的操作处理。
  • 多个 reactor 线程(池化管理)

    • 属于网络建连操作与网络 I/O 读写操作的拆分,因为由一个reactor在一个线程中完成所有 I/O 操作也会遇到性能瓶颈,可采取拆分并增加reactor策略,将 I/O 负载分配给多个 reactor(每个reactor都有自己的线程、选择器和调度循环)以达到负载平衡。这看起来挺不错,但谁来执行分配以达到负载均衡呢?或许是因为这个问题,将reactor拆分为两类角色,mainReactor负责接收连接,之后采用一定的负载均衡策略将新连接分配给其他subReactor来处理 I/O 读写,这样的拆分自然流畅。

如此就演进出如上图中的主从reactor多线程模型。请注意,结合【Scalable IO in Java】原文中的用词和描述看,上图中的mainReactorsubReactor可以有多个并做池化管理,所有也有一些文章中会看到如主ReactorGroupmainReactorGroup从ReactorGroupsubReactorGroup等这类名词用 Group 后缀来强调 Reactor 是池化管理。 或许是不好布局,也或许是为了凸显主从reactor角色的协作关系,上图中都只展示了一个,另外服务端应用通常只暴露一个服务端口时,只需用一个 mainReactor 来监听端口上的连接事件并处理。

二、Netty 主从 reactor 多线程模型

Nettyreactor所对应的实现类是NioEventLoop,其核心逻辑如下:

  • 不同类型的 channel 向 Selector 注册所感兴趣的事件
  • 扫描是否有感兴趣的事件发生
  • 事件发生后做相应的处理

客户端和服务端分别会有不同类型的channel,客户端创建SocketChannel向服务端发起连接请求,服务端创建ServerSocketChannel监听客户端连接,建连后创建SocketChannel与客户端的SocketChannel互相收发数据,这些channel分工不同,向 Selector 注册所感兴趣的事件情况也不同:

客户端/服务端 channel OP_ACCEPT OP_CONNECT OP_WRITE OP_READ
客户端 SocketChannel YES
服务端 ServerSocketChannel YES
服务端 SocketChannel YES YES

Netty中 Nio 方式实现几种 reactor 模型如下:

mainReactor 对应 Netty 中配置的 bossGroup 线程组(下图中的主ReactorGroup),主要负责接受客户端连接的建立。每 bind 一个端口就用掉一个bossGroup中的线程。

subReactor 对应 Netty 中配置的 workerGroup 线程组(下图中的 reactorGroup),bossGroup 线程组接受完客户端的连接后,将 channel 转交给 workerGroup 线程组,在 workerGroup 线程组内选择一个线程,执行 I/O 读写的处理,workerGroup 线程组默认是 2 * CPU 核数个线程。

主从 reactor 模式的核心流程:

  1. 如果只监听一个端口,那么只需一个主reactor干活儿,所以通常看到boosGroup只配置一个线程。主reactor运行在独立的线程中 ,该线程中只负责与客户端的连接请求

  2. reactor在服务器端可以不止一个, 通常运行多个从 reactor , 每个从 reactor 也运行在一个独立的线程中 ,负责与客户端的读写操作

  3. reactor 检测到客户端的链接后,创建 NioSocketChannel,按照一定的算法循环选取(负载均衡)一个从reactor,并把刚创建的NioSocketChannel 注册到这个从 reactor 中,这样建连和读写事件互不影响。

  4. 一个 reactor 中可被注册多个NioSocketChannel,这个 reactor 监听所有的被分配的 NioSocketChannel 的读写事件 , 如果监听到客户端的数据发送事件 , 将对应的业务逻辑转发给 NioSocketChannel 中的pipeline 里的 handler 链进行处理

  5. handler 最好只负责响应 I/O 事件,不处理具体的与客户端交互的业务逻辑 , 这样不会长时间阻塞 , 其 read 方法读取客户端数据后 , 将消息数据交给业务线程池去处理相关业务逻辑

  6. 业务线程池完成相关业务逻辑的处理后,将结果返回,通过NioSocketChannel的的pipeline 里的 handler 链将结果消息写回给客户端

  7. buffer不满足将结果消息写回给客户端时的条件时,注册写事件,等待可写时再写

三、Seata Server 端 的 reactor 模式应用

Seata Server 采用了 主从 reactor 多线程模型,对应这个模型的话是有四个线程池,其中自定义业务线程池是两个。

功能 线程池对象 备注
接收客户端连接 NettyServerBootstrap#eventLoopGroupBoss
处理 IO 事件 NettyServerBootstrap#eventLoopGroupWorker 部分 RPC 消息在这里处理
处理客户端的 r
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值