2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback),和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。
Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC 模式。
为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对 Fescar 进行品牌升级,并更名为 Seata,意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案。
Seata 融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累,并沉淀了新零售、云计算和新金融等场景下丰富的实践经验。
解决分布式事务问题,有两个设计初衷
对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
高性能:减少分布式事务解决方案所带来的性能消耗
seata中有两种分布式事务实现方案,AT及TCC
完成一个案例,用户下单的时候记录下单日志,完成订单添加,完成用户账户扣款,完成商品库存削减功能,一会在任何一个微服务中制造异常,测试分布式事务。
CREATE TABLE `log_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content` varchar(255) DEFAULT NULL,
`createtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `item_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL COMMENT '商品名称',
`count` int(11) DEFAULT NULL COMMENT '商品数量',
`price` int(11) DEFAULT NULL COMMENT '商品价格',
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `item_info` VALUES ('1', '华为P30', '90', '10');
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `order_info`;
CREATE TABLE `order_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`message` varchar(200) DEFAULT NULL COMMENT '留言',
`money` int(11) DEFAULT NULL COMMENT '总金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
)
CREATE TABLE `user_info` (
`account` varchar(255) NOT NULL,
`money` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`account`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user_info` VALUES ('zhangsan', '80', '张三');
pom.xml依赖如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-parent,artifactId>
<version>1.0-SNAPSHOTversion>
<modules>
<module>fescar-eurekamodule>
<module>fescar-apimodule>
<module>fescar-itemmodule>
<module>fescar-ordermodule>
<module>fescar-businessmodule>
<module>fescar-usermodule>
<module>fescar-transactionmodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.4.RELEASEversion>
parent>
<packaging>pompackaging>
<properties>
<skipTests>trueskipTests>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
<version>2.0.4version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
在fescar-parent工程下创建注册中心工程:
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
application.yml:
server:
port: 7001
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://127.0.0.1:7001/eureka/
启动类:EurekaServerApplication
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
创建fescar-user
微服务,并引入公共工程依赖。
(1)pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fescar-parent,artifactId>
<groupId>com.lanfacaigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>fescar-userartifactId>
<dependencies>
<dependency>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
将所有数据库对应的Pojo/Feign抽取出一个公共工程fescar-api
,在该工程中导入依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fescar-parentartifactId>
<groupId>com.lanfacaigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<description>
API:Model和Feign
description>
<artifactId>fescar-apiartifactId>
project>
将pojo以及feign
feign
@FeignClient(name="item")
public interface ItemInfoFeign {
/**
* 库存递减
* @param id
* @param count
* @return
*/
@PostMapping(value = "/itemInfo/decrCount")
String decrCount(@RequestParam(value = "id") int id, @RequestParam(value = "count") int count);
}
@FeignClient(name="order")
public interface OrderInfoFeign {
/**
* 增加订单
* @param username
* @param id
* @param count
*/
@PostMapping(value = "/orderInfo/add")
String add(@RequestParam(value = "name") String username, @RequestParam(value = "id") int id, @RequestParam(value = "count") int count);
}
@FeignClient(name="user")
public interface UserInfoFeign {
/***
* 账户余额递减
* @param username
* @param money
*/
@PostMapping(value = "/userInfo/add")
String decrMoney(@RequestParam(value = "username") String username, @RequestParam(value = "money") int money);
}
pojo
/**
* @author lanfacai
* @Description 商品信息
*/
@Table(name="item_info")
public class ItemInfo implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;//
@Column(name = "name")
private String name;//商品名称
@Column(name = "count")
private Integer count;//商品数量
@Column(name = "price")
private Integer price;//商品价格
get set.......
/**
* @author lanfacai
* @Description 日志
*/
@Table(name="log_info")
public class LogInfo implements Serializable{
@Id
@Column(name = "id")
private Integer id;// 日志id
@Column(name = "createtime")
private Date createtime;// 创建时间
@Column(name = "content")
private String content;// 内容
get.... set....
/**
* @author lanfacai
* @Description 订单
*/
@Table(name="order_info")
public class OrderInfo implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;//主键
@Column(name = "message")
private String message;//留言
@Column(name = "money")
private Integer money;//总金额
get.... set....
/**
* @author lanfacai
* @Description 用户
**/
@Table(name="user_info")
public class UserInfo implements Serializable{
@Id
@Column(name = "account")
private String account;// 账户
@Column(name = "money")
private Integer money;// 余额
@Column(name = "name")
private String name;// 用户名
创建fescar-user
微服务,并引入公共工程依赖。
(1)pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fescar-parent,artifactId>
<groupId>com.lanfacaigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>fescar-userartifactId>
<dependencies>
<dependency>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
(2)Dao
创建com.lanfacai.dao.UserInfoMapper
,代码如下:
public interface UserInfoMapper extends Mapper<UserInfo> {
}
(3)Service
创建com.lanfacai.service.UserInfoService
接口,代码如下:
public interface UserInfoService {
/***
* 账户金额递减
* @param username
* @param money
*/
void decrMoney(String username, int money);
}
创建com.lanfacai.service.impl.UserInfoServiceImpl
实现用户账户扣款,代码如下:
注意:这里有搞了个异常,模拟付款失败
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
/***
* 账户金额递减
* @param username
* @param money
*/
// @Transactional(rollbackFor = Exception.class)
@Override
public void decrMoney(String username, int money) {
UserInfo userInfo = userInfoMapper.selectByPrimaryKey(username);
userInfo.setMoney(userInfo.getMoney()-money);
int q=10/0;
int count = userInfoMapper.updateByPrimaryKeySelective(userInfo);
System.out.println("添加用户受影响行数:"+count);
}
}
(4)Controller
创建com.lanfacai.controller.UserInfoController
代码如下:
@RestController
@RequestMapping("/userInfo")
@CrossOrigin
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/***
* 账户余额递减
* @param username
* @param money
*/
@PostMapping(value = "/add")
public String decrMoney(@RequestParam(value = "username") String username, @RequestParam(value = "money") int money){
userInfoService.decrMoney(username,money);
return "success";
}
}
(5)启动类
创建com.lanfacai.UserApplication
,代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.lanfacai.feign"})
@MapperScan(basePackages = {"com.lanfacai.dao"})
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
(6)application.yml
创建application.yml配置如下:
server:
port: 18084
spring:
application:
name: user
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/fescar-user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
main:
allow-bean-definition-overriding: true
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#hystrix 配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
strategy: SEMAPHORE
创建fescar-item
微服务,在该工程中实现库存削减。
(1)pom.xml
<dependencies>
<dependency>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
(2)Dao
创建com.lanfacai.dao.ItemInfoMapper
,代码如下:
public interface ItemInfoMapper extends Mapper<ItemInfo> {
}
(3)Service
创建com.lanfacai.service.ItemInfoService
接口,并创建库存递减方法,代码如下:
public interface ItemInfoService {
/**
* 库存递减
* @param id
* @param count
*/
void decrCount(int id, int count);
}
创建com.lanfacai.service.impl.ItemInfoServiceImpl
实现库存递减操作,代码如下:
@Service
public class ItemInfoServiceImpl implements ItemInfoService {
@Autowired
private ItemInfoMapper itemInfoMapper;
/***
* 库存递减
* @param id
* @param count
*/
@Override
public void decrCount(int id, int count) {
//查询商品信息
ItemInfo itemInfo = itemInfoMapper.selectByPrimaryKey(id);
itemInfo.setCount(itemInfo.getCount()-count);
int dcount = itemInfoMapper.updateByPrimaryKeySelective(itemInfo);
System.out.println("库存递减受影响行数:"+dcount);
}
}
(4)Controller
创建com.lanfacai.controller.ItemInfoController
,代码如下:
@RestController
@RequestMapping("/itemInfo")
@CrossOrigin
public class ItemInfoController {
@Autowired
private ItemInfoService itemInfoService;
/**
* 库存递减
* @param id
* @param count
* @return
*/
@PostMapping(value = "/decrCount")
public String decrCount(@RequestParam(value = "id") int id, @RequestParam(value = "count") int count){
//库存递减
itemInfoService.decrCount(id,count);
return "success";
}
}
(5)启动类
创建com.lanfacai.ItemApplication
代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.lanfacai.feign"})
@MapperScan(basePackages = {"com.lanfacai.dao"})
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class, args);
}
}
(6)application.yml
创建application.yml配置如下:
server:
port: 18084
spring:
application:
name: user
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/fescar-user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
main:
allow-bean-definition-overriding: true
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#hystrix 配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
strategy: SEMAPHORE
创建fescar-order
订单微服务,在订单微服务中实现调用商品微服务递减库存。
(1)pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fescar-parent,artifactId>
<groupId>com.lanfacaigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>fescar-orderartifactId>
<dependencies>
<dependency>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
(2)Dao
创建com.lanfacai.dao.OrderInfoMapper
,代码如下:
public interface OrderInfoMapper extends Mapper<OrderInfo> {
}
(3)Service
创建com.lanfacai.service.OrderInfoService
实现添加订单操作,代码如下:
public interface OrderInfoService {
/***
* 添加订单
* @param username
* @param id
* @param count
*/
void add(String username, int id, int count);
}
创建com.lanfacai.service.impl.OrderInfoServiceImpl
,代码如下:
@Service
public class OrderInfoServiceImpl implements OrderInfoService {
@Autowired
private OrderInfoMapper orderInfoMapper;
@Autowired
private ItemInfoFeign itemInfoFeign;
/***
* 添加订单
* @param username
* @param id
* @param count
*/
@Override
public void add(String username, int id, int count) {
//添加订单
OrderInfo orderInfo = new OrderInfo();
orderInfo.setMessage("生成订单");
orderInfo.setMoney(10);
int icount = orderInfoMapper.insertSelective(orderInfo);
System.out.println("添加订单受影响函数:"+icount);
//递减库存
itemInfoFeign.decrCount(id,count);
}
}
(3)Controller
创建com.lanfacai.controller.OrderInfoController
调用下单操作,代码如下:
@RestController
@RequestMapping("/orderInfo")
@CrossOrigin
public class OrderInfoController {
@Autowired
private OrderInfoService orderInfoService;
/**
* 增加订单
* @param username
* @param id
* @param count
*/
@PostMapping(value = "/add")
public String add(@RequestParam(value = "name") String username, @RequestParam(value = "id") int id, @RequestParam(value = "count") int count){
//添加订单
orderInfoService.add(username,id,count);
return "success";
}
}
(4)启动类
创建com.lanfacai.OrderApplication
启动类,代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.lanfacai.feign"})
@MapperScan(basePackages = {"com.lanfacai.dao"})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
(5)application.yml配置
server:
port: 18083
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.211.132:3306/fescar-order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
main:
allow-bean-definition-overriding: true
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#hystrix 配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
strategy: SEMAPHORE
创建fescar-business
业务微服务,在该微服务中实现分布式事务控制,下单入口从这里开始。
(1)pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fescar-parent,artifactId>
<groupId>com.lanfacaigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<description>分布式事务业务控制description>
<artifactId>fescar-businessartifactId>
<dependencies>
<dependency>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
(2)Dao
创建com.lanfacai.dao.LogInfoMapper
代码如下:
public interface LogInfoMapper extends Mapper<LogInfo> {
}
(3)Service
创建com.lanfacai.service.BusinessService
接口,代码如下:
public interface BusinessService {
/**
* 下单
* @param username
* @param id
* @param count
*/
void add(String username, int id, int count);
}
创建com.lanfacai.service.impl.BusinessServiceImpl
,代码如下:
@Service
public class BusinessServiceImpl implements BusinessService {
@Autowired
private OrderInfoFeign orderInfoFeign;
@Autowired
private UserInfoFeign userInfoFeign;
@Autowired
private LogInfoMapper logInfoMapper;
/***
* 下单
* @param username
* @param id
* @param count
*/
@Override
public void add(String username, int id, int count) {
//添加订单日志
LogInfo logInfo = new LogInfo();
logInfo.setContent("添加订单数据---"+new Date());
logInfo.setCreatetime(new Date());
int logcount = logInfoMapper.insertSelective(logInfo);
System.out.println("添加日志受影响行数:"+logcount);
//添加订单
orderInfoFeign.add(username,id,count);
//用户账户余额递减
userInfoFeign.decrMoney(username,10);
}
}
(5)启动类
创建启动类com.lanfacai.BusinessApplication
,代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.lanfacai.feign"})
@MapperScan(basePackages = {"com.lanfacai.dao"})
public class BusinessApplication {
public static void main(String[] args) {
SpringApplication.run(BusinessApplication.class,args);
}
}
(6)application.yml配置
server:
port: 18081
spring:
application:
name: business
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.211.132:3306/fescar-business?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
main:
allow-bean-definition-overriding: true
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#读取超时设置
ribbon:
ReadTimeout: 30000
#hystrix 配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
strategy: SEMAPHORE
localhost:18081/business/addorder
程序执行前
下面是我们正常情况:订单生成了,库存减了,用户的钱也减少了
运行一下我们的程序,我们刚才在com.lanfacai.service.impl.UserInfoServiceImpl
搞了个异常 ,模拟付款失败
然后我看一下数据库 :订单生成了,库存减了,用户的钱并没有减少了[外链图片转存失败(img-ESqUezjb-1567256255920)(没开启事务.png)]
上面案例,并没有实现分布式事务,在我们以后工作中,也并非每个服务都需要实现分布式事务,我们可以将分布式事务抽取出来。
创建fescar-transaction
微服务工程,在该工程中实现分布式事务控制。
相关概念讲解
XID:全局事务的唯一标识,由 ip:port:sequence 组成;
Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager (TM ):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
Fescar 使用 XID 表示一个分布式事务,XID 需要在一次分布式事务请求所涉的系统中进行传递,从而向 feacar-server 发送分支事务的处理情况,以及接收 feacar-server 的 commit、rollback 指令。
(1)pom.xml依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fescar-parent,artifactId>
<groupId>com.lanfacaigroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<description>fescar分布式事务微服务description>
<artifactId>fescar-transactionartifactId>
<properties>
<fescar.version>0.4.2fescar.version>
properties>
<dependencies>
<dependency>
<groupId>com.alibaba.fescargroupId>
<artifactId>fescar-tmartifactId>
<version>${fescar.version}version>
dependency>
<dependency>
<groupId>com.alibaba.fescargroupId>
<artifactId>fescar-springartifactId>
<version>${fescar.version}version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starterartifactId>
dependency>
dependencies>
project>
创建com.lanfacai.fescar.FescarAutoConfiguration
,代码如下:
public class FescarAutoConfiguration {
public static final String FESCAR_XID = "fescarXID";
/***
* 创建代理数据库
* 会将und_log绑定到本地事务中
* @param environment
* @return
*/
@Bean
public DataSource dataSource(Environment environment){
//创建数据源对象
DruidDataSource dataSource = new DruidDataSource();
//获取数据源链接地址
dataSource.setUrl(environment.getProperty("spring.datasource.url"));
try {
//设置数据库驱动
dataSource.setDriver(DriverManager.getDriver(environment.getProperty("spring.datasource.url")));
} catch (SQLException e) {
throw new RuntimeException("无法识别驱动类型");
}
//获取数据库名字
dataSource.setUsername(environment.getProperty("spring.datasource.username"));
//获取数据库密码
dataSource.setPassword(environment.getProperty("spring.datasource.password"));
//将数据库封装成一个代理数据库
return new DataSourceProxy(dataSource);
}
/***
* 全局事务扫描器
* 用来解析带有@GlobalTransactional注解的方法,然后采用AOP的机制控制事务
* @param environment
* @return
*/
@Bean
public GlobalTransactionScanner globalTransactionScanner(Environment environment){
//事务分组名称
String applicationName = environment.getProperty("spring.application.name");
String groupName = environment.getProperty("fescar.group.name");
if(applicationName == null){
return new GlobalTransactionScanner(groupName == null ? "my_test_tx_group" : groupName);
}else{
return new GlobalTransactionScanner(applicationName, groupName == null ? "my_test_tx_group" : groupName);
}
}
/***
* 每次微服务和微服务之间相互调用
* 要想控制全局事务,每次TM都会请求TC生成一个XID,每次执行下一个事务,也就是调用其他微服务的时候都需要将该XID传递过去
* 所以我们可以每次请求的时候,都获取头中的XID,并将XID传递到下一个微服务
* @param restTemplates
* @return
*/
@ConditionalOnBean({RestTemplate.class})
@Bean
public Object addFescarInterceptor(Collection<RestTemplate> restTemplates){
restTemplates.stream()
.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if(interceptors != null){
interceptors.add(fescarRestInterceptor());
}
});
return new Object();
}
@Bean
public FescarRMRequestFilter fescarRMRequestFilter(){
return new FescarRMRequestFilter();
}
@Bean
public FescarRestInterceptor fescarRestInterceptor(){
return new FescarRestInterceptor();
}
}
使用 DataSourceProxy
的目的是为了引入 ConnectionProxy
,fescar 无侵入的一方面就体现在 ConnectionProxy
的实现上,即分支事务加入全局事务的切入点是在本地事务的 commit
阶段,这样设计可以保证业务数据与 undo_log
是在一个本地事务中。
undo_log
是需要在业务库上创建的一个表,fescar 依赖该表记录每笔分支事务的状态及二阶段 rollback
的回放数据。不用担心该表的数据量过大形成单点问题,在全局事务 commit
的场景下事务对应的 undo_log
会异步删除。
所以在每个微服务对应的数据库中需要创建一张undo_log表。
创建com.lanfacai.fescar.FescarRestInterceptor
,代码如下:
public class FescarRestInterceptor implements RequestInterceptor, ClientHttpRequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String xid = RootContext.getXID();
if(!StringUtils.isEmpty(xid)){
System.out.println("全局事务唯一ID:"+xid);
requestTemplate.header(FescarAutoConfiguration.FESCAR_XID, xid);
}
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String xid = RootContext.getXID();
if(!StringUtils.isEmpty(xid)){
HttpHeaders headers = request.getHeaders();
headers.put(FescarAutoConfiguration.FESCAR_XID, Collections.singletonList(xid));
}
return execution.execute(request, body);
}
}
创建com.lanfacai.fescar.FescarRMRequestFilter
,代码如下:
public class FescarRMRequestFilter extends OncePerRequestFilter {
private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(FescarRMRequestFilter.class);
/**
* 给每次线程请求绑定一个XID
* @param request
* @param response
* @param filterChain
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String currentXID = request.getHeader(FescarAutoConfiguration.FESCAR_XID);
if(!StringUtils.isEmpty(currentXID)){
RootContext.bind(currentXID);
LOGGER.info("当前线程绑定的XID :" + currentXID);
}
try{
filterChain.doFilter(request, response);
} finally {
String unbindXID = RootContext.unbind();
if(unbindXID != null){
LOGGER.info("当前线程从指定XID中解绑 XID :" + unbindXID);
if(!currentXID.equals(unbindXID)){
LOGGER.info("当前线程的XID发生变更");
}
}
if(currentXID != null){
LOGGER.info("当前线程的XID发生变更");
}
}
}
在resources下创建META-INF/spring.factories
,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lanfacai.fescar.FescarAutoConfiguration
在resources下创建file.conf
内容如下:
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
}
service {
#vgroup->rgroup
vgroup_mapping.my_test_tx_group = "default"
#only support single node 配置Client连接TC的地址
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
disableGlobalTransaction = false
}
client {
#RM接收TC的commit通知缓冲上限
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
}
fescar 的配置入口文件是 registry.conf, 查看代码 ConfigurationFactory 得知目前还不能指定该配置文件,所以配置文件名称只能为 registry.conf。
在 registry
中可以指定具体配置的形式,默认使用 file 类型,在 file.conf 中有 3 部分配置内容:
transport transport :用于定义 Netty 相关的参数,TM、RM 与 fescar-server 之间使用 Netty 进行通信。
还有registry.conf
:
registry {
# file 、nacos 、eureka、redis、zk
type = "file"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:1001/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6381"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk
type = "file"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
apollo {
app.id = "fescar-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
file {
name = "file.conf"
}
}
(1)添加依赖
在fescar-api
工程下添加依赖;因为我们每个微服务都依赖了这个服务(传递依赖),不需要一个个添加该依赖
<dependency>
<groupId>com.lanfacaigroupId>
<artifactId>fescar-transactionartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
下载Seata的事务协调器
https://github.com/seata/seata/releases
我们使用的是0.4.2版本
解压seata\fescar-server-0.4.2.zip
文件包(解压到一个没有中文,空格的目录下),并点击bin\fescar-server.bat
启动Seata的事务协调器。
注意:如果无法启动
把JDK下bin目录下的server包复制到Jer下bin目录中 [外链图片转存失败(img-JTMpJa4W-1567256255924)(jdk.png)]
[外链图片转存失败(img-lLmGeclt-1567256255925)(jer.png)]
在订单微服务的(入口)BusinessServiceImpl的add方法上增加@GlobalTransactional(name = “add”)注解,代码如下:
/***
* 下单
* @param username
* @param id
* @param count
*/
@GlobalTransactional(name = "add")
@Override
public void add(String username, int id, int count) {
//添加订单日志
LogInfo logInfo = new LogInfo();
logInfo.setContent("添加订单数据---"+new Date());
logInfo.setCreatetime(new Date());
int logcount = logInfoMapper.insertSelective(logInfo);
System.out.println("添加日志受影响行数:"+logcount);
//添加订单
orderInfoFeign.add(username,id,count);
//用户账户余额递减
userInfoFeign.decrMoney(username,10);
}
启动服务
测试http://localhost:18081/business/addorder
我们先查询下数据库数据,然后再测试一次,现在我们发现就是异常了,事务也控制住了。