1-fescar(seata)源码分析-server端

1-fescar源码分析-server端

一.官网介绍(以下截取至自官网)

https://github.com/alibaba/fescar/wiki/%E6%A6%82%E8%A7%88

  • 1.1 定义一个分布式事务

    首先,很自然的,我们可以把一个分布式事务理解成一个包含了若干 分支事务 的 全局事务。全局事务 的职责是协调其下管辖的 分支事务 达成一致,要么一起成功提交,要么一起失败回滚。此外,通常 分支事务 本身就是一个满足 ACID 的 本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。

    image.png
  • 定义三个组件


    image.png
  • Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

  • Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

  • Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

  • 具体配合


    image.png
  • 具体流程

  • TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  • XID 在微服务调用链路的上下文中传播。
  • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
  • TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
  • 提交与回滚


    image.png

--

二、主要流程(模块)概要

  • 1.fescar-server:TC模块,server端启动:fescar的主服务,主要是netty服务端,接收TM的提交或者回滚消息以及向RM发送提交或者回滚消息指令等。
  • 2.fescar-tm:TM模块,主要提供事务管理,向TC发送消息,触发事务的提交或者回滚。
  • 3.rm-datasource: RM模块,数据库相关,覆盖rpc服务的执行逻辑,备份脚本执行前后结果,生成回滚脚本,向TC注册事务分支,执行正向脚本(逻辑)。
  • 4.fescar-core:核心对象打包
  • 5.fescar-spring:提供注解,保证对业务的非侵入性
1.服务启动,通过@GlobalTransactional执行到GlobalTransactionalInterceptor拦截器,进入对业务逻辑进行增强,执行TransactionalTemplate逻辑
2.TransactionalTemplate对全局事务的获取、执行业务逻辑、异常回滚、事务提交进行统筹,并同时通过服务的维持channel将每个步骤与server的netty进行交涉.
3.同时TransactionalTemplate在执行业务逻辑的同时,通过spring jdbc的覆盖,通过直接调用RM模块生成相关的备份及回滚脚本。
4.server接收到相关服务的消息后,触发相关的动作,并最终触发RM去进行数据库层面的操作。

--

三、(原理)源码分析

其实细看上面官网的介绍,基本能清楚fescar的大致思想。下面从源码角度出发一步一步的将流程以代码的形式读完。

3.1 demo
  • 先看下官网的结构图:


    image.png
项目中存在官方的example模块,里面就模拟了上图的相关流程:先回到本节主题:server
3.2.TC server
  • 3.2.1.Server服务端启动

    public class Server {
    
        private static final ThreadPoolExecutor WORKING_THREADS = new ThreadPoolExecutor(100, 500, 500, TimeUnit.SECONDS, new LinkedBlockingQueue(20000), new ThreadPoolExecutor.CallerRunsPolicy());
        public static void main(String[] args) throws IOException {
    
            // netty实现的一个简单的rpc服务端
            RpcServer rpcServer = new RpcServer(WORKING_THREADS);
            
            int port = 8091;
            if (args.length == 0) {
                rpcServer.setListenPort(port);
            }
    
            if (args.length > 0) {
                try {
                    port = Integer.parseInt(args[0]);
                } catch (NumberFormatException e) {
                    System.err.println("Usage: sh fescar-server.sh $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA");
                    System.exit(0);
                }
                rpcServer.setListenPort(port);
            }
            
            String dataDir = null;
            if (args.length > 1) {
                dataDir = args[1];
            }
    
            /**
             * SessionHolder主要负责事务信息存储,对应的ROOT_SESSION_MANAGER,ASYNC_COMMITTING_SESSION_MANAGER,RETRY_COMMITTING_SESSION_MANAGER,RETRY_ROLLBACKING_SESSION_MANAGER分别对应相应的文件存储到本地
             */
            SessionHolder.init(dataDir);
    
            //协调者初始化
            /**
             * DefaultCoordinator 继承至 AbstractTCInboundHandler, AbstractTCInboundHandler handle方法会调用doGlobalRollback()方法,doGlobalRollback()方法会调用DefaultCore的rollback()
             */
            DefaultCoordinator coordinator = new DefaultCoordinator(rpcServer);
            coordinator.init();
            rpcServer.setHandler(new DefaultCoordinator(rpcServer));
    
            //全局事务发号器初始化
            UUIDGenerator.init(1);
    
            XID.setIpAddress(NetUtil.getLocalIp());
            XID.setPort(rpcServer.getListenPort());
    
            /**
             * 完成服务的启动及监听
             * RpcServer启动之后就会监听来自TM的消息,如上代码所示,在开启RpcServer之前会注册一个DefaultServerMessageListenerImpl用于对TM发过来的消息进行监听。
             * */
            rpcServer.init();
            System.exit(0);
        }
    }
    
  • RpcServer就是netty一个服务端

    public RpcServer(ThreadPoolExecutor messageExecutor) {
        super(new NettyServerConfig(), messageExecutor);
    }
    
    #AbstractRpcRemotingServer
    public AbstractRpcRemotingServer(final NettyServerConfig nettyServerConfig,
                                     final ThreadPoolExecutor messageExecutor, final ChannelHandler... handlers) {
        super(messageExecutor);
        this.serverBootstrap = new ServerBootstrap();
        this.nettyServerConfig = nettyServerConfig;
        if (NettyServerConfig.enableEpoll()) {
            this.eventLoopGroupBoss = new EpollEventLoopGroup(nettyServerConfig.getBossThreadSize(),
                new NamedThreadFactory(nettyServerConfig.getBossThreadPrefix(), nettyServerConfig.getBossThreadSize()));
            this.eventLoopGroupWorker = new EpollEventLoopGroup(nettyServerConfig.getServerWorkerThreads(),
                new NamedThreadFactory(nettyServerConfig.getWorkerThreadPrefix(),
                    nettyServerConfig.getServerWorkerThreads()));
        } else {
            this.eventLoopGroupBoss = new NioEventLoopGroup(nettyServerConfig.getBossThreadSize(),
                new NamedThreadFactory(nettyServerConfig.getBossThreadPrefix(), nettyServerConfig.getBossThreadSize()));
            this.eventLoopGroupWorker = new NioEventLoopGroup(nettyServerConfig.getServerWorkerThreads(),
                new NamedThreadFactory(nettyServerConfig.getWorkerThreadPrefix(),
                    nettyServerConfig.getServerWorkerThreads()));
        }
        if (null != handlers) {
            channelHandlers = handlers;
        }
        // init listenPort in constructor so that getListenPort() will always get the exact port
        setListenPort(nettyServerConfig.getDefaultListenPort());
    }
    

    看到这里就是netty服务端构造的惯用逻辑了。

  • 紧接着初始化SessionHolder:

    SessionHolder.init(dataDir);
    
    # SessionHolder
    public static void init(String sessionStorePath) throws IOException {
        if (sessionStorePath == null) {
            ROOT_SESSION_MANAGER = new DefaultSessionManager(ROOT_SESSION_MANAGER_NAME);
            ASYNC_COMMITTING_SESSION_MANAGER = new DefaultSessionManager(ASYNC_COMMITTING_SESSION_MANAGER_NAME);
            RETRY_COMMITTING_SESSION_MANAGER = new DefaultSessionManager(RETRY_COMMITTING_SESSION_MANAGER_NAME);
            RETRY_ROLLBACKING_SESSION_MANAGER = new DefaultSessionManager(RETRY_ROLLBACKING_SESSION_MANAGER_NAME);
        } else {
            if (!sessionStorePath.endsWith("/")) {
                sessionStorePath = sessionStorePath + "/";
            }
            ROOT_SESSION_MANAGER = new FileBasedSessionManager(ROOT_SESSION_MANAGER_NAME, sessionStorePath);
            ASYNC_COMMITTING_SESSION_MANAGER = new DefaultSessionManager(ASYNC_COMMITTING_SESSION_MANAGER_NAME);
            RETRY_COMMITTING_SESSION_MANAGER = new DefaultSessionManager(RETRY_COMMITTING_SESSION_MANAGER_NAME);
            RETRY_ROLLBACKING_SESSION_MANAGER = new DefaultSessionManager(RETRY_ROLLBACKING_SESSION_MANAGER_NAME);
        }
    }
    

    无非就是暂存各种DefaultSessionManager,共后续任务轮训时使用。DefaultSessionManager继承至
    AbstractSessionManager,看下面父类属性sessionMap,无非就是管理GlobalSession了。
    而GlobalSession就是server根据XID缓存全局事务的对象,里面属性branchSessions记录了全局事务里对应的所有分支事务,这样就能根据TM的相关触发消息去处理全局事务,进而处理分支事务了。

    #AbstractSessionManager
    protected Map<Long, GlobalSession> sessionMap = new ConcurrentHashMap<>();
    
    public class GlobalSession implements SessionLifecycle, SessionStorable {
        private private long transactionId;
        private GlobalStatus status;
        private String applicationId;
        private String transactionServiceGroup;
        private String transactionName;
        private int timeout;
        private long beginTime;
        private boolean active;
        ArrayList<BranchSession> branchSessions = new ArrayList<>();
        ...
    }
    
  • 再者初始化coordinator:

    coordinator.init();
    public void init() {
        retryRollbacking.scheduleAtFixedRate(new Runnable() {
    
            @Override
            public void run() {
                try {
                    handleRetryRollbacking();
                } catch (Exception e) {
                    LOGGER.info("Exception retry rollbacking ... ", e);
                }
            }
        }, 0, 5, TimeUnit.MILLISECONDS);
    
        retryCommitting.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    handleRetryCommitting();
                } catch (Exception e) {
                    LOGGER.info("Exception retry committing ... ", e);
                }
            }
        }, 0, 5, TimeUnit.MILLISECONDS);
    
        asyncCommitting.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    handleAsyncCommitting();
                } catch (Exception e) {
                    LOGGER.info("Exception async committing ... ", e);
                }
            }
        }, 0, 10, TimeUnit.MILLISECONDS);
    
        timeoutCheck.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    timeoutCheck();
                } catch (Exception e) {
                    LOGGER.info("Exception timeout checking ... ", e);
                }
            }
        }, 0, 2, TimeUnit.MILLISECONDS);
    }
    

    这里无非就是启动一些异步任务处理逻辑,其实这里最主要的就是commit异步任务删除回滚日志的逻辑。因为这里涉及到服务的相关触发逻辑,暂时先暂停到此。

  • 继续rpcServer

    rpcServer.init();
    
    #init
    @Override
    public void init() {
        super.init();
        setChannelHandlers(RpcServer.this);
        DefaultServerMessageListenerImpl defaultServerMessageListenerImpl = new DefaultServerMessageListenerImpl(transactionMessageHandler);
        defaultServerMessageListenerImpl.setServerMessageSender(this);
        this.setServerMessageListener(defaultServerMessageListenerImpl);
        super.start();
    }
    
  • 1.父类初始化逻辑

  • 2.设置处理器handler

  • 3.构造消息设置监听器并监听

  • 4.启动服务

    这里最重要的就是设置监听器了,因为这里关乎如何监听TM的事务提交或者回滚消息的。直接看实现:

    #DefaultServerMessageListenerImpl
    @Override
    public void onTrxMessage(long msgId, ChannelHandlerContext ctx, Object message, ServerMessageSender sender) {
        RpcContext rpcContext = ChannelManager.getContextFromIdentified(ctx.channel());
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("server received:" + message + ",clientIp:" + NetUtil.toIpAddress(ctx.channel().remoteAddress()) + ",vgroup:" + rpcContext.getTransactionServiceGroup());
        } else {
            messageStrings.offer(message + ",clientIp:" + NetUtil.toIpAddress(ctx.channel().remoteAddress()) + ",vgroup:" + rpcContext.getTransactionServiceGroup());
        }
        if (!(message instanceof AbstractMessage)) { return; }
        if (message instanceof MergedWarpMessage) {
            AbstractResultMessage[] results = new AbstractResultMessage[((MergedWarpMessage)message).msgs.size()];
            for (int i = 0; i < results.length; i++) {
                final AbstractMessage subMessage = ((MergedWarpMessage)message).msgs.get(i);
                results[i] = transactionMessageHandler.onRequest(subMessage, rpcContext);
            }
            MergeResultMessage resultMessage = new MergeResultMessage();
            resultMessage.setMsgs(results);
            sender.sendResponse(msgId, ctx.channel(), resultMessage);
        } else if (message instanceof AbstractResultMessage) {
            transactionMessageHandler.onResponse((AbstractResultMessage)message, rpcContext);
        }
    }
    

    这里就是监听事务消息的触发点了,看下调用链:

    @Override
    public void dispatch(long msgId, ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof RegisterRMRequest) {
            serverMessageListener.onRegRmMessage(msgId, ctx, (RegisterRMRequest)msg, this,
                checkAuthHandler);
        } else {
            if (ChannelManager.isRegistered(ctx.channel())) {
                serverMessageListener.onTrxMessage(msgId, ctx, msg, this);
            } else {
                try {
                    closeChannelHandlerContext(ctx);
                } catch (Exception exx) {
                    LOGGER.error(exx.getMessage());
                }
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info(String.format("close a unhandled connection! [%s]", ctx.channel().toString()));
                }
            }
        }
    }
    

    继续往上追:

    #AbstractRpcRemoting
    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof RpcMessage) {
            final RpcMessage rpcMessage = (RpcMessage)msg;
            if (rpcMessage.isRequest()) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(String.format("%s msgId:%s, body:%s", this, rpcMessage.getId(), rpcMessage.getBody()));
                }
                try {
                    AbstractRpcRemoting.this.messageExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                dispatch(rpcMessage.getId(), ctx, rpcMessage.getBody());
                            } catch (Throwable th) {
                                LOGGER.error(FrameworkErrorCode.NetDispatch.errCode, th.getMessage(), th);
                            }
                        }
                    });
                }
            }
        }
    

到了netty的channel层了,SocketChannel就是netty本身的消息读写通道,一切消息交换由此追溯,即netty完成通信后触发了监听器逻辑,进而继续TC服务逻辑的处理。

  • 启动服务

    super.start();
    
    #AbstractRpcRemotingServer
    @Override
    public void start() {
        this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupWorker)
            .channel(nettyServerConfig.SERVER_CHANNEL_CLAZZ)
            .option(ChannelOption.SO_BACKLOG, nettyServerConfig.getSoBackLogSize())
            .option(ChannelOption.SO_REUSEADDR, true)
            .childOption(ChannelOption.SO_KEEPALIVE, true)
            .childOption(ChannelOption.TCP_NODELAY, true)
            .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSendBufSize())
            .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketResvBufSize())
            .childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK,
                nettyServerConfig.getWriteBufferHighWaterMark())
            .childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, nettyServerConfig.getWriteBufferLowWaterMark())
            .localAddress(new InetSocketAddress(listenPort))
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0))
                        .addLast(new MessageCodecHandler());
                    if (null != channelHandlers) {
                        addChannelPipelineLast(ch, channelHandlers);
                    }
    
                }
            });
    
        if (nettyServerConfig.isEnableServerPooledByteBufAllocator()) {
            this.serverBootstrap.childOption(ChannelOption.ALLOCATOR, NettyServerConfig.DIRECT_BYTE_BUF_ALLOCATOR);
        }
    
        try {
            ChannelFuture future = this.serverBootstrap.bind(listenPort).sync();
            LOGGER.info("Server started ... ");
            future.channel().closeFuture().sync();
        } catch (InterruptedException exx) {
            throw new RuntimeException(exx);
        }
    }
    

惯用的netty服务启动方式,至此,TC Server服务启动OK,下面总结下。

--

四.TC Server服务端启动总结
  • 1.构造RpcServer的netty服务端
  • 2.初始化SessionHolder,用于初始化各种DefaultSessionManager,DefaultSessionManager就是管理GlobalSession和BranchSession的管理器
  • 3.初始化DefaultCoordinator,DefaultCoordinator就是具体执行提交、回滚、注册分支事务等逻辑的类,同时启动异步执行任务,比如提交全局事务后,异步删除回滚sql。
  • 4.初始化RpcServer,设置事务消息监听器
  • 5.启动RpcServer的netty服务端。

--

五.未完待续。。。

后续分析主要还是根据example官方实例分为:全局事务的获取、事务逻辑执行、事务回滚、事务提交进行。
同时后续每一流程都紧密关联Server,因此还会频繁回到上叙server启动后,收到消息被触发的后续逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容