Seata RPC 模块的重构之路

Seata RPC 模块的重构之路

作者 | 张乘辉
来源|阿里巴巴云原生公众号

RPC 模块是我最初研究 Seata 源码开始的地方,因此我对 SeataRPC 模块有过一些深刻研究,在我研究了一番后,发现 RPC 模块中的代码需要进行优化,使得代码更加优雅,交互逻辑更加清晰易懂,本着 “让天下没有难懂的 RPC 通信代码” 的初衷,我开始了 RPC 模块的重构之路。

这里建议n U R z Y + | i )想要深入了解 Seata 交互细节的,不妨从 RPC 模块的源_ J f q q y V .码入手,RPC 模块相当于 Seata 的中枢,Seata 所有的交互逻辑在 RPC 模块中表现得淋漓尽致。

这次 RPC 模块的重构将会使得 Seata 的中= T E } u枢变得更加健壮和易于/ 6 :解读。

重构继承关系

在 Seata 的旧版本中,RPC 模块的整体结构有点混乱,尤其是在各个类的继承关系上,主要体现在:

  1. 直接在 Remoting 类继承9 ( @ C f Netty Handler,使得 Remoting 类与 Netty Handler 处理逻辑p k 7 _ y f k耦合在一起。
  2. 客户端和服务端的 Reomting 类继承关系B j [ 0 T v _不统一。
  3. RemotingC% j @ *lient 被 RpcClientBootstrap 实现,而 Re8 8 R G hmotingServer 却被 RpcSerF d } i Dver 实现,没有一个独立的 ServerBootstrap,这个看起来关系非常. x 2 X ( J f混乱。
  4. 有些接口没必要抽取出来,比如 ClientMessageSender、Client- P h O M G JMessageListener、ServerMessageSender 等接口,因这些接口会增加整体结构继承关系的复杂性。

针对上面发现的问题,在重构过程中我大致做了如下事情:

  1. 将 Netty Handler 抽象成一个内部类放在 Remoting 类中。
  2. 将 RemotingClient 为客户端顶级接口,定义客户端与服务端交互的基本方法,抽象一层 AbstractNettyRemoZ 4 G U + 7tingClient,下面分别有 RmNettyRemotingClient、TmNet2 c ) ~tyRemotingClient;将 RemotingServer 为服务端顶级接口,定义服务端与客户端交互的基本方法,实现类 NettyRemotingServer。
  3. 同时将 C& n Z W ~lientMessageSender、ClientMessa= y %geListener、ServerMeB k D `ssageSe+ , * x % + bnder 等接口方法归入到 RemotingClient、RemotingServer 中,由 Reomting 类实现 RemotingClient、RemotingServer,统一 Remoting 类C * I |继承关系。
  4. 新建 RemotingBootstrap 接口,客户端和服务端分别实现 NettyClientBootstrap、NettyServerBootstrap,将引导类逻辑从 Reomta v C Ming 类抽离出来。

在最新的 RPC{ j K t D K z 3 N 模块中的继承关系简单清G c v m a ( b G d晰,用如下类关系图表示:

  1. AbstractNettyRemotin@ $ 2 K _g:Remoting 类的最顶C W ;层抽象,包含了客户端和服m v | O务端公用的成员变量与公用方法,拥有通用的请求方法(文章后面会讲到),Processor 处理器调用逻辑(文章后面会讲; G N 6 9到)。
  2. Remotin2 z { M V 4 Q [gClient:客户端最顶级接口,定义客户端与服w V ) / , l s c _务端交互的基本方法。
  3. Remu P K 4 R = S ,otingServer:服务端最顶级接口,定义服务端与客户端交互的基本方法。
  4. Ai p O : e ?bstractNettyRemotingClient:客户端抽象类,继承 AbstractNettyRemoting 类并实现了 RemotingClient 接口。
  5. NettyRemotingServer:服务端实现类,继承 AbstractNeA Q I 0 HtJ z m 1 S ctyRemoting 类并实现了l & C 9 h M I ^ RemA f v U L H IotingServer 接口/ ( z
  6. RmNettyRemotingClient:Rm 客户端实现类,继承 Abstr} & g N ^ tactNettyRemotingClient 类。
  7. TmNettyRemotingClienr e Y q I m ot:Tm 客户端实现q G ~ 4 { x D f类,继承 AbstractNet( v F 3 }tyRemotingClient 类。

同时将客户端和服务端的引导类逻辑抽象出来,b d _ !如下类关系图表示:

Seata RPC 模块的重构之路

  1. Remoti W T X NngBootstrap:引导类接口,有 start 和 stop 两个抽象方法。
  2. NettyClientBootstrap:客户端引导实现类。
  3. NettyServerBootstrap:服务端引导实现类。

解耦处理逻6 + y 5 Y

解耦处理逻辑即是将 RPH J } V 2 S D MC 交互的处理逻辑从 Netty Handler 中抽离出来,并将处理逻辑抽象成一个个 Processor,为什么要这么做呢?我大致讲下现在存在的一些问题:

  1. Netty Handler 与 处理逻辑是糅合在一起的,由于g ; X 6 Z d L r y客户端与服务端都共用了一套处理逻辑,因此为了兼容更多的交互,在处理逻辑中你可以看到非常多难以理解的判断逻辑。
  2. 在 Seata 的交互中有些请求是异步处理的,也有一些请求是同步处理的,但是在旧的处理代码逻辑中对同步异步处理的表达非常隐晦,而且难以看明白。
  3. 无法从代码逻辑当中清晰地表达出请求消息类m 9 9 b型与对应的处理逻辑关系。
  4. 在 Seata 后面的更新迭代中,如果不将处理处理逻辑抽离出来,这部分代码想要增加新的交互逻辑,将会非常困难。

在将处理逻辑从 Nettyb ( 9 Han/ + x g I # ^ T jdler 进行抽离之前,我们先梳理一下 Seata 现有的交互逻辑。

  • RM 客户端请求服务T ! ? . 0 u a :端的交互逻辑:

Seata RPC 模块的重构之路

  • TM 客户端请求服务端的交互逻辑:

Seata RPC 模块的重构之路

  • 服务端请求 RM 客户端的交互逻辑: & | t

Seata RPC 模块的重构之路

从以上的交互图中可以清晰地看到了B # A Y 9 ~ & Seata 的交] 9 - 2 * ~ 7 %互逻辑。

客户端总共接收服务端的消息:

1)服务端请求消息

  • BranchCommitRequest、BranchRollbackRequest、UndoLogDeleteRequest

2)服务端响应消息

  • RegisterRMResponse、BranchRegisterRespons? 4 ae、BranchReportReF Osponse、GlobalLockQueryResponse
  • RegisterTMResponse、GlobalBeginResponse、GlobalCommitResponse、GlobalRollbackResponse、GlobalStatusResponse、GlobalReportResponse
  • HeartbeatMessa: 4 s : &ge(PONG)

服务端总共接收客户端的消息:

1)客户端Y ) i请求消息

  • RegisterRMRequest、BranchRegisterRequest、BranchReportRequest、GlobalLockQueryRequest
  • RegisterTMRequest、GlobalBeginRequest、GZ 3 J slobalCommitRequest、GlobalRollbackRequest、GlobalStatusRequest、j n G # c *GlobalReporM ) ` a U A B 3t^ . | 1 o b d FReques, I t q dt
  • HeartbeatMessage(PING)

2)客户端响应消息

  • BranchCommitRe/ & j : h K H 7sponse、BranchRollbackResponse

基于以上| B # 5 C q / r的交互逻辑分析,我们可以将处理消息的9 ] e | ] 6 k p逻辑抽象成若干个 Processor,一个 Processor 可以处理一个或者多个消息类h & @ d & *型的消息,只需在 Seata 启动时注册将消息类型注册到 ProcessorTable 中即可,形成一个映射关系,这样就可以根据消息类型调用对应的i 5 . Z 9 Processor 对消息进行处理,用如下图表示:

Seata RPC 模块的重构之路

在抽象 Remoting 类中定一个 processMessage 方法,方法逻辑是根据消息类型从 ProcessorTable 中拿到消息类型对应的 Processor。

这样就成功将处理逻辑从d c L ) Netty Handler 中彻底抽离出来了,Hand@ ? %ler#channelRead 方法只需要调S h . N (用 processMessage 方法即可,且还可以灵活根据消息类型动态注册 Processor 到 ProcessorTaL 5 p i ( M = ~ble 中,处理逻辑的可扩展性得到了极大的提升。

以下是 Proc# J k 1 d 7 } g bessoV ] G v [ 2r 的调用流程:

1* 5 v b I 7 P)客户端

  • RmBranchCommitProcessor:处理服务端全局提交请求。
  • RmBranch_ h l , d -RollbackProcessor:处理服务端全局回滚请求。
  • RmUndoLo9 ^ ~ H e # MgProcessor:处理服务端 undo log 删除请求。* u , C E 0 D r (
  • C} } ` ` [ h plientOnResponseProcessor:客户端处理服务端响应请求,如:BranchRegisterResponse、GlobalBeginResponse、GlobalCommitResponse 等。
  • ClientHeartbeatProcessor:处理服务端心跳响应。

2)服务端

Seata RPC 模块的重构之路

  • RegRmProcessor:处理 RM 客户端注册请求8 : u W
  • RegTmProcessor:处理 TM 客户端注册请求。
  • ServerOnRequestProcessor:处理客户端相关请求,如:BranchRegisterRequest、GlobalBeginRequest、GlobalLockQueryRequest 等。
  • ServerOnResponseProcessor:处理客户端相关响应,如:BranchCommitResponse、BranchRollbackReA K : B _sponse 等。
  • ServerHeartbeatProcessor:处理客户端心跳响应。

下面我以 TM 发起全局事务提交请求为例子,让大家感受下 Processor 在整个交互中所处的位置:

Seata RPC 模块的重构之路

重构请求方法

在 Seata 的旧版本当中,RPk J = u R , x P WC 的请求方法也是欠缺优雅,主要体现在:

  1. 请求方法过于s c P E杂乱无章,没有层次感。
  2. sendAsyncM 0 vRequest 方法耦合的代码X x | ^ 0 S e太多,逻辑过于混乱,客户端与服务端都共用了一套请求逻辑,方法中决定是否批量发送是根据参数 address 是否为 null 决定,决定是否同步请求是根据 timeouy 5 ` l V : Zt 是否大于 0 决定,显得极为不合理,且批量请求只有客户端有用到,服务端并没有批量请求,共用一套请求逻辑还会导致服务端异步请求也会创建 MessageFuture 放入 fus 6 Xtures 中。
  3. 请求方法名称v I C ? z O 7 m t风格不统一,比如客户端 sendMsgWithResponse,服务端却叫 sendSyncR) { o O * D !equest;

针对以上旧版本 RPC 请求方法的各种缺点,我作了以下改动:

  1. 将请求方法统一放入 RemotingClient、RemotingServer 接口当中,并作为顶h 4 u级接口;
  2. 分离客户端与服务端请求逻辑,将批量请求逻辑单独抽到客户端相关请求方法中,使得是否批量发送不再根据参数 address 是否为 null 决定;
  3. 由于 Seata 自身的逻辑特点,客户端服务端请求方法的参数无法统一,可通过抽取通用的同步/异步请求方法,客户端和服务端根据自- G p 3 h身请求逻辑特点n 7 [ B s ? T / %实现自身的同步/异k / @ j步请求逻辑,最后再调用通用的同步/异步请求方法,k U 1 2 x使得同步/异步请求都有明确的方法,不再根据 timeout 是否C ~ ( 9 & d L大于 0 决定;
  4. 统一请求. i % ) l名称风格。

最终,Seata RPC 的请求方法终于看起来更加优雅且有层次感了。

同步请求:

Seata RPC 模块的重构之路

异步请求:

Seata RPC 模块的重构之路

其它

  1. 类目录调整:RPC 模块目录中还有一个 netty 目e f w H S 1录,也可以从目录结构中发现 Seata 的初衷是兼容多个 RPC 框架,目前只实现了 netty,但发现 nettZ ! 9 i 5 Ly 模块中有些类并不 ”netty“,且 RPC 跟目录的类也并不通用,因此需要将相关类的位置进行调整。
  2. 某些类重新命名,比如 netty 相关类包含 「netty」。

最终 RPC 模块看起来6 V H是这样的:

Seata RPC 模块的重构之路

更多详情可访问Seata官网进行了解。