導讀:本篇做爲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哈哈~!
系列文章
歡迎掃碼關注微信公衆號或 我的博客