1. 概述
Seata的核心流程,框架,处理逻辑大部分已分析,其实还有不少内容可以继续分析,但基于初衷只是作个大致了解,做到心中有数,而非深度技术选型或者要出一本关于seata源码的书。所以暂且到这里,本篇是Seata源码分析的最后一篇,简单做个小结和补充。
2. 可以直接借鉴的
部分受限版本,可能不是最新用法。
2.1 压缩算法
2.2 序列化算法
2.3 配置中心
2.4 服务注册发现
2. 机制/框架
2.1 消息框架
seata的整个消息框架功能比较全面,包括编解码、数据压缩、序列化、消息合并、同步/异步处理、消息派发,扩展接口等等,基本可以照抄,在自己的业务开发中借鉴。
需要完善的部分是机机鉴权部分,实现零信任架构。
2.2 消息派发
2.2.1 基本框架
在Seata源码中,不同的请求消息对应不同的RemotingProcessor:
并且Seata也实现了一套完整的流程处理框架。
通常,对于客户端/服务端交互场景,当有不同的业务需要处理时,可以按照如下的通用框架抽象:
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的事件通知处理,类结构如下:
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的类实例管理框架如下:
| 类/条目 | 描述 |
|---|---|
| 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
类加载资源文件路径和文件名(接口完整类型名)规范以及内容:
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基础(五)函数式接口-复用,解耦之利刃