乐趣区

关于阿里云:Seata-RPC-模块的重构之路

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

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

这里倡议想要深刻理解 Seata 交互细节的,无妨从 RPC 模块的源码动手,RPC 模块相当于 Seata 的中枢,Seata 所有的交互逻辑在 RPC 模块中体现得酣畅淋漓。

这次 RPC 模块的重构将会使得 Seata 的中枢变得更加强壮和易于解读。

重构继承关系

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

  1. 间接在 Remoting 类继承 Netty Handler,使得 Remoting 类与 Netty Handler 解决逻辑耦合在一起。
  2. 客户端和服务端的 Reomting 类继承关系不对立。
  3. RemotingClient 被 RpcClientBootstrap 实现,而 RemotingServer 却被 RpcServer 实现,没有一个独立的 ServerBootstrap,这个看起来关系十分凌乱。
  4. 有些接口没必要抽取进去,比方 ClientMessageSender、ClientMessageListener、ServerMessageSender 等接口,因这些接口会减少整体构造继承关系的复杂性。

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

  1. 将  Netty Handler 形象成一个外部类放在 Remoting 类中。
  2. 将 RemotingClient 为客户端顶级接口,定义客户端与服务端交互的根本办法,形象一层 AbstractNettyRemotingClient,上面别离有 RmNettyRemotingClient、TmNettyRemotingClient;将 RemotingServer 为服务端顶级接口,定义服务端与客户端交互的根本办法,实现类 NettyRemotingServer。
  3. 同时将 ClientMessageSender、ClientMessageListener、ServerMessageSender 等接口办法纳入到 RemotingClient、RemotingServer 中,由 Reomting 类实现 RemotingClient、RemotingServer,对立 Remoting 类继承关系。
  4. 新建 RemotingBootstrap 接口,客户端和服务端别离实现 NettyClientBootstrap、NettyServerBootstrap,将疏导类逻辑从 Reomting 类抽离进去。

在最新的 RPC 模块中的继承关系简略清晰,用如下类关系图示意:

  1. AbstractNettyRemoting:Remoting 类的最顶层形象,蕴含了客户端和服务端专用的成员变量与专用办法,领有通用的申请办法(文章前面会讲到),Processor 处理器调用逻辑(文章前面会讲到)。
  2. RemotingClient:客户端最顶级接口,定义客户端与服务端交互的根本办法。
  3. RemotingServer:服务端最顶级接口,定义服务端与客户端交互的根本办法。
  4. AbstractNettyRemotingClient:客户端抽象类,继承 AbstractNettyRemoting 类并实现了 RemotingClient 接口。
  5. NettyRemotingServer:服务端实现类,继承 AbstractNettyRemoting 类并实现了 RemotingServer 接口。
  6. RmNettyRemotingClient:Rm 客户端实现类,继承 AbstractNettyRemotingClient 类。
  7. TmNettyRemotingClient:Tm 客户端实现类,继承 AbstractNettyRemotingClient 类。

同时将客户端和服务端的疏导类逻辑形象进去,如下类关系图示意:

  1. RemotingBootstrap:疏导类接口,有 start 和 stop 两个形象办法。
  2. NettyClientBootstrap:客户端疏导实现类。
  3. NettyServerBootstrap:服务端疏导实现类。

解耦解决逻辑

解耦解决逻辑即是将 RPC 交互的解决逻辑从 Netty Handler 中抽离进去,并将解决逻辑形象成一个个 Processor,为什么要这么做呢?我大抵讲下当初存在的一些问题:

  1. Netty Handler 与 解决逻辑是糅合在一起的,因为客户端与服务端都共用了一套解决逻辑,因而为了兼容更多的交互,在解决逻辑中你能够看到十分多难以了解的判断逻辑。
  2. 在 Seata 的交互中有些申请是异步解决的,也有一些申请是同步解决的,然而在旧的解决代码逻辑中对同步异步解决的表白十分费解,而且难以看明确。
  3. 无奈从代码逻辑当中清晰地表白出申请音讯类型与对应的解决逻辑关系。
  4. 在 Seata 前面的更新迭代中,如果不将解决解决逻辑抽离进去,这部分代码想要减少新的交互逻辑,将会十分艰难。

在将解决逻辑从 Netty Handler 进行抽离之前,咱们先梳理一下 Seata 现有的交互逻辑。

  • RM 客户端申请服务端的交互逻辑:

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

  • 服务端申请 RM 客户端的交互逻辑:

从以上的交互图中能够清晰地看到了 Seata 的交互逻辑。

客户端总共接管服务端的音讯:

1)服务端申请音讯

  • BranchCommitRequest、BranchRollbackRequest、UndoLogDeleteRequest

2)服务端响应音讯

  • RegisterRMResponse、BranchRegisterResponse、BranchReportResponse、GlobalLockQueryResponse
  • RegisterTMResponse、GlobalBeginResponse、GlobalCommitResponse、GlobalRollbackResponse、GlobalStatusResponse、GlobalReportResponse
  • HeartbeatMessage(PONG)

服务端总共接管客户端的音讯:

1)客户端申请音讯

  • RegisterRMRequest、BranchRegisterRequest、BranchReportRequest、GlobalLockQueryRequest
  • RegisterTMRequest、GlobalBeginRequest、GlobalCommitRequest、GlobalRollbackRequest、GlobalStatusRequest、GlobalReportRequest
  • HeartbeatMessage(PING)

2)客户端响应音讯

  • BranchCommitResponse、BranchRollbackResponse

基于以上的交互逻辑剖析,咱们能够将解决音讯的逻辑形象成若干个 Processor,一个 Processor 能够解决一个或者多个音讯类型的音讯,只需在 Seata 启动时注册将音讯类型注册到 ProcessorTable 中即可,造成一个映射关系,这样就能够依据音讯类型调用对应的 Processor 对音讯进行解决,用如下图示意:

在形象 Remoting 类中定一个 processMessage 办法,办法逻辑是依据音讯类型从 ProcessorTable 中拿到音讯类型对应的 Processor。

这样就胜利将解决逻辑从 Netty Handler 中彻底抽离进去了,Handler#channelRead 办法只须要调用 processMessage 办法即可,且还能够灵便依据音讯类型动静注册 Processor 到 ProcessorTable 中,解决逻辑的可扩展性失去了极大的晋升。

以下是 Processor 的调用流程:

1)客户端

  • RmBranchCommitProcessor:解决服务端全局提交申请。
  • RmBranchRollbackProcessor:解决服务端全局回滚申请。
  • RmUndoLogProcessor:解决服务端 undo log 删除申请。
  • ClientOnResponseProcessor:客户端解决服务端响应申请,如:BranchRegisterResponse、GlobalBeginResponse、GlobalCommitResponse 等。
  • ClientHeartbeatProcessor:解决服务端心跳响应。

2)服务端

  • RegRmProcessor:解决 RM 客户端注册申请。
  • RegTmProcessor:解决 TM 客户端注册申请。
  • ServerOnRequestProcessor:解决客户端相干申请,如:BranchRegisterRequest、GlobalBeginRequest、GlobalLockQueryRequest 等。
  • ServerOnResponseProcessor:解决客户端相干响应,如:BranchCommitResponse、BranchRollbackResponse 等。
  • ServerHeartbeatProcessor:解决客户端心跳响应。

上面我以 TM 发动全局事务提交申请为例子,让大家感触下 Processor 在整个交互中所处的地位:

重构申请办法

在 Seata 的旧版本当中,RPC 的申请办法也是欠缺优雅,次要体现在:

  1. 申请办法过于横七竖八,没有层次感。
  2. sendAsyncRequest 办法耦合的代码太多,逻辑过于凌乱,客户端与服务端都共用了一套申请逻辑,办法中决定是否批量发送是依据参数 address 是否为 null 决定,决定是否同步申请是依据 timeout 是否大于 0 决定,显得极为不合理,且批量申请只有客户端有用到,服务端并没有批量申请,共用一套申请逻辑还会导致服务端异步申请也会创立 MessageFuture 放入 futures 中。
  3. 申请办法名称格调不对立,比方客户端 sendMsgWithResponse,服务端却叫 sendSyncRequest;

针对以上旧版本 RPC 申请办法的各种毛病,我作了以下改变:

  1. 将申请办法对立放入 RemotingClient、RemotingServer 接口当中,并作为顶级接口;
  2. 拆散客户端与服务端申请逻辑,将批量申请逻辑独自抽到客户端相干申请办法中,使得是否批量发送不再依据参数 address 是否为 null 决定;
  3. 因为 Seata 本身的逻辑特点,客户端服务端申请办法的参数无奈对立,可通过抽取通用的同步 / 异步申请办法,客户端和服务端依据本身申请逻辑特点实现本身的同步 / 异步申请逻辑,最初再调用通用的同步 / 异步申请办法,使得同步 / 异步申请都有明确的办法,不再依据 timeout 是否大于 0 决定;
  4. 对立申请名称格调。

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

同步申请:

异步申请:

其它

  1. 类目录调整:RPC 模块目录中还有一个 netty 目录,也能够从目录构造中发现 Seata 的初衷是兼容多个 RPC 框架,目前只实现了 netty,但发现 netty 模块中有些类并不”netty“,且 RPC 跟目录的类也并不通用,因而须要将相干类的地位进行调整。
  2. 某些类重新命名,比方 netty 相干类蕴含「netty」。

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

更多详情可拜访 Seata 官网进行理解。

退出移动版