实战分布式事务【Seata+Spring Cloud】
上一篇文章我们使用Atomikos实现了分布式事务:但是里面有个问题:可能会对同一个表生成两份mapper相关的内
上一篇文章我们使用Atomikos实现了分布式事务:
但是里面有个问题:可能会对同一个表生成两份mapper相关的内容。
(资料图)
于是,我们继续探索其他分布式事务组件。
常见分布式事务组件:Seata 、Atomikos、bitronix、Narayana等
下面是它们的优缺点和适用场景:
优点:
支持多种分布式事务模型,包括 AT、TCC、SAGA 和 XA。 具有高可用性和高可靠性,支持多种注册中心和存储模式。 支持多种语言和框架,适用于各种应用场景。缺点:
对于一些复杂的事务场景,需要进行一定的配置和调试。 目前社区还比较小,文档和案例不是很丰富。适用场景:
高并发、高可用的分布式系统。 支持多种分布式事务模型的业务系统。优点:
支持 JTA 和非 JTA 事务。 支持多种资源管理器和数据库。 具有高性能和高可靠性。缺点:
对于一些复杂的事务场景,需要进行一定的配置和调试。 需要付费使用。适用场景:
对于需要支持 JTA 的业务系统。 对于需要高可靠性和高性能的业务系统。优点:
支持 JTA 和非 JTA 事务。 具有高性能和高可靠性。 免费使用。缺点:
对于一些复杂的事务场景,需要进行一定的配置和调试。 文档和案例相对较少。适用场景:
对于需要支持 JTA 的业务系统。 对于需要高可靠性和高性能的业务系统。还有Narayana也能实现分布式事务,一种开源的Java事务管理器,支持XA协议和非XA事务管理,也支持TCC和Saga事务管理。
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
1、典型的场景就是微服务架构 微服务之间通过远程调用完成事务操作。比如:订单微服务和库存微服务,下单的同时订单微服务请求库存微服务减库存。简言之:跨JVM进程产生分布式事务。
2、单体系统访问多个数据库实例 当单体系统需要访问多个数据库(实例)时就会产生分布式事务。比如:用户信息和订单信息分别在两个MySQL实例存储,用户管理系统删除用户信息,需要分别删除用户信息及用户的订单信息,由于数据分布在不同的数据实例,需要通过不同的数据库链接去操作数据,此时产生分布式事务。简言之:跨数据库实例产生分布式事务。
3、多服务访问同一个数据库实例 比如:订单微服务和库存微服务即使访问同一个数据库也会产生分布式事务,原因就是跨JVM进程,两个微服务持有了不同的数据库链接进行数据库操作,此时产生分布式事务。
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,其内部版本在阿里系内部一直扮演着应用架构层数据一致性的中间件角色,帮助经济体平稳的度过历年的双11,对上层业务进行了有力的技术支撑。经过多年沉淀与积累,其商业化产品先后在阿里云、金融云上售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助用户快速落地分布式事务解决方案。
Seata 是一款阿里巴巴开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
目前已支持Dubbo、Spring Cloud、Sofa-RPC、Motan 和 gRPC 等RPC框架,其他框架持续集成中
--------来自官网
本案例使用Nacos作为服务注册中心和分布式配置中心,请先准备好Nacos环境。
seata-server为release版本1.4.2,demo采用本地单机部署
本文采用seata的默认模式:AT模式
Seata下载地址:https://github.com/seata/seata/releases
下载到本地后解压,接着修改相关配置:
第一步:conf/registry.conf
registry{type=\"nacos\"nacos{serverAddr=\"127.0.0.1\"namespace=\"\"cluster=\"default\"}}config{type=\"nacos\"nacos{serverAddr=\"127.0.0.1\"namespace=\"\"cluster=\"default\"}}不想相关的都删掉。
第二步:在conf目录仙剑文件:config.txt,内容:
transport.type=TCPtransport.server=NIOtransport.heartbeat=truetransport.enableTmClientBatchSendRequest=falsetransport.enableRmClientBatchSendRequest=truetransport.enableTcServerBatchSendResponse=falsetransport.rpcRmRequestTimeout=30000transport.rpcTmRequestTimeout=30000transport.rpcTcRequestTimeout=30000transport.threadFactory.bossThreadPrefix=NettyBosstransport.threadFactory.workerThreadPrefix=NettyServerNIOWorkertransport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandlertransport.threadFactory.shareBossWorker=falsetransport.threadFactory.clientSelectorThreadPrefix=NettyClientSelectortransport.threadFactory.clientSelectorThreadSize=1transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThreadtransport.threadFactory.bossThreadSize=1transport.threadFactory.workerThreadSize=defaulttransport.shutdown.wait=3transport.serialization=seatatransport.compressor=none#service.vgroupMapping.order-service-group=defaultservice.default.grouplist=127.0.0.1:8091service.enableDegrade=falseservice.disableGlobalTransaction=falseclient.rm.asyncCommitBufferLimit=10000client.rm.lock.retryInterval=10client.rm.lock.retryTimes=30client.rm.lock.retryPolicyBranchRollbackOnConflict=trueclient.rm.reportRetryCount=5client.rm.tableMetaCheckEnable=trueclient.rm.tableMetaCheckerInterval=60000client.rm.sqlParserType=druidclient.rm.reportSuccessEnable=falseclient.rm.sagaBranchRegisterEnable=falseclient.rm.sagaJsonParser=fastjsonclient.rm.tccActionInterceptorOrder=-2147482648client.tm.commitRetryCount=5client.tm.rollbackRetryCount=5client.tm.defaultGlobalTransactionTimeout=60000client.tm.degradeCheck=falseclient.tm.degradeCheckAllowTimes=10client.tm.degradeCheckPeriod=2000client.tm.interceptorOrder=-2147482648client.undo.dataValidation=trueclient.undo.logSerialization=jacksonclient.undo.onlyCareUpdateColumns=trueserver.undo.logSaveDays=7server.undo.logDeletePeriod=86400000client.undo.logTable=undo_logclient.undo.compress.enable=trueclient.undo.compress.type=zipclient.undo.compress.threshold=64ktcc.fence.logTableName=tcc_fence_logtcc.fence.cleanPeriod=1hlog.exceptionRate=100store.mode=filestore.lock.mode=filestore.session.mode=filestore.publicKey=store.file.dir=file_store/datastore.file.maxBranchSessionSize=16384store.file.maxGlobalSessionSize=512store.file.fileWriteBufferCacheSize=16384store.file.flushDiskMode=asyncstore.file.sessionReloadReadSize=100store.db.datasource=druidstore.db.dbType=mysqlstore.db.driverClassName=com.mysql.jdbc.Driverstore.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=truestore.db.user=rootstore.db.password=123456store.db.minConn=5store.db.maxConn=30store.db.globalTable=global_tablestore.db.branchTable=branch_tablestore.db.distributedLockTable=distributed_lockstore.db.queryLimit=100store.db.lockTable=lock_tablestore.db.maxWait=5000#store.redis.mode=singlestore.redis.single.host=127.0.0.1store.redis.single.port=6379store.redis.sentinel.masterName=store.redis.sentinel.sentinelHosts=store.redis.maxConn=10store.redis.minConn=1store.redis.maxTotal=100store.redis.database=0store.redis.password=store.redis.queryLimit=100server.recovery.committingRetryPeriod=1000server.recovery.asynCommittingRetryPeriod=1000server.recovery.rollbackingRetryPeriod=1000server.recovery.timeoutRetryPeriod=1000server.maxCommitRetryTimeout=-1server.maxRollbackRetryTimeout=-1server.rollbackRetryTimeoutUnlockEnable=falseserver.distributedLockExpireTime=10000server.xaerNotaRetryTimeout=60000server.session.branchAsyncQueueSize=5000server.session.enableBranchAsyncRemove=falseserver.enableParallelRequestHandle=false#Metrics configuration, only for the servermetrics.enabled=falsemetrics.registryType=compactmetrics.exporterList=prometheusmetrics.exporterPrometheusPort=9898注意把这个配置文件汇总的mysql相关配置改成你的mysql相关的配置。
这里的seata数据库包含三张表:
DROPTABLEIFEXISTS`branch_table`;CREATETABLE`branch_table`(`branch_id`bigintNOTNULL,`xid`varchar(128)NOTNULL,`transaction_id`bigintDEFAULTNULL,`resource_group_id`varchar(32)DEFAULTNULL,`resource_id`varchar(256)DEFAULTNULL,`lock_key`varchar(128)DEFAULTNULL,`branch_type`varchar(8)DEFAULTNULL,`status`tinyintDEFAULTNULL,`client_id`varchar(64)DEFAULTNULL,`application_data`varchar(2000)DEFAULTNULL,`gmt_create`datetimeDEFAULTNULL,`gmt_modified`datetimeDEFAULTNULL,PRIMARYKEY(`branch_id`),KEY`idx_xid`(`xid`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci;DROPTABLEIFEXISTS`global_table`;CREATETABLE`global_table`(`xid`varchar(128)NOTNULL,`transaction_id`bigintDEFAULTNULL,`status`tinyintNOTNULL,`application_id`varchar(64)DEFAULTNULL,`transaction_service_group`varchar(64)DEFAULTNULL,`transaction_name`varchar(64)DEFAULTNULL,`timeout`intDEFAULTNULL,`begin_time`bigintDEFAULTNULL,`application_data`varchar(2000)DEFAULTNULL,`gmt_create`datetimeDEFAULTNULL,`gmt_modified`datetimeDEFAULTNULL,PRIMARYKEY(`xid`),KEY`idx_gmt_modified_status`(`gmt_modified`,`status`),KEY`idx_transaction_id`(`transaction_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci;DROPTABLEIFEXISTS`lock_table`;CREATETABLE`lock_table`(`row_key`varchar(128)NOTNULL,`xid`varchar(96)DEFAULTNULL,`transaction_id`mediumtext,`branch_id`mediumtext,`resource_id`varchar(256)DEFAULTNULL,`table_name`varchar(32)DEFAULTNULL,`pk`varchar(32)DEFAULTNULL,`gmt_create`datetimeDEFAULTNULL,`gmt_modified`datetimeDEFAULTNULL,PRIMARYKEY(`row_key`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci;第四步:修改conf配置文件,file.conf
修改:mode = \"db\"
再把数据库相关配置项修改为自己数据库的配置信息。
第五步:在Nacos上添加配置:
Data Id=seataServer.properties
Group=SEATA_GROUP
配置内容:
store.publicKey=123456store.db.datasource=druidstore.db.dbType=mysqlstore.db.driverClassName=com.mysql.cj.jdbc.Driverstore.db.url=jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=truestore.db.user=rootstore.db.password=123456记得选properties文件类型。
第六步:项目中配置resources目录下创建registry.conf文件。
文件内容:
registry { type = \"nacos\" nacos {application = \"seata-server\" serverAddr = \"127.0.0.1:8848\" group = \"SEATA_GROUP\" namespace = \"\" cluster = \"default\" username = \"nacos\" password = \"nacos\" }}config { type = \"nacos\" nacos { serverAddr = \"127.0.0.1:8848\" namespace = \"\" group = \"SEATA_GROUP\" username = \"nacos\" password = \"nacos\" dataId = \"seataServer.properties\" }}注意:这里面的group、dataId对应Nacos中的分布式配置。
第七步:在application.properties配置文件中。
# Nacos 注册中心地址spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848# seata 服务分组,要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应seata.tx-service-group=stock-service-groupseata.service.vgroup-mapping.stock-service-group=defaultlogging.level.io.seata=debug第八步,启动seata。双击seata-server-1.4.2/bin下面的seata-server.bat。
用户下单:
扣除库存--库存表中库存数量减少 生成订单--订单表新增一条订单数据stock-service 服务
service
@Transactional(rollbackFor=Exception.class)publicvoiddeduct(StringcommodityCode,intcount){if(commodityCode.equals(\"product-2\")){thrownewRuntimeException(\"异常:模拟业务异常:stockbranchexception\");}QueryWrapperwrapper=newQueryWrapper<>();wrapper.setEntity(newStock().setCommodityCode(commodityCode));Stockstock=stockDAO.selectOne(wrapper);stock.setCount(stock.getCount()-count);stockDAO.updateById(stock);} controller
@RestController@RequestMapping(\"stock\")publicclassStockController{@ResourceprivateStockServicestockService;/***减库存**@paramcommodityCode商品代码*@paramcount数量*@return*/@RequestMapping(path=\"/deduct\")publicBooleandeduct(StringcommodityCode,Integercount){stockService.deduct(commodityCode,count);returntrue;}}order-service服务:
@FeignClient(name=\"stock-service\")publicinterfaceStockFeignClient{@GetMapping(\"stock/deduct\")Booleandeduct(@RequestParam(\"commodityCode\")StringcommodityCode,@RequestParam(\"count\")Integercount);}@ServicepublicclassOrderService{@ResourceprivateStockFeignClientstockFeignClient;@ResourceprivateOrderDAOorderDAO;/***下单:创建订单、减库存,涉及到两个服务**@paramuserId*@paramcommodityCode*@paramcount*/@GlobalTransactional@Transactional(rollbackFor=Exception.class)publicvoidplaceOrder(StringuserId,StringcommodityCode,Integercount){BigDecimalorderMoney=newBigDecimal(count).multiply(newBigDecimal(5));Orderorder=newOrder().setUserId(userId).setCommodityCode(commodityCode).setCount(count).setMoney(orderMoney);orderDAO.insert(order);stockFeignClient.deduct(commodityCode,count);}}
@GlobalTransactional我们在业务代码部分,只需要关注这个注解就行,在你需要实现分布式事务的地方加上。
@Slf4j@RestController@RequestMapping(\"/order\")publicclassOrderController{@ResourceprivateOrderServiceorderService;@ResourceprivateStockFeignClientstockFeignClient;/***下单:插入订单表、扣减库存,模拟回滚*/@RequestMapping(\"/placeOrder/commit\")publicBooleanplaceOrderCommit(){orderService.placeOrder(\"1\",\"product-1\",1);returntrue;}/***下单:插入订单表、扣减库存,模拟回滚*/@RequestMapping(\"/placeOrder/rollback\")publicBooleanplaceOrderRollback(){//product-2扣库存时模拟了一个业务异常,try{orderService.placeOrder(\"1\",\"product-2\",1);}catch(Exceptionex){log.error(\"----------\",ex);}returntrue;}}每个数据库都必须见一张表:
CREATETABLE`undo_log`(`id`bigintNOTNULLAUTO_INCREMENT,`branch_id`bigintNOTNULL,`xid`varchar(100)NOTNULL,`context`varchar(128)NOTNULL,`rollback_info`longblobNOTNULL,`log_status`intNOTNULL,`log_created`datetimeNOTNULL,`log_modified`datetimeNOTNULL,`ext`varchar(100)DEFAULTNULL,PRIMARYKEY(`id`),UNIQUEKEY`ux_undo_log`(`xid`,`branch_id`))ENGINE=InnoDBAUTO_INCREMENT=10DEFAULTCHARSET=utf8;其他的两个表:
CREATETABLE`stock_tbl`(`id`intNOTNULLAUTO_INCREMENT,`commodity_code`varchar(255)DEFAULTNULL,`count`intDEFAULT"0",PRIMARYKEY(`id`),UNIQUEKEY`commodity_code`(`commodity_code`))ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8;CREATETABLE`order_tbl`(`id`intNOTNULLAUTO_INCREMENT,`user_id`varchar(255)DEFAULTNULL,`commodity_code`varchar(255)DEFAULTNULL,`count`intDEFAULT"0",`money`intDEFAULT"0",PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=22DEFAULTCHARSET=utf8;库存:
然后我们启动项目,测试;
访问正常:
GET http://localhost:9091/order/placeOrder/commit
订单库数据库订单表插入成功:
库存扣除成功:
再来掉一个模仿失败的场景,然数据库回滚;
GET http://localhost:9091/order/placeOrder/rollback
再去查订单表和库存表完全没有变化。
到此,咱们使用seata实现的分布式事务就这么“轻松的”完成了。 ,实话实说真不轻松,不行你按照官网文档试试。
为什么要聊分布式事务?
其实,我们在面试中多多少少都会问到相关的问题,不熟悉的减分,能回答出理论的加分但是不多,有实战项目的加分。
另外,我最近在搞充电桩项目时,因为整个项目规划分成多个子系统,数据库也拆分,在开发中遇到一个问题:用户采用积分兑换优惠券。积分在用户服务、优惠券在营销服务。这就需要同时操作两个数据库,并且要保证用户积分足够、优惠券数量足够。所以,思来想去还是使用seata来实现分布式事务。
关键词:
上一篇文章我们使用Atomikos实现了分布式事务:但是里面有个问题:可能会对同一个表生成两份mapper相关的内
1、去问问哪个学校的学生,一下就了解了e我毕业两年了,学的过程装备与控制工程我上学那个时候。2、我们
又一国际评级机构将美国列入负面观察名单
1、哈哈。2、电玩巴士全国联保……那纯粹是忽悠。3、他们就是把别人拆下来的零件修好,或者直接进组装的配
六一儿童节即将到来!小体这就送出儿童节福利带大家一起探索位于闵行区的都市运动中心——游悉谷多种亲子运
1、压缩包。2、迷羊全集。本文到此分享完毕,希望对大家有所帮助。
1、水滴筹需要满足的条件:在线填写筹款信息,填写完毕后即可开始筹款;在线完善相关验证信息,通过审核才
你们好,最近小品发现有诸多的小伙伴们对于藕怎么炒才好吃,藕片怎么炒这个问题都颇为感兴趣的,今天小活为
经过一夜较强降雨后,5月27日白天,达州市大部分地方降雨逐渐减弱或停歇,迎来阴间多云天气,万源仍有小雨
来为大家解答以上的问题。曙光大道,光大道介绍这个很多人还不知道,现在让我们一起来看看吧!1、为破除柳东
牛油果烂了一点一般不可以吃。通常腐烂的牛油果都是不宜吃的,但是有些水果只是烂了一点点,许多人觉得剩下
诸多的对于iphone开灯壁纸有特效,iphone开灯壁纸这个问题都颇为感兴趣的,为大家梳理了下,一起往
1、这个人叫:MeganRain也叫kianacoleman,1996年出生于美国加州,出道时间2014年百度没
昨天午后到夜里,浙北北部和浙西南地区局部出现强对流天气,有25个乡镇(街道)雨量超过50毫米,其中1个超
1、主要还是要看你的经济能力,如果你标出你的经济范围也好把哪个牌子的那种款给你具体的说以下:300元一下光
1、完结作品! ================ 恶魔狂想曲之明日骄阳作者:胡鳕 疾风佣兵团中毫
文 李妹妍出乎很多人的意料的是,一向聚焦经济发展领域大事的中央财经委会议上, "人口 "成了关键词之一。
欢迎观看本篇文章,小升来为大家解答以上问题。天翼高清机顶盒怎么用,中国电信机顶盒怎么连接电视机播放很
很快WBG也官宣了夏季赛的大名单,和春季赛的主力阵容相比,只有打野位置多了替补weiwei,其他的位置都只有
1、流星般的奔驰,为秋天画下一道风景,辛酸的泪汗,为生命添上一份色彩,成功的喜悦,为心绪加上一份激情