Seata源码(十八)小结

200 阅读6分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是  铿然架构  的第 108  篇原创文章

1. 概述

Seata的核心流程,框架,处理逻辑大部分已分析,其实还有不少内容可以继续分析,但基于初衷只是作个大致了解,做到心中有数,而非深度技术选型或者要出一本关于seata源码的书。所以暂且到这里,本篇是Seata源码分析的最后一篇,简单做个小结和补充。

2. 可以直接借鉴的

部分受限版本,可能不是最新用法。

2.1 压缩算法

压缩算法.png

2.2 序列化算法

序列化算法.png

2.3 配置中心

配置中心.png

2.4 服务注册发现

服务注册发现.png

2. 机制/框架

2.1 消息框架

seata的整个消息框架功能比较全面,包括编解码、数据压缩、序列化、消息合并、同步/异步处理、消息派发,扩展接口等等,基本可以照抄,在自己的业务开发中借鉴。

需要完善的部分是机机鉴权部分,实现零信任架构。

2.2 消息派发

2.2.1 基本框架

在Seata源码中,不同的请求消息对应不同的RemotingProcessor:

Processor列表.png

并且Seata也实现了一套完整的流程处理框架。

通常,对于客户端/服务端交互场景,当有不同的业务需要处理时,可以按照如下的通用框架抽象:

消息派发框架.png

1.定义一个业务处理接口,每类业务实现这个接口,实现类的参数可以用泛型定义,这样不需要对参数强转。

2.应用启动时,每类业务自行调用Registry将Processor注册到系统内

3.服务入口ServiceEntry收到服务请求消息后,转换为业务对象,并获取请求中的业务类型(或者action,简言之就是识别是哪类业务),然后调用Dispatcher来派发请求。

4.Dispatcher根据业务类型查找到对应的Processor子类,并调用和返回调用结果给ServiceEntry。

2.2.2 优点

● 解耦框架开发者,避免通过if/switch语句根据业务类型查找processor,导致随着业务增加要不断地修改这段代码,框架开发者不会因为业务的增加而修改代码。(这种通过map控制处理逻辑的方法也叫表驱动)

● 解耦业务开发者,业务开发者只需要关注自己开发的xxxProcessor,以及通过Registry注册业务类型和Processor的关系则可,不需要因为新增变化修改控制逻辑。

● 职责分离,注册逻辑,和派发逻辑术语不同的维度,有不同的变化点,分成两个类可以独立演进。

2.3 事件通知

2.3.1 基本框架

在seata中封装了Google的事件通知处理,类结构如下:

事件通知机制.png

1.订阅者要订阅某个事件(对象),只要将注解Subscribe先添加到类的方法上,然后通过GuavaEventBus注册这个类,这个了类就成了一个事件订阅者。

2.发送事件通知时,调用GuavaEventBus的post方法发送事件对象,只要该对象的类型和订阅者有Subscribe注解的订阅方法的参数一致,那么订阅者就可以收到该事件进行处理。

2.3.2 优点

● 客户端和服务端解耦,客户端不需要知道有哪些订阅者,只需要发布事件则可,例如:

DefaultCore.java

        // 这里通过异步事件通知,调用MetricsSubscriber的recordGlobalTransactionEventForMetrics方法
        eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
            session.getTransactionName(), applicationId, transactionServiceGroup, session.getBeginTime(), null, session.getStatus()));

● 服务端和客户端没有代码依赖,只要在订阅者的方法上增加Subscribe注解,并且方法参数类型和发布的事件对象一致则可。

MetricsSubscriber.java

@Subscribe
public void recordGlobalTransactionEventForMetrics(GlobalTransactionEvent event) {
    if (registry != null && consumers.containsKey(event.getStatus())) {
        consumers.get(event.getStatus()).accept(event);
    }
}

2.4 类实例管理框架

2.4.1 基本类结构

Seata没有使用spring的类实例管理,自行实现了类实例管理框架。一个类实例管理框架应具备如下基本能力:

● 允许单例或者多例加载

● 同一个接口/抽象类的多个子类允许定义加载优先级,以变定制通过设置优先级替代默认实现

● 可以动态加载,不在代码中写死创建逻辑

Seata的类实例管理框架如下:

类加载机制.png

类/条目描述
Scope定义类实例化方式是单例还是多例
LoadLevel注解,设置要加载类的别名,优先级,实例化方式
ExtensionDefinition要加载的类的扩展信息,在加载类时使用
EnhancedServiceLoader类实例加载器
类资源文件定义要加载的子类,文件名为接口/抽象类型名

2.4.2 例子

看一个SessionManager加载的例子。

指定类的别名加载:

SessionHolder.java

    public static void init(String mode) {
        if (StringUtils.isBlank(mode)) {
            mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE);
        }
        StoreMode storeMode = StoreMode.get(mode);
        if (StoreMode.DB.equals(storeMode)) {
            ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName());
            ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
                new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});

下面是注解上的别名定义和实例类型定义,别名为“db”,实例类型为“原型模式”:

DataBaseSessionManager.java

@LoadLevel(name = "db", scope = Scope.PROTOTYPE)
public class DataBaseSessionManager extends AbstractSessionManager

类加载资源文件路径和文件名(接口完整类型名)规范以及内容:

类加载资源文件.png

3. 设计模式/面向对象

1.业务本身涉及到很多处理流程,在流程固化时多处使用了模版方法模式。

2.单例模式很常用,各类holder存在。

3.本地线程变量多处使用。

4.函数式接口多处使用,好处是灵活,缺点是抽象层次更高,不够贴近实际的业务语义。

5.注重面向接口编程,合理使用窄接口。

4. 部分可以改善的点

● 循环依赖,A包含B,B包含A,此类用法很多,有的还好,有的可以规避,例如BranchCommitRequest的调用关系,是作为充血对象好些,还是简单的作为一个数据对象好些。

● 系统中很多控制类和业务类实现了相同的业务接口,实际两者不是相同层面的东西,应该区分开,例如DefaultRMHandler、DefaultCore,它们和其他同级子类不是一个层面的,不一定要强制实现相同的接口。

● 部分接口定义不够准确,导致实现时出现无效方法、无效方法参数实现。例如AbstractRMHandler和DefaultCoordinator。

● ......

但不管怎么样,都应对老代码保持敬畏之心。

.end


其他阅读:

萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃