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

347 阅读7分钟

banner窄.png

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

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管理涉及的核心类结构如下:

image.png

类或接口描述
SessionLifecyclesession生命周期接口,定义session生命周期方法
SessionStorablesession编解码接口,将session内容编码和解码
SessionHelpersession助手类,助手的作用通常有方法复用和避免主类膨胀
GlobalSession全局session
BranchSession分支session
GlobalSessionLock全局session锁
SessionHoldersession holder类,一般定义为Holder,就是要缓存对象实例,并且可以在多个对象之间通过静态方法获取,不需要通过方法参数或者构造器参数传递
SessionManagersession管理者接口
TransactionStoreManagersession存储管理接口,定义session的读写操作
AbstractSessionManager抽象session管理者,定义session管理的公共方法
DataBaseSessionManager数据库session管理者
FileSessionManager文件session管理者
RedisSessionManagerRedis session管理者
TransactionWriteStoresession写存储类,只是封装了SessionStorable的子类,没有做实际的持久化操作
DataBaseTransactionStoreManager数据库session持久化类,将session信息存储到数据库中
FileTransactionStoreManager文件session持久化类,将session信息存储文件中
RedisTransactionStoreManagerredis session持久化类,将session信息存储Redis中

3. 部分源码

3.1 编解码

编码的目的是做持久化,避免会话丢失。

3.1.1 编码格式

● 将数据编码为字节。

● 顺序编码,如果是int和long型的属性直接存入,string类型的数据转换为字节后先计算长度,先存入short类型的长度,然后存入byte[]数据,这样解码时就能按照顺序和事先设置的数据长度正确获取到数据。

image.png

● 数据存入ByteBuffer,转换为byte[]。

3.1.2 GlobalSession编码

GlobalSession编码后数据有大小限制,通过参数file.maxGlobalSessionSize配置。

编码字段和顺序如下:

字段描述
transactionId全局事务ID
timeoutsession超时时间
applicationId应用标识
transactionServiceGroup事务分组
transactionName全局事务名称
xid全局会话标识
applicationData应用数据
beginTimesession开始时间
statussession状态

GlobalSession.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

    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表:

字段名数据类型
xidvarchar(96)
transaction_idlong
STATUSint
application_idvarchar(32)
transaction_service_groupvarchar(32)
transaction_namevarchar(128)
timeoutint
begin_timelong
application_datavarchar(500)
gmt_createTIMESTAMP(6)
gmt_modifiedTIMESTAMP(6)

branch_table表:

字段名数据类型
xidvarchar(96)
transaction_idlong
branch_idlong
resource_group_idvarchar(32)
resource_idvarchar(32)
lock_keyvarchar(64)
branch_typevarchar(32)
statusint
client_idvarchar(128)
application_datavarchar(500)
gmt_createTIMESTAMP(6)
gmt_modifiedTIMESTAMP(6)

3.2.1.3 类结构

image.png

描述
DataBaseSessionManager数据库模式session管理入口类
DataBaseTransactionStoreManagerdb事务存储管理类,DB操作入口
LogOperation日志操作类型,枚举类
GlobalSession全局session
BranchSession分支session
GlobalTransactionDO全局事务数据对象,更新DB使用
BranchTransactionDO分支事务数据对象,更新DB使用
SessionConvertersession对象转换器
LogStore日志存储接口
LogStoreDataBaseDAO日志存储db实现类
LogStoreSqlsFactory日志存储SQL生成器工厂
LogStoreSqls日志存储SQL生成器接口
AbstractLogStoreSqls日志存储SQL生成器抽象类,下面的子类负责生成各种类型数据库对应的操作SQL

3.2.2 redis持久化

redis持久化不涉及配置,通过jedis访问redis。

3.2.2.1 类结构

image.png

RedisSessionManager为session管理入口,RedisTransactionStoreManager为持久化入口类,使用jedis和Pipeline类来访问redis,熟悉对应方法则可。

3.2.3 文件持久化

3.2.3.1 相关配置

描述
store.file.dirsession文件存储路径,默认值sessionStore
store.file.flushDiskModesession文件刷盘方式,sync:同步,async:异步
store.file.fileWriteBufferCacheSize写文件buffer缓存大小,默认1024 * 16

3.2.3.2 类结构

image.png

描述
FileSessionManager文件处理模式session管理者,入口类
FileTransactionStoreManager文件管理模式session持久化管理者
SessionStorablesession存储接口
TransactionWriteStore封装了GlobalSession和BranchSession,并做编解码,没有做实际的写操作
WriteDataFileRunnable数据文件操作线程,FileTransactionStoreManager会将各个StoreRequest请求发送给它来处理
StoreRequestsession存储操作接口
SyncFlushRequest同步刷新请求,同步刷新文件内容到磁盘
AsyncFlushRequest异步刷新请求,异步刷新文件内容到磁盘
CloseFileRequest关闭文件请求,关闭文件

end.


其他阅读:

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