一、前言

上一节已经将raincat demo工程运行起来了,这一节来分析下raincat启动过程的源码

主要包括:

事务协调者启动过程

事务参与者启动过程

二、协调者启动过程

主要就是在启动类中通过如下代码来启动 netty 服务端

nettyService.start()

三、参与者启动过程概览

参与者在启动过程中,主要做了如下5件事:

(1)保存SpringContext上下文

(2)通过加载spi,来使用用户自定义配置(序列化方式、日志存储方式)

(3)启动Netty客户端,与txManager进行连接,并且维持心跳。

(4)启动事务补偿日志,建表,定时补偿。

(5)启动事务日志事件生产者。将事务补偿日志放入disruptor的环形队列中,由disruptor去异步执行。

时序图如下:

InitServiceImpl

    @Override
public void initialization(final TxConfig txConfig) {
try {
loadSpi(txConfig);
nettyClientService.start(txConfig);
txCompensationService.start(txConfig);
txTransactionEventPublisher.start(txConfig.getBufferSize());
} catch (Exception e) {
throw new TransactionRuntimeException("tx transaction ex:{}:" + e.getMessage());
}
LogUtil.info(LOGGER, () -> "tx transaction init success!"); }

四、保存Spring上下文

源码见 SpringBeanUtils 类

  • 设置Spring 上下文
  • 提供spring bean 的注册与获取方法。
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.raincat.core.helper; import org.springframework.context.ConfigurableApplicationContext; /**
* SpringBeanUtils.
* @author xiaoyu
*/
public final class SpringBeanUtils { private static final SpringBeanUtils INSTANCE = new SpringBeanUtils(); private ConfigurableApplicationContext cfgContext; private SpringBeanUtils() {
if (INSTANCE != null) {
throw new Error("error");
}
} /**
* get SpringBeanUtils.
* @return SpringBeanUtils
*/
public static SpringBeanUtils getInstance() {
return INSTANCE;
} /**
* acquire spring bean.
*
* @param type type
* @param <T> class
* @return bean
*/
public <T> T getBean(final Class<T> type) {
return cfgContext.getBean(type);
} /**
* register bean in spring ioc.
* @param beanName bean name
* @param obj bean
*/
public void registerBean(final String beanName, final Object obj) {
cfgContext.getBeanFactory().registerSingleton(beanName, obj);
} /**
* set application context.
* @param cfgContext application context
*/
public void setCfgContext(final ConfigurableApplicationContext cfgContext) {
this.cfgContext = cfgContext;
}
}

五、加载spi

1.主要操作

  1. 获取序列化方式
  2. 获取 TransactionRecoverRepository(事务恢复的存储方式,示例中其实现是 JdbcTransactionRecoverRepository),并设置其序列化方式。
  3. 将TransactionRecoverRepository注入Spring容器,以便在事务补偿器中使用

InitServiceImpl

/**
* load spi.
*
* @param txConfig {@linkplain TxConfig}
*/
private void loadSpi(final TxConfig txConfig) {
//spi serialize
final SerializeProtocolEnum serializeProtocolEnum
= SerializeProtocolEnum.acquireSerializeProtocol(txConfig.getSerializer());
final ServiceLoader<ObjectSerializer> objectSerializers
= ServiceBootstrap.loadAll(ObjectSerializer.class);
final ObjectSerializer serializer =
StreamSupport.stream(objectSerializers.spliterator(), false)
.filter(s -> Objects.equals(s.getScheme(), serializeProtocolEnum.getSerializeProtocol()))
.findFirst().orElse(new KryoSerializer()); //spi RecoverRepository support
final CompensationCacheTypeEnum compensationCacheTypeEnum
= CompensationCacheTypeEnum.acquireCompensationCacheType(txConfig.getCompensationCacheType()); final ServiceLoader<TransactionRecoverRepository> recoverRepositories
= ServiceBootstrap.loadAll(TransactionRecoverRepository.class);
final TransactionRecoverRepository repository =
StreamSupport.stream(recoverRepositories.spliterator(), false)
.filter(r -> Objects.equals(r.getScheme(), compensationCacheTypeEnum.getCompensationCacheType()))
.findFirst().orElse(new JdbcTransactionRecoverRepository());
//将compensationCache实现注入到spring容器
repository.setSerializer(serializer);
SpringBeanUtils.getInstance().registerBean(TransactionRecoverRepository.class.getName(), repository);
}

2. 作用

SPI的全名为Service Provider Interface,该机制其实就是为接口寻找服务实现类

3. 如何使用

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件

该文件里就是实现该服务接口的具体实现类

而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

4. 优点

基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。

5. 示例

六、启动netty客户端

1 主要操作

Netty客户端启动过程中,主要做了以下几件事:

  • 定期更新TxMagager的信息

启动一个单线程的调度线程池,定期向TxMagager发送post请求,获取Eureka服务注册表中TxMagager的信息(appName,instanceId,homepageUrl)

  • 设置 Bootstrap

安装ChannelInitializer,并将一个ChannelHandler加入管道中

  • 连接Netty服务端

(1)获取前面更新的TxManager的信息(appName,instanceId,homepageUrl)

(2)向TxManager发送Post请求,获取Netty服务器连接信息(host,port)

(3)连接到Netty服务端

(4)通过ChannelFutureListener监听连接成功或失败的事件,若连接失败则定期重试。

2 事件监听

客户端除了连接服务端成功或失败事件监听,还有监听了以下事件。

前面向Netty管道中填充了一个ChannelHandler,这样就能通过ChannelHandler监控Netty生命周期中的消息入站事件:

channelRead

exceptionCaught

channelInactive

channelActive

userEventTriggered

3.Netty客户端启动时序图

七、事务补偿日志启动

1.主要操作

事务补偿日志启动过程中,主要做了以下几件事:

  • 事务补偿日志数据库准备

创建用来存储日志的数据表

  • 定时进行事务补偿

开启线程池,进行定时事务补偿

(1)获取到所有的事务补偿日志,并进行遍历

(2)根据每个日志的事务组ID,向协调者获取到对应的事务组信息

(3)如果整个事务组状态是提交的,而事务参与者自己不是提交的,则进行补偿。----不确定

(4)事务补偿:

反射执行事务参与者的事务,然后向事务协调者发送事务完成消息,最后事务参与者提交事务。

2.时序图

八、事件生产者启动

这里主要使用 disruptor 作为一个高性能环形缓存队列。

补偿日志是异步的,先把日志扔到环形队列,然后由disruptor 的事件消费者进行事务日志补偿的增删改和补偿操作

1.disruptor中的角色

角色 描述 raincat中对应角色
Event 事件 TxTransactionEvent
EventFactory 事件工厂 TxTransactionEventFactory
EventHandler 事件消费者 TxTransactionEventHandler
EventProducer 事件生产者 TxTransactionEventPublisher
EventTranslatorOneArg 3.0版本的Translator,可用来填充RingBuffer的事件槽 TxTransactionEventTranslator

2.主要操作

事件生产者启动过程是一个标准的 disruptor 启动过程,主要是设置事件工厂、事件消费者、设置线程池,然后启动disruptor

   /**
* disruptor start.
*
* @param bufferSize this is disruptor buffer size.
*/
public void start(final int bufferSize) {
disruptor = new Disruptor<>(new TxTransactionEventFactory(), bufferSize, r -> {
AtomicInteger index = new AtomicInteger(1);
return new Thread(null, r, "disruptor-thread-" + index.getAndIncrement());
}, ProducerType.MULTI, new YieldingWaitStrategy());
disruptor.handleEventsWith(txTransactionEventHandler);
disruptor.setDefaultExceptionHandler(new ExceptionHandler<TxTransactionEvent>() {
@Override
public void handleEventException(Throwable ex, long sequence, TxTransactionEvent event) {
LogUtil.error(LOGGER, () -> "Disruptor handleEventException:"
+ event.getType() + event.getTransactionRecover().toString());
} @Override
public void handleOnStartException(Throwable ex) {
LogUtil.error(LOGGER, () -> "Disruptor start exception");
} @Override
public void handleOnShutdownException(Throwable ex) {
LogUtil.error(LOGGER, () -> "Disruptor close Exception ");
}
});
executor = new ThreadPoolExecutor(MAX_THREAD, MAX_THREAD, 0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
TxTransactionThreadFactory.create("raincat-log-disruptor", false),
new ThreadPoolExecutor.AbortPolicy());
disruptor.start();
}

3.事件消费者

这里的事件消费者主要是监听事件,进行事务日志补偿的增删改和补偿操作。

/*
*
* * Licensed to the Apache Software Foundation (ASF) under one or more
* * contributor license agreements. See the NOTICE file distributed with
* * this work for additional information regarding copyright ownership.
* * The ASF licenses this file to You under the Apache License, Version 2.0
* * (the "License"); you may not use this file except in compliance with
* * the License. You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/ package com.raincat.core.disruptor.handler; import com.lmax.disruptor.EventHandler;
import com.raincat.common.enums.CompensationActionEnum;
import com.raincat.core.compensation.TxCompensationService;
import com.raincat.core.disruptor.event.TxTransactionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* Disroptor handler.
*
* @author xiaoyu(Myth)
*/
@Component
public class TxTransactionEventHandler implements EventHandler<TxTransactionEvent> { @Autowired
private TxCompensationService txCompensationService; @Override
public void onEvent(final TxTransactionEvent txTransactionEvent, final long sequence, final boolean endOfBatch) {
if (txTransactionEvent.getType() == CompensationActionEnum.SAVE.getCode()) {
txCompensationService.save(txTransactionEvent.getTransactionRecover());
} else if (txTransactionEvent.getType() == CompensationActionEnum.DELETE.getCode()) {
txCompensationService.remove(txTransactionEvent.getTransactionRecover().getId());
} else if (txTransactionEvent.getType() == CompensationActionEnum.UPDATE.getCode()) {
txCompensationService.update(txTransactionEvent.getTransactionRecover());
} else if (txTransactionEvent.getType() == CompensationActionEnum.COMPENSATE.getCode()) {
txCompensationService.compensation(txTransactionEvent.getTransactionRecover());
}
txTransactionEvent.clear();
}
}

4.时序图

九、参考资料

  1. Raincat-github地址
  2. Raincat-源码解析视频
  3. Java之SPI机制
  4. Disruptor_学习_00_资源帖

分布式事务_02_2PC框架raincat源码解析-启动过程的更多相关文章

  1. 分布式_事务_02_2PC框架raincat源码解析

    一.前言 上一节已经将raincat demo工程运行起来了,这一节来分析下raincat的源码 二.协调者启动过程 主要就是在启动类中通过如下代码来启动 netty nettyService.sta ...

  2. 分布式事务_03_2PC框架raincat源码解析-事务提交过程

    一.前言 前面两节,我们已经将raincat的demo工程启动,并简单分析了下事务协调者与事务参与者的启动过程. 这一节,我们来看下raincat的事务提交过程. 二.事务提交过程概览 1.二阶段对应 ...

  3. Syncthing源码解析 - 启动过程

    我相信很多朋友会认为启动就是双击一下Syncthing程序图标,随后就启动完毕了!如果这样认为,对,也不对!对,是因为的确是这样操作,启动了Syncthing:不对是因为在调试Syncthing启动过 ...

  4. 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析

    通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...

  5. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/12/es-code03/ 前提 上篇文章写完了 ES 流程启动的一部分,main 方法都入 ...

  6. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/11/es-code02/ 前提 上篇文章写了 ElasticSearch 源码解析 -- ...

  7. Symfony2源码分析——启动过程2

    文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...

  8. quartz2.x源码分析——启动过程

    title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...

  9. mysql源码分析-启动过程

    mysql源码分析-启动过程 概要 # sql/mysqld.cc, 不包含psi的初始化过程 mysqld_main: // 加载my.cnf和my.cnf.d,还有命令行参数 if (load_d ...

随机推荐

  1. 江卓尔与比特币增发,谣言or先知-千氪

    最近,很多圈内人都在讨论比特币是否应该增发,但等等,比特币真的会增发吗?到底是真有增发计划还是某些人别有用心地在散布谣言? 那么消息是从哪里出来的?北京时间 2 月 10 日晚,莱比特矿池创始人江卓尔 ...

  2. 预防SQL注入攻击

    /** * 预防SQL注入攻击 * @param string $value * @return string */ function check_input($value) { // 去除斜杠 if ...

  3. C51数据类型

  4. vue(组件、路由)懒加载

    const Login = resolve => require(['@/components/Login'], resolve) //就不用import了 Vue.use(Router) le ...

  5. Windows虚拟机安装Linux系统

    windows系统安装linux centos虚拟系统 1.下载 VMware Workstation Pro并安装,效果如图 2.下载linux系统 https://www.centos.org/d ...

  6. Linux centos7 防火墙设置

    1.查看防火墙状态 systemctl list-unit-files|grep firewalld.service 或 systemctl status firewalld.service 2.开启 ...

  7. console.log()方法中%s的作用

    一.console.log("log信息"); 二.console.log("%s","first","second") ...

  8. PHP面试题汇总一

    1.表单中 get与post提交方法的区别? 答:get是发送请求HTTP协议通过url参数传递进行接收,而post是实体数据,可以通过表单提交大量信息. 2.session与cookie的区别? 答 ...

  9. 根据Django后台的ajax大全

    一.什么是ajax 1.1 什么是JSON? AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”.即使用Javascript语 ...

  10. 一览Swift中的常用关键字

    要学习Swift这门语言,就必须先了解Swift的关键字及对应的解释.这里就列一下在Swift中常用到的关键字. 关键字是类似于标识符的保留字符序列,除非用重音符号(`)将其括起来,否则不能用作标识符 ...