参考资料:https://mp.weixin.qq.com/s/2KSidJ72YsovpJ94P1aK1g seata官徽
新人文档:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
说真的,这个seata搭建与案例演示让我爬了好多坑~~总算测试成功了~现在将自己的步骤与注意事项记录下来:
此片文章的前提是上一篇中讲到的seata-server的正确安装,下面进行client端的配置与编码等准备。
mysql: 5.7
nacos: latest镜像
spring-cloud-alibaba: 2.2.0
seata: 1.2.0
第一步下载seata服务,第二步创建Seata高可用的db,此两步在上篇文章中已经提到了~可回看,下面讲客户端操作
1、先准备业务数据库,新建数据库seata-order,seata-account,seata-storage,并分别执行一下建表语句
CREATE TABLE `t_order` (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`user_id` BIGINT (11) DEFAULT NULL COMMENT '用户id',`product_id` BIGINT (11) DEFAULT NULL COMMENT '产品id',`count` INT (11) DEFAULT NULL COMMENT '数量',`money` DECIMAL (11,0) DEFAULT NULL COMMENT '金额',`status' INT ( 1 ) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
) ENGINE = INNODB AUTO_INCREMENT = 7 DEFAULT CHARSET = utf8;
CREATE TABLE `t_storage` (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`product_id` BIGINT (11) DEFAULT NULL COMMENT '产品id',`total` INT (11) DEFAULT NULL COMMENT '总库存',`used` INT (11) DEFAULT NULL COMMENT '已用库存',`residue` INT (11) DEFAULT NULL COMMENT '剩余库存') ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_storage.`t_storage` (id,product_id,total,used,residue) VALUES ('1','1','100','0','100');
CREATE TABLE t_account (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',`user_id` BIGINT (11) DEFAULT NULL COMMENT '用户id',`total` DECIMAL (10,0) DEFAULT NULL COMMENT '总额度',`used` DECIMAL (10,0) DEFAULT NULL COMMENT '已用余额',`residue` DECIMAL (10,0) DEFAULT '0' COMMENT '剩余可用额度') ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account (id,user_id,total,used,residue) VALUES ('1','1','1000','0','1000');
然后每个涉及到事务的数据库中都多添加一张undo_log表
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (`id` BIGINT (20) NOT NULL AUTO_INCREMENT,`branch_id` BIGINT (20) NOT NULL,`xid` VARCHAR (100) NOT NULL,`context` VARCHAR (128) 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`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2、添加seata依赖
注意:官方推荐是这么配置,但是我本地集成的时候出现了问题,版本不对
com.alibaba.cloud
spring-cloud-alibaba-seata
2.2.0.RELEASE
io.seata
seata-spring-boot-starter
io.seata
seata-spring-boot-starter
1.2.0
这样配置好以后我的依赖中seata-all的版本是0.7,以致于我的注解配置enable-auto-data-source-proxy: true报错
后经过修改,修改pom文件中的依赖为这个样子
com.alibaba.cloud
spring-cloud-alibaba-seata
2.2.0.RELEASE
io.seata
seata-spring-boot-starter
io.seata
seata-spring-boot-starter
1.2.0
io.seata
seata-all
io.seata
seata-all
1.2.0
此处要根据自己的项目中更新的依赖来判断要不要做一步处理~~~~
3、加入seata所需的参数配置
从官方github仓库拿到参考配置做修改:https://github.com/seata/seata/tree/develop/script/client,只是参考,官方给的是全部配置,自己挑选对应的配置拿下来即可~加入到你的项目的appliaction.yml中即可
seata:
enabled: true
application-id: orders-service
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
config:
type: nacos
file:
name: file.conf
nacos:
namespace:
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace:
userName: "nacos"
password: "nacos"
4、配置为高可用db模式参数并提交至配置中心
运行你下载的nacos,并参考https://github.com/seata/seata/tree/develop/script/config-center 的config.txt并修改,此处也是给的全量配置参数,选取对应的配置即可,运行仓库中提供的nacos脚本,将以上信息提交到nacos控制台,如果有需要更改,可直接通过控制台更改。脚本存放地址https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
注意:此配置只需要执行一次即可,因为是针对seata-server的,然后脚本存放的目录在参考网址中对应的nacos/zk等文件夹中,需要注意即可,我是下载下来,然后拷贝到了项目中,将config.txt和脚本放在了相对路径下,这个相对路径要和参考网址上那个一样,否则会报找不到config.txt
配置和脚本存放位置:
我的配置:
service.vgroupMapping.my_test_tx_group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
5、更改seata-server服务端的注册&配置中心为nacos然后重新启动seata-server,这一步在上一篇应该修改过了,这里在贴一下
更改server中的registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "localhost"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
6、开发业务代码,加入全局事务注解进行调试
此处因为业务代码比较多,所以我这里只贴关键和注意的部分,其他的可以去我的github上直接下载源码项目cloud2020,主要是三个module:cloudalibaba-seata-order-service2001,cloudalibaba-seata-storage-service2002,cloudalibaba-seata-account-service2001
cloudalibaba-seata-order-service2001中:
orderServiceImpl
package com.king.springcloud.service.impl;
import com.king.springcloud.dao.OrderDao;
import com.king.springcloud.domain.Order;
import com.king.springcloud.service.AccountService;
import com.king.springcloud.service.OrderService;
import com.king.springcloud.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* created by king on 2020/6/3 10:56 下午
*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:下订单->扣库存->减余额->改状态
*/
@Override
@GlobalTransactional
public void create(Order order) {
log.info("----->开始新建订单");
//1 新建订单
orderDao.create(order);
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decreaseStorage(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decreaseAccount(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}
}
accountService:
package com.king.springcloud.service;
import com.king.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
/**
* created by king on 2020/6/3 11:01 下午
*/
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping(value = "account/decrease")
CommonResult decreaseAccount(@RequestParam(value="userId") Long userId, @RequestParam(value="money") BigDecimal money);
}
storageService:
package com.king.springcloud.service;
import com.king.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* created by king on 2020/6/3 10:58 下午
*/
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping(value = "/storage/decrease")
CommonResult decreaseStorage(@RequestParam(value ="productId") Long productId,@RequestParam(value="count") Integer count);
}
此处需要注意:在FeignClient调用的下面接口中,@RequestParam中要有value即@RequestParam(value ="productId") Long productId格式,不能省略value写成@RequestParam(productId) Long productId这样,否则会访问的时候报异常feign.FeignException: status 400 reading xxx#xxxx(String);
orderDao:
package com.king.springcloud.dao;
import com.king.springcloud.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* created by king on 2020/6/3 10:51 下午
*/
@Mapper
public interface OrderDao {
//1、创建订单
void create (Order order);
//2、更新订单状态,从0改为1
void update (@Param("userId") Long userId, @Param("status") Integer status);
}
注意:此处要加@Param,而且是ibatis下的包,我此处就因为倒错包了,错误导入了Feing包下的,导致了以下异常binding.BindingException: Parameter 'userId' not found. Available parameters
剩余两个module也就是业务操作处理了,可以直接下载项目源码来查看比较方便,主要是项目构建module的几个步骤,
7、进行调试代码,在被调用的account中添加sleep或者int i=1/0等异常代码,来测试事务回滚状态:
当没有添加@GlobalTranctional注解时,上述两种异常发生后都不会回滚,超时时账户也会扣减,而且由于feign的重试机制,账户余额还有可能被多次扣减,但是两种情况下订单状态都不会改变
当添加了@GlobalTranctional注解后,上述两种情况都会回滚,可以在在中间环境打个断点,来查看seata库中的表的数据,存储的就是preimage、事务统一id、分组等信息,还有undo_log表中也会存在记录,但是当事务处理完,提交或回滚后,会删除这些表中数据,这也就是为什么我们执行完事务后,再去查看这些表,发现里面依然是空表~~
8、高可用Seata-server搭建
确保你已经完成了以上七步操作后,按照以上的第1,6两步即可把你的seata新节点接入到同一个nacos集群,配置&注册中心中,由于是同一个配置中心,所以db也是采用的共同配置.至此高可用搭建已经顺利完结,如果你想测试,仅需关掉其中一个server节点,验证服务是否可用即可.
启动另一个seata-server命令:
sh ./seata-server.sh -p 8877
至此,seata的分布式事务项目构建案例完成了,演示成功了~~~~~