铿然架构 | 作者 / 铿然一叶 这是铿然架构的第 102 篇原创文章
相关阅读:
萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
Seata源码(一)初始化
Seata源码(二)事务基础对象
Seata源码(三)事务处理类结构和流程
Seata源码(四)全局锁GlobalLock
Seata源码(五)Seata数据库操作
Seata源码(六)Seata的undo日志操作
Seata源码(七)Seata事务故障处理
Seata源码(八)Seata事务生命周期hook
Seata源码(九)TCC核心类和处理逻辑
Seata源码(十)RM接收到请求后的调用过程
Seata源码(十一)TC接收到请求后的处理过程\
1. 概述
1.1 全局session
在Seata全局事务处理过程中,涉及到RM、TM、TC之间的多次交互,为了识别和获取到整个交互过程中的数据,就需要通过session来统一管理,每个全局事务都有一个独立session,就好比在某东购物,你和客服人员之间的完整对话就是一个session。
1.2 关键属性
1.2.1 transactionId
全局事务ID,在创建GlobalSession时通过UUIDGenerator.generateUUID()生成。
1.2.2 xid
在创建GlobalSession时生成。
格式:ipAddress + ":" + port + ":" + transactionId
作用如下:
1.TM向TC申请全局事务,全局事务创建成功并生成全局唯一的XID;
2.XID在微服务调用链路的上下文中传播;
3.RM向TC注册分支事务,将其纳入XID对应全局事务的管理;
4.TM向TC发起针对XID的全局提交或回滚决议;
5.TC调度XID下管辖的全部分支事务完成提交或回滚请求;
1.2.3 applicationId
应用ID,标识一个微服务,来自配置seata.application-id。
1.2.4 transactionServiceGroup
事务分组,来自配置seata.tx-service-group。
参考:事务分组专题
1.2.5 transactionName
事务名称,来自GlobalTransactional注解上的name属性。
1.2.6 resourceId
资源标识,取jdbc的url问号之前的部分,例如: jdbc:mysql://${SITEDB_ADDRESS}:3306/sitedb?serverTimezone=GMT&allowPublicKeyRetrieval=true
取: jdbc:mysql://${SITEDB_ADDRESS}:3306/sitedb
2. 核心类结构
session管理涉及的核心类结构如下:
类或接口 | 描述 |
---|---|
SessionLifecycle | session生命周期接口,定义session生命周期方法 |
SessionStorable | session编解码接口,将session内容编码和解码 |
SessionHelper | session助手类,助手的作用通常有方法复用和避免主类膨胀 |
GlobalSession | 全局session |
BranchSession | 分支session |
GlobalSessionLock | 全局session锁 |
SessionHolder | session holder类,一般定义为Holder,就是要缓存对象实例,并且可以在多个对象之间通过静态方法获取,不需要通过方法参数或者构造器参数传递 |
SessionManager | session管理者接口 |
TransactionStoreManager | session存储管理接口,定义session的读写操作 |
AbstractSessionManager | 抽象session管理者,定义session管理的公共方法 |
DataBaseSessionManager | 数据库session管理者 |
FileSessionManager | 文件session管理者 |
RedisSessionManager | Redis session管理者 |
TransactionWriteStore | session写存储类,只是封装了SessionStorable的子类,没有做实际的持久化操作 |
DataBaseTransactionStoreManager | 数据库session持久化类,将session信息存储到数据库中 |
FileTransactionStoreManager | 文件session持久化类,将session信息存储文件中 |
RedisTransactionStoreManager | redis session持久化类,将session信息存储Redis中 |
3. 部分源码
3.1 编解码
编码的目的是做持久化,避免会话丢失。
3.1.1 编码格式
● 将数据编码为字节。
● 顺序编码,如果是int和long型的属性直接存入,string类型的数据转换为字节后先计算长度,先存入short类型的长度,然后存入byte[]数据,这样解码时就能按照顺序和事先设置的数据长度正确获取到数据。
● 数据存入ByteBuffer,转换为byte[]。
3.1.2 GlobalSession编码
GlobalSession编码后数据有大小限制,通过参数file.maxGlobalSessionSize配置。
编码字段和顺序如下:
字段 | 描述 |
---|---|
transactionId | 全局事务ID |
timeout | session超时时间 |
applicationId | 应用标识 |
transactionServiceGroup | 事务分组 |
transactionName | 全局事务名称 |
xid | 全局会话标识 |
applicationData | 应用数据 |
beginTime | session开始时间 |
status | session状态 |
GlobalSession.java
java
public byte[] encode() {
byte[] byApplicationIdBytes = applicationId != null ? applicationId.getBytes() : null;
byte[] byServiceGroupBytes = transactionServiceGroup != null ? transactionServiceGroup.getBytes() : null;
byte[] byTxNameBytes = transactionName != null ? transactionName.getBytes() : null;
byte[] xidBytes = xid != null ? xid.getBytes() : null;
byte[] applicationDataBytes = applicationData != null ? applicationData.getBytes() : null;
int size = calGlobalSessionSize(byApplicationIdBytes, byServiceGroupBytes, byTxNameBytes, xidBytes,
applicationDataBytes);
if (size > MAX_GLOBAL_SESSION_SIZE) {
throw new RuntimeException("global session size exceeded, size : " + size + " maxBranchSessionSize : " +
MAX_GLOBAL_SESSION_SIZE);
}
ByteBuffer byteBuffer = byteBufferThreadLocal.get();
//recycle
byteBuffer.clear();
byteBuffer.putLong(transactionId);
byteBuffer.putInt(timeout);
if (byApplicationIdBytes != null) {
byteBuffer.putShort((short)byApplicationIdBytes.length);
byteBuffer.put(byApplicationIdBytes);
} else {
byteBuffer.putShort((short)0);
}
if (byServiceGroupBytes != null) {
byteBuffer.putShort((short)byServiceGroupBytes.length);
byteBuffer.put(byServiceGroupBytes);
} else {
byteBuffer.putShort((short)0);
}
if (byTxNameBytes != null) {
byteBuffer.putShort((short)byTxNameBytes.length);
byteBuffer.put(byTxNameBytes);
} else {
byteBuffer.putShort((short)0);
}
if (xidBytes != null) {
byteBuffer.putInt(xidBytes.length);
byteBuffer.put(xidBytes);
} else {
byteBuffer.putInt(0);
}
if (applicationDataBytes != null) {
byteBuffer.putInt(applicationDataBytes.length);
byteBuffer.put(applicationDataBytes);
} else {
byteBuffer.putInt(0);
}
byteBuffer.putLong(beginTime);
byteBuffer.put((byte)status.getCode());
byteBuffer.flip();
byte[] result = new byte[byteBuffer.limit()];
byteBuffer.get(result);
return result;
}
3.1.3 GlobalSession解码
解码时先将byte[]数据放入ByteBuffer,然后根据编码规则获取数据:
GlobalSession.java
java
public void decode(byte[] a) {
ByteBuffer byteBuffer = ByteBuffer.wrap(a);
this.transactionId = byteBuffer.getLong();
this.timeout = byteBuffer.getInt();
short applicationIdLen = byteBuffer.getShort();
if (applicationIdLen > 0) {
byte[] byApplicationId = new byte[applicationIdLen];
byteBuffer.get(byApplicationId);
this.applicationId = new String(byApplicationId);
}
short serviceGroupLen = byteBuffer.getShort();
if (serviceGroupLen > 0) {
byte[] byServiceGroup = new byte[serviceGroupLen];
byteBuffer.get(byServiceGroup);
this.transactionServiceGroup = new String(byServiceGroup);
}
short txNameLen = byteBuffer.getShort();
if (txNameLen > 0) {
byte[] byTxName = new byte[txNameLen];
byteBuffer.get(byTxName);
this.transactionName = new String(byTxName);
}
int xidLen = byteBuffer.getInt();
if (xidLen > 0) {
byte[] xidBytes = new byte[xidLen];
byteBuffer.get(xidBytes);
this.xid = new String(xidBytes);
}
int applicationDataLen = byteBuffer.getInt();
if (applicationDataLen > 0) {
byte[] applicationDataLenBytes = new byte[applicationDataLen];
byteBuffer.get(applicationDataLenBytes);
this.applicationData = new String(applicationDataLenBytes);
}
this.beginTime = byteBuffer.getLong();
this.status = GlobalStatus.get(byteBuffer.get());
}
3.1.4 BranchSession编码
BranchSession编码后数据有大小限制,通过参数file.maxBranchSessionSize。
编码方式和GlobalSession一样,编码字段和顺序如下:
字段 | 描述 |
---|---|
transactionId | 全局事务标识 |
branchId | 分支事务标识 |
resourceId | 资源标识 |
lockKey | 分支事务加锁key,根据undo镜像生成,表的主键数据 |
clientId | 客户端标识 |
applicationData | 应用数据 |
xid | 全局会话标识 |
branchType | 分支事务类型 |
status | 分支事务状态 |
3.1.4 BranchSession解码
编码方式和GlobalSession一样,解码字段同编码字段。
3.2 会话持久化
3.2.1 db持久化
3.2.1.1 相关配置
序号 | 类型 |
---|---|
store.db.queryLimit | 查询表记录条数限制 |
store.db.datasource | 数据源类型,dbcp,druid,hikari |
store.db.dbType | 数据库类型,mysql、oceanbase,h2,oracle,postgresql |
store.db.globalTable | 全局事务表,默认值global_table |
store.db.branchTable | 分支事务表,默认值branch_table |
3.2.1.2 相关表
global_table表:
字段名 | 数据类型 |
---|---|
xid | varchar(96) |
transaction_id | long |
STATUS | int |
application_id | varchar(32) |
transaction_service_group | varchar(32) |
transaction_name | varchar(128) |
timeout | int |
begin_time | long |
application_data | varchar(500) |
gmt_create | TIMESTAMP(6) |
gmt_modified | TIMESTAMP(6) |
branch_table表:
字段名 | 数据类型 |
---|---|
xid | varchar(96) |
transaction_id | long |
branch_id | long |
resource_group_id | varchar(32) |
resource_id | varchar(32) |
lock_key | varchar(64) |
branch_type | varchar(32) |
status | int |
client_id | varchar(128) |
application_data | varchar(500) |
gmt_create | TIMESTAMP(6) |
gmt_modified | TIMESTAMP(6) |
3.2.1.3 类结构
类 | 描述 |
---|---|
DataBaseSessionManager | 数据库模式session管理入口类 |
DataBaseTransactionStoreManager | db事务存储管理类,DB操作入口 |
LogOperation | 日志操作类型,枚举类 |
GlobalSession | 全局session |
BranchSession | 分支session |
GlobalTransactionDO | 全局事务数据对象,更新DB使用 |
BranchTransactionDO | 分支事务数据对象,更新DB使用 |
SessionConverter | session对象转换器 |
LogStore | 日志存储接口 |
LogStoreDataBaseDAO | 日志存储db实现类 |
LogStoreSqlsFactory | 日志存储SQL生成器工厂 |
LogStoreSqls | 日志存储SQL生成器接口 |
AbstractLogStoreSqls | 日志存储SQL生成器抽象类,下面的子类负责生成各种类型数据库对应的操作SQL |
3.2.2 redis持久化
redis持久化不涉及配置,通过jedis访问redis。
3.2.2.1 类结构
RedisSessionManager为session管理入口,RedisTransactionStoreManager为持久化入口类,使用jedis和Pipeline类来访问redis,熟悉对应方法则可。
3.2.3 文件持久化
3.2.3.1 相关配置
类 | 描述 |
---|---|
store.file.dir | session文件存储路径,默认值sessionStore |
store.file.flushDiskMode | session文件刷盘方式,sync:同步,async:异步 |
store.file.fileWriteBufferCacheSize | 写文件buffer缓存大小,默认1024 * 16 |
3.2.3.2 类结构
类 | 描述 |
---|---|
FileSessionManager | 文件处理模式session管理者,入口类 |
FileTransactionStoreManager | 文件管理模式session持久化管理者 |
SessionStorable | session存储接口 |
TransactionWriteStore | 封装了GlobalSession和BranchSession,并做编解码,没有做实际的写操作 |
WriteDataFileRunnable | 数据文件操作线程,FileTransactionStoreManager会将各个StoreRequest请求发送给它来处理 |
StoreRequest | session存储操作接口 |
SyncFlushRequest | 同步刷新请求,同步刷新文件内容到磁盘 |
AsyncFlushRequest | 异步刷新请求,异步刷新文件内容到磁盘 |
CloseFileRequest | 关闭文件请求,关闭文件 |
end.