Seata源码(十二)Session管理和持久化


铿然架构 | 作者 / 铿然一叶 这是铿然架构的第 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.


阅过留痕,点赞,收藏,关注三连!

相关推荐
公贵买其鹿5 分钟前
List深拷贝后,数据还是被串改
java
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫5 小时前
泛型(2)
java
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石5 小时前
12/21java基础
java
李小白665 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp5 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea