Seata解析-文件会话管理器FileSessionManager详解

x33g5p2x  于2021-12-21 转载在 其他  
字(7.2k)|赞(0)|评价(0)|浏览(399)

本文基于seata 1.3.0版本

在《Seata解析-TC会话管理器SessionManager》中介绍了SessionManager管理器有三个实现类:DataBaseSessionManager、FileSessionManager、RedisSessionManager,可以通过store.mode指定需要使用的管理器实现类,store.mode在file.conf文件中配置,有三个值:db、file、redis,分别对应了上面三个实现类。这三个管理器的区别在于事务日志的写入位置不用,比如DataBaseSessionManager将事务日志写入数据库,FileSessionManager是写入文件。
本文将分析FileSessionManager,另外两个和FileSessionManager类似,以后文章在分析介绍。

一、FileSessionManager

FileSessionManager从名字上可以看出这个管理器和文件相关。
下面先来介绍FileSessionManager如何被创建的。

1、创建

在服务端启动的时候也就是执行Server类的main方法时,会执行下面这行代码:

SessionHolder.init(parameterParser.getStoreMode());

入参parameterParser.getStoreMode()表示从启动命令里面获取设置的存储模式,可以通过“-m”在启动命令里面指定存储模式,和在file.conf文件了设置store.mode是一样的,我们来看一下SessionHolder.init:

public static void init(String mode) throws IOException {
		//如果启动命令里面设置为空,那么从file.conf文件里获取
		//所以启动命令可以覆盖配置文件
        if (StringUtils.isBlank(mode)) {
            mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE);
        }
        StoreMode storeMode = StoreMode.get(mode);
        //下面根据store.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});
            RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
                new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
            RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
                new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
        } else if (StoreMode.FILE.equals(storeMode)) {
            String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR,
                DEFAULT_SESSION_STORE_FILE_DIR);
            if (StringUtils.isBlank(sessionStorePath)) {
                throw new StoreException("the {store.file.dir} is empty.");
            }
            ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Object[] {ROOT_SESSION_MANAGER_NAME, sessionStorePath});
            ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Class[] {String.class, String.class}, new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME, null});
            RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Class[] {String.class, String.class}, new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME, null});
            RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Class[] {String.class, String.class}, new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME, null});
        } else if (StoreMode.REDIS.equals(storeMode)) {
            ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.REDIS.getName());
            ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
                StoreMode.REDIS.getName(), new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});
            RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
                StoreMode.REDIS.getName(), new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
            RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
                StoreMode.REDIS.getName(), new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
        } else {
            // unknown store
            throw new IllegalArgumentException("unknown store mode:" + mode);
        }
        reload();
    }

上面init方法我们只关注file分支,也就是FileSessionManager的创建。

//从file.conf文件中查找属性store.file.dir的值,该属性设置了事务日志的存储目录
            //默认目录为sessionStore
            String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR,
                DEFAULT_SESSION_STORE_FILE_DIR);
            if (StringUtils.isBlank(sessionStorePath)) {
                throw new StoreException("the {store.file.dir} is empty.");
            }
            //创建根会话管理器,ROOT_SESSION_MANAGER_NAME指定了文件名:root.data,该会话管理器的数据会存储到root.data文件中
            ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Object[] {ROOT_SESSION_MANAGER_NAME, sessionStorePath});
            // 异步提交事务管理器,
            // 第一个参数ASYNC_COMMITTING_SESSION_MANAGER_NAME设置了文件名,表示该管理器数据存储到该文件中,
            // 第二个参数指定了文件的路径,这里设置为null,表示该管理器数据不存储文件,因此第一个参数其实没有用处
            //以下两个管理创建使用的参数含义类似。
            ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Class[] {String.class, String.class}, new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME, null});
            //重试提交事务管理器
            RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Class[] {String.class, String.class}, new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME, null});
            //重试回滚事务管理器
            RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                new Class[] {String.class, String.class}, new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME, null});

从上面的分析中可以看出,FileSessionManager类名的中的file其实指的是文件root.data,根会话管理器会将事务日志写入该文件中。
上面一共创建了四个会话管理器。关于这四个会话管理器的作用,本文后面介绍。
下面来看一下构造方法:

public FileSessionManager(String name, String sessionStoreFilePath) throws IOException {
		//调用父类的构造方法,主要就是设置管理器的名字
        super(name);
        if (StringUtils.isNotBlank(sessionStoreFilePath)) {
         	//如果路径不为空的,则创建FileTransactionStoreManager对象
            transactionStoreManager = new FileTransactionStoreManager(
                    sessionStoreFilePath + File.separator + name, this);
        } else {
        	//如果路径是空的,那么事务日志无法写入日志文件,seata提供了一个writeSession空实现
        	//任何写入都默认是成功的
            transactionStoreManager = new AbstractTransactionStoreManager() {
                @Override
                public boolean writeSession(LogOperation logOperation, SessionStorable session) {
                    return true;
                }
            };
        }
    }

FileSessionManager还提供了一个属性:

private Map<String, GlobalSession> sessionMap = new ConcurrentHashMap<>();

该属性的作用是在内存中记录所有的全局事务,key是事务id,因为事务日志写入了文件,而从文件中获取关于全局事务信息非常影响性能,所以FileSessionManager在将日志写入文件的同时,也会更新sessionMap。当seata重启后,FileSessionManager也会读取日志文件,将事务信息写入sessionMap中。
因为FileSessionManager涉及的方法也比较多,我们从中抽取两个方法看一下:

public void addGlobalSession(GlobalSession session) throws TransactionException {
		//调用父类方法将session写入文件中
        super.addGlobalSession(session);
        //然后更新内存
        sessionMap.put(session.getXid(), session);
    }

    @Override
    public GlobalSession findGlobalSession(String xid)  {
    	//查询时直接从内存中读取
        return sessionMap.get(xid);
    }
    public void removeGlobalSession(GlobalSession session) throws TransactionException {
    	//删除时,先写入文件,然后从内存中删除
        super.removeGlobalSession(session);
        sessionMap.remove(session.getXid());
    }

2、日志写入

在FileSessionManager的构造方法中创建了FileTransactionStoreManager对象,该对象负责将事务日志写入文件。比如FileSessionManager的addGlobalSession方法最终会调用FileTransactionStoreManager的writeSession方法将事务信息写入文件。
因为FileTransactionStoreManager类内容非常多,所以本文只介绍写入流程,源码解析在以后的文章介绍。

seata保留两个日志文件,一个是当前正在写入的文件root.data,另一个是历史文件,历史文件的文件名为root.data.1。每当有新的历史文件生成时,都会将之前的历史文件覆盖。
在上面的流程图中有个问题是为什么日志文件切换时要将超过30分钟未提交的事务重新写入文件?因为事务文件只保留两个,而且日志文件切换时间间隔是30分钟,如果不把超过30分钟的事务重新写入文件一遍,那么日志文件切换时会覆盖之前写入文件的事务信息,这样seata一旦重启,这些超过30分钟的事务信息就无法恢复。

3、重新载入

在第一小节介绍init方法时,在该方法的最后调用了reload方法。该方法用于读取两个日志文件,然后将日志文件里面的未提交或者未回滚的事务信息写入到sessionMap中。这样未结束的事务信息就可以重新恢复,事务信息不会丢失。

4、总结

FileSessionManager的功能分为两个方面:一是在sessionMap中保存所有的事务对象GlobalSession,其保存着GlobalSession的最新数据,二是将GlobalSession的变化情况通过FileTransactionStoreManager写入日志文件中。

二、四种功能会话管理器介绍

在上面第一小节的init方法中可以看到,针对每种存储模式,seata都提供了四种不同功能的会话管理器:根会话管理器,异步提交事务管理器,重试提交事务管理器,重试回滚事务管理器。这四种管理器的使用场景不同。

  • 根会话管理器:所有的事务新增删除、状态修改都要通过该管理器完成,全局所有的事务信息都在该管理器中保存。当全局事务提交或者回滚时,就会从该事务管理器中删除,即使提交或者回滚失败也会从管理器中删除。
  • 异步提交事务管理器:当全局事务可以异步提交时,会将全局事务对象添加到该事务管理器中,后续异步线程访问该管理器,并从中取出所有的事务对象进行后续处理。
  • 重试提交事务管理器:当事务提交失败时,那么该全局事务就可能会被移交给重试提交事务管理器,这样事务的重试提交就由重试提交事务管理器处理。但是在AT模式下,如果事务提交失败会继续由异步提交事务管理器继续重试。
  • 重试回滚事务管理器:当全局事务需要回滚时,如果回滚失败,那么该事务会被加入到重试回滚事务管理器中,有该管理器异步再次通知各个分支事务回滚。

相关文章