# fat
FAT ,基于springboot , 使用zookeeper,redis , spring async , spring transactionManager的强一致性分布式事务解决方案
## 框架介绍
纯编码方式,强一致性。<br>
使用redis/zookeeper作为注册中心 ,代理事务的执行,使用spring async异步处理事务线程。<br>
基于注解使用,对业务代码可以说是零入侵,目前内置适配spring-cloud(Feign调用) , dubbo。<br>
同时具备一定的扩展性与兼容性,因为存在自定义的服务框架,或者以后会涌现出更多的流行分布式服务框架,所以会提供一些组件适配自定义服务框架。
## Maven依赖
```java
<dependency>
<groupId>com.github.cjyican</groupId>
<artifactId>fat-common</artifactId>
<version>1.0.6-RELEASE</version>
</dependency>
```
## 使用示例
### step0:SpringBootApplication加上@EnableFat注解
```java
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
@EnableFat
public class FatboyEurekaRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(FatboyEurekaRibbonApplication.class, args);
}
}
```
### step1:配置注册中心
使用redis/zookeeper作为注册中心,优先使用zookeeper。为隔离业务使用的redis和注册中心的redis,提供了一套属性配置。
在业务redis/zookeeper作为注册中心与注册中心相同时,也需要配置。
请保证各个服务的注册中心配置一致,否则无法协调分布式事务。
```java
#Fat
# Redis数据库索引(默认为0)
fat.redis.database=0
# Redis服务器地址
fat.redis.host=
# Redis服务器连接端口
fat.redis.port=6379
# Redis服务器连接密码(默认为空)
fat.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
fat.redis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
fat.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
fat.redis.pool.max-idle=10
# 连接池中的最小空闲连接
fat.redis.pool.min-idle=2
# 连接超时时间(毫秒)
fat.redis.timeout=1000
# 集群模式,如有配置,将优先使用集群
fat.redis.cluster.nodes=x.x.x.x:x,x.x.x.x:x
# zookeeper服务器地址
fat.zookeeper.host=x.x.x.x:x,x.x.x.x:x
# zookeeper活跃时间
fat.zookeeper.sessionTimeout=x.x.x.x:x,x.x.x.x:x
```
应用标识,与spirng.application.name一致,必须配置
```java
spring.application.name=fatboy-eureka-ribbon
```
### step2:服务入口方法加入注解@FatServiceRegister注册
在需要开启分布式事务管理的入口方法中加入注解@FatServiceRegister,注意不要重复添加。dubbo的直接加在serviceImpl.method上面就可以了。
```java
@RequestMapping("/user-service/{userId}/updateUserOrderNum1")
@FatServiceRegister
public Integer updateUserOrderNum1(@PathVariable("userId") Long userId , @RequestParam("lastOrderId") Long lastOrderId ) throws Exception {
int userResult = service.updateUserOrderNum(userId , lastOrderId);
prodFeign.updateStock(1l);
//int i = 10 / 0;测试使用
return userResult;
}
```
注解解析
```java
```
### step3:业务方法加注解@FatTransaction纳入分布式管控
注意@FatTransaction必须要与@Transactional配合使用已获取用户配置的事务信息,否则将会报错
```java
@FatTransaction
@Transactional
public Integer updateUserOrderNum(Long userId ,Long lastOrderId ) throws Exception {
User user = new User();
user.setLastOrderId(lastOrderId);
user.setUserId(userId);
int i = mapper.updateUserOrderNum(user);
//int j = 10 /0 ;//测试使用
return i;
}
```
注解解析
```java
/**
* 等待当前服务返回值的超时时间 , 默认3秒
*/
long waitResultMillisSeconds() default 3000;
/**
* 服务等待提交时间 ,默认3秒
*/
long waitCommitMillisSeconds() default 3000;
```
OK,到这里这个接口的服务链路已经完成了,可以跑起来了。简单吧,嘿嘿嘿。
## 运行流程
![头像](https://github.com/cjyican/img-respo/blob/master/TIM20190118153529.png)
<br>图不重要,重要的思想和代码,下面介绍一下FAT的一些设计和源码
## 相关性能分析
### 响应速度
采取了异步执行业务操作,操作事务的方式。需要阻塞当前事务提交线程,主线程会不会响应很慢?在FAT里面,主线程得到响应是非常快的,因为在服务链路的场景里,调用服务B,需要使用到服务A的结果,而这个服务A的结果,实际上是不需要等到事务提交的,所以调用服务A的时间上是业务执行的结果时间,不是事务提交的时间。
### 可靠性
FAT对于事务的监控阻塞,目前设计是三个阶段:<br>
1,业务执行完毕的阻塞,即服务执行完业务操作,向注册中心注册业务完成标示,此为第一次阻塞,用于等待服务链路其他服务的执行。此时是可以通过等待超时回滚整个分布式事务的。此阻塞还有另一个意义,让整个分布式事务的时间线趋于平衡状态,降低超时出错几率。<br>
2,所有服务执行完毕,事务准备提交的阻塞。即所有服务的业务操作已经完毕,注册中心已经获取到所有服务的完成标识。此阻塞将不会有超时限制。<br>
3,分组协调器的阻塞,在服务调服务的场景中,会进行事务分组,每个事务组完成,将会到分组协调器注册标识,当所有事务分组完成,事务才开始进行提交。<br>
注意:<br>
在阶段3之前,也就是业务操作过程,业务超时 or 服务链路挂了 or 客户端挂了 or 注册中心挂了,整个事务都会由于协调超时而回滚,不会出现不一致的情况,但是某个服务挂了,由于事务尚未提交,该服务的事务需要DB手工操作。<br>
阶段3,某个服务挂了,将不影响其他服务的事务提交,但是某个服务挂了,由于事务尚未提交,该服务的事务需要DB手工操作。注册中心挂了,已经提交的不影响,剩余的将会回滚。<br>
综上,应当保证服务的业务操作效率以及注册中心的稳定性。
## 设计与源码解析
直接看代码,注释非常清晰<br>
主要处理流程都集中在<br>
https://github.com/cjyican/fat/tree/master/src/main/java/com/cjy/fat/resolve
### 注册流程
https://github.com/cjyican/fat/blob/master/src/main/java/com/cjy/fat/resolve/ServiceRegisterAspect.java<br>
https://github.com/cjyican/fat/blob/master/src/main/java/com/cjy/fat/resolve/ServiceRegisterResolver.java<br>
### 业务方法流程
https://github.com/cjyican/fat/blob/master/src/main/java/com/cjy/fat/resolve/TransactionAspect.java<br>
https://github.com/cjyican/fat/blob/master/src/main/java/com/cjy/fat/resolve/handler/ServiceRunningHandler.java<br>
### 事务监听提交流程
https://github.com/cjyican/fat/blob/master/src/main/java/com/cjy/fat/resolve/CommitResolver.java<br>
## 可自定义的配置
### 事务处理的线程池
FAT使用Sping Async处理事务流程,自然需要用到线程池,线程池默认有配置,也可以根据项目运行情况自定义,以下为配置信息
```java
@Value("${fat.thread.core_pool_size:20}")
private int corePoolSize ;
@Value("${fat.thread.max_pool_size:50}")
private int maxPoolSize ;
@Value("${fat.thread.queue_capacity:200}")
private int queueCapacity ;
@Value("${fat.thread.keep_alive_seconds:60}")
private int keepAliveSeconds;
```
### 监听事务执行情况,业务结果的间歇时间
根据项目运行情况配置,默认0.1秒
```java
/**
* 间歇消费时间(毫秒)默认200毫秒
* 争抢可提交标识的时候,可能发生错误,避免继续阻塞,导致jdbcConnection/数据库事务迟迟不肯放手,为了提高响应速度,
* 将pop的阻塞时间分段请求
*/
@Value("${tx.commit.blankTime:100}")
private long commitBlankTime ;
@Value("${tx.waitResult.blankTime:100}")
private long waitResultBlankTime;
```
## 扩展
以后应该会有更多的流行�