导读:本篇做为SpringCloud Alibaba微服务实战系列的第七篇,主要内容是使用Seata解决分布式事务问题。系列文章,欢迎持续关注。html
订单服务order-service
须要对外提供建立订单的接口,建立订单的业务逻辑以下:java
先调用本地的orderService
保存订单操做,而后经过feign调用远程的accout-service
进行帐户余额扣减,最后再经过feign调用远程的product-service
进行库存扣减操做。git
关键的逻辑代码以下:github
OrderController
对外提供建立订单的接口@PostMapping("/order/create") public ResultData<OrderDTO> create(@RequestBody OrderDTO orderDTO){ log.info("create order:{}",orderDTO); orderDTO.setOrderNo(UUID.randomUUID().toString()); orderDTO.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getCount()))); orderService.createOrder(orderDTO); return ResultData.success("create order success"); }
OrderServiceImpl
负责处理建立订单的业务逻辑@Transactional(rollbackFor = RuntimeException.class) @Override public void createOrder(OrderDTO orderDTO) { Order order = new Order(); BeanUtils.copyProperties(orderDTO,order); //本地存储Order this.saveOrder(order); //库存扣减 productFeign.deduct(orderDTO.getProductCode(),order.getCount()); //帐户余额扣减 accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount()); } @Transactional(rollbackFor = RuntimeException.class) void saveOrder(Order order) { orderMapper.insert(order); }
本地先保存,而后调用两个远程服务进行扣减操做。spring
AccountServiceImpl
扣减帐户余额@Transactional(rollbackFor = RuntimeException.class) @Override public void reduceAccount(String accountCode, BigDecimal amount) { Account account = accountMapper.selectByCode(accountCode); if(null == account){ throw new RuntimeException("can't reduce amount,account is null"); } BigDecimal subAmount = account.getAmount().subtract(amount); if(subAmount.compareTo(BigDecimal.ZERO) < 0){ throw new RuntimeException("can't reduce amount,account'amount is less than reduce amount"); } account.setAmount(subAmount); accountMapper.updateById(account); }
作些简单的校验,当帐户余额不足的时候不容许扣减操做。sql
ProductServiceImpl
扣减产品库存@Transactional(rollbackFor = RuntimeException.class) @Override public void deduct(String productCode, Integer deductCount) { Product product = productMapper.selectByCode(productCode); if(null == product){ throw new RuntimeException("can't deduct product,product is null"); } int surplus = product.getCount() - deductCount; if(surplus < 0){ throw new RuntimeException("can't deduct product,product's count is less than deduct count"); } product.setCount(surplus); productMapper.updateById(product); }
作些简单的校验,当产品库存不足时不容许扣减操做。数据库
order-service
、product-service
、account-service
分属不一样的服务,当其中一个服务抛出异常没法提交时就会致使分布式事务,如当使用feign调用account-service
执行扣减帐户余额时,account-service
校验帐户余额不足抛出异常,可是order-service
的保存操做不会回滚;或者是前两步执行成功可是product-service
校验不经过前面的操做也不会回滚,这就致使了数据不一致,也就是分布式事务问题!segmentfault
在Springcloud Alibaba体系中使用Seata做为分布式事务解决方案,你们能够访问seata官网去了解详情。
此次咱们先使用Seata的file配置解决上面出现的问题,后面再来对其改造。微信
$ sh ./bin/seata-server.sh
在Windows下 bin\seata-server.bat
app
### 引入seata组件
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> </dependency>
spring: cloud: alibaba: seata: tx-service-group: ${spring.application.name}-seata
registry.conf
、file.conf
2个文件拷贝到微服务中的resources文件夹下registry{ type = "file" file { name = "file.conf" } } config{ type = "file" file { name = "file.conf" } }
主要修改以下三处: service.vgroup_mapping.
后面的值修改成配置文件spring.cloud.alibaba.seata.tx-service-group
的属性 service.default.grouplist=
修改成Seata Server的ip:端口 support.spring.datasource.autoproxy
的值修改成true,开启datasource
自动代理
在微服务的业务库下执行以下语句,生成undo_log表
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库) drop table `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;
在分布式事务方法入口添加注解@GlobalTransactional
,这里只须要在createOrder
方法上添加此注解便可!
@GlobalTransactional(name = "TX_ORDER_CREATE") @Override public void createOrder(OrderDTO orderDTO) { Order order = new Order(); BeanUtils.copyProperties(orderDTO,order); //本地存储Order this.saveOrder(order); log.info("ORDER XID is: {}", RootContext.getXID()); //帐户余额扣减 accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount()); //库存扣减 productFeign.deduct(orderDTO.getProductCode(),orderDTO.getCount()); }
在代码中可使用RootContext.getXID()
获取全局xid
服务正常启动后在Seata Server控制台能够看到注册信息
改造完成后对接口进行测试,若是其余服务抛出异常会看到以下错误日志,再结合数据库数据观察是否正常回滚
执行过程当中咱们经过debug能够发现undo_log
表会不断插入数据,在执行后又会被删除。
经过上面几步咱们使用Seata实现了分布式事务,保证了数据的一致性,最后说一句Seata真香,大家要不要感觉一下。
至此本期的“SpringCloud Alibaba微服务实战七 - 分布式事务”篇也就该结束啦,我们下期有缘再见!
再见以前让我在求一波关注吧,O(∩_∩)O哈哈~!
系列文章
欢迎扫码关注微信公众号或 我的博客