seata xid是什么_使用Seata彻底解决Spring Cloud中的分布式事务问题!

Seata是Alibaba开源的一款分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,本文将通过一个简单的下单业务场景来对其用法进行详细介绍。

什么是分布式事务问题?

单体应用

单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。

be285f8a5c910dc146b1a8ce6dfb071a.png

微服务应用

随着业务需求的变化,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

5e1b8ca5ab058067c19ef13f86786571.png

小结

在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。

Seata简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata原理和设计

定义一个分布式事务

我们可以把一个分布式事务理解成一个包含了若干分支事务的全局事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足ACID的本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。

协议分布式事务处理过程的三个组件Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;

Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

一个典型的分布式事务过程TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;

XID 在微服务调用链路的上下文中传播;

RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;

TM 向 TC 发起针对 XID 的全局提交或回滚决议;

TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

seata-server的安装与配置我们先从官网下载seata-server,这里下载的是seata-server-0.9.0.zip,下载地址:https://github.com/seata/seata/releases

解压seata-server安装包到指定目录,修改conf目录下的file.conf配置文件,主要修改自定义事务组名称,事务日志存储模式为db及数据库连接信息;service {

#vgroup->rgroup

vgroup_mapping.fsp_tx_group = "default" #修改事务组名称为:fsp_tx_group,和客户端自定义的名称对应

#only support single node

default.grouplist = "127.0.0.1:8091"

#degrade current not support

enableDegrade = false

#disable

disable = false

#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent

max.commit.retry.timeout = "-1"

max.rollback.retry.timeout = "-1"

}

## transaction log store

store {

## store mode: file、db

mode = "db" #修改此处将事务信息存储到数据库中

## database store

db {

## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.

datasource = "dbcp"

## mysql/oracle/h2/oceanbase etc.

db-type = "mysql"

driver-class-name = "com.mysql.jdbc.Driver"

url = "jdbc:mysql://localhost:3306/seat-server" #修改数据库连接地址

user = "root" #修改数据库用户名

password = "root" #修改数据库密码

min-conn = 1

max-conn = 3

global.table = "global_table"

branch.table = "branch_table"

lock-table = "lock_table"

query-limit = 100

}

}由于我们使用了db模式存储事务日志,所以我们需要创建一个seat-server数据库,建表sql在seata-server的/conf/db_store.sql中;

修改conf目录下的registry.conf配置文件,指明注册中心为nacos,及修改nacos连接信息即可;registry {

# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa

type = "nacos" #改为nacos

nacos {

serverAddr = "localhost:8848" #改为nacos的连接地址

namespace = ""

cluster = "default"

}

}先启动Nacos,再使用seata-server中/bin/seata-server.bat文件启动seata-server。

数据库准备

创建业务数据库seat-order:存储订单的数据库;

seat-storage:存储库存的数据库;

seat-account:存储账户信息的数据库。

初始化业务表

order表CREATE TABLE `order` (

`id` bigint(11) NOT NULL AUTO_INCREMENT,

`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 '金额',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

ALTER TABLE `order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' AFTER `money` ;

storage表CREATE TABLE `storage` (

`id` bigint(11) NOT NULL AUTO_INCREMENT,

`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 '剩余库存',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO `seat-storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');

account表CREATE TABLE `account` (

`id` bigint(11) NOT NULL AUTO_INCREMENT 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 '剩余可用额度',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO `seat-account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');

创建日志回滚表

使用Seata还需要在每个数据库中创建日志表,建表sql在seata-server的/conf/db_undo_log.sql中。

完整数据库示意图

制造一个分布式事务问题

这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

客户端配置对seata-order-service、seata-storage-service和seata-account-service三个seata的客户端进行配置,它们配置大致相同,我们下面以seata-order-service的配置为例;

修改application.yml文件,自定义事务组的名称;spring:

cloud:

alibaba:

seata:

tx-service-group: fsp_tx_group #自定义事务组名称需要与seata-server中的对应添加并修改file.conf配置文件,主要是修改自定义事务组名称;service {

#vgroup->rgroup

vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称

#only support single node

default.grouplist = "127.0.0.1:8091"

#degrade current not support

enableDegrade = false

#disable

disable = false

#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent

max.commit.retry.timeout = "-1"

max.rollback.retry.timeout = "-1"

disableGlobalTransaction = false

}添加并修改registry.conf配置文件,主要是将注册中心改为nacos;registry {

# file 、nacos 、eureka、redis、zk

type = "nacos" #修改为nacos

nacos {

serverAddr = "localhost:8848" #修改为nacos的连接地址

namespace = ""

cluster = "default"

}

}在启动类中取消数据源的自动创建:@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

@EnableDiscoveryClient

@EnableFeignClients

public class SeataOrderServiceApplication{

public static void main(String[] args){

SpringApplication.run(SeataOrderServiceApplication.class, args);

}

}创建配置使用Seata对数据源进行代理:/**

* 使用Seata对数据源进行代理

* Created by macro on 2019/11/11.

*/

@Configuration

public class DataSourceProxyConfig{

@Value("${mybatis.mapperLocations}")

private String mapperLocations;

@Bean

@ConfigurationProperties(prefix = "spring.datasource")

public DataSource druidDataSource(){

return new DruidDataSource();

}

@Bean

public DataSourceProxy dataSourceProxy(DataSource dataSource){

return new DataSourceProxy(dataSource);

}

@Bean

public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception{

SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

sqlSessionFactoryBean.setDataSource(dataSourceProxy);

sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()

.getResources(mapperLocations));

sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());

return sqlSessionFactoryBean.getObject();

}

}使用@GlobalTransactional注解开启分布式事务:package com.macro.cloud.service.impl;

import com.macro.cloud.dao.OrderDao;

import com.macro.cloud.domain.Order;

import com.macro.cloud.service.AccountService;

import com.macro.cloud.service.OrderService;

import com.macro.cloud.service.StorageService;

import io.seata.spring.annotation.GlobalTransactional;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

/**

* 订单业务实现类

* Created by macro on 2019/11/11.

*/

@Service

public class OrderServiceImpl implements OrderService{

private static final Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);

@Autowired

private OrderDao orderDao;

@Autowired

private StorageService storageService;

@Autowired

private AccountService accountService;

/**

* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态

*/

@Override

@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)

public void create(Order order){

LOGGER.info("------->下单开始");

//本应用创建订单

orderDao.create(order);

//远程调用库存服务扣减库存

LOGGER.info("------->order-service中扣减库存开始");

storageService.decrease(order.getProductId(),order.getCount());

LOGGER.info("------->order-service中扣减库存结束:{}",order.getId());

//远程调用账户服务扣减余额

LOGGER.info("------->order-service中扣减余额开始");

accountService.decrease(order.getUserId(),order.getMoney());

LOGGER.info("------->order-service中扣减余额结束");

//修改订单状态为已完成

LOGGER.info("------->order-service中修改订单状态开始");

orderDao.update(order.getUserId(),0);

LOGGER.info("------->order-service中修改订单状态结束");

LOGGER.info("------->下单结束");

}

}

分布式事务功能演示运行seata-order-service、seata-storage-service和seata-account-service三个服务;

数据库初始信息状态:

6f678f96d269b291e58dda3d08b8f65b.png调用接口进行下单操作后查看数据库:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100

234f6c70125435e9b477d993e3b5c209.png我们在seata-account-service中制造一个超时异常后,调用下单接口:/**

* 账户业务实现类

* Created by macro on 2019/11/11.

*/

@Service

public class AccountServiceImpl implements AccountService{

private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);

@Autowired

private AccountDao accountDao;

/**

* 扣减账户余额

*/

@Override

public void decrease(Long userId, BigDecimal money){

LOGGER.info("------->account-service中扣减账户余额开始");

//模拟超时异常,全局事务回滚

try {

Thread.sleep(30*1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

accountDao.decrease(userId,money);

LOGGER.info("------->account-service中扣减账户余额结束");

}

}此时我们可以发现下单后数据库数据并没有任何改变;

234f6c70125435e9b477d993e3b5c209.png我们可以在seata-order-service中注释掉@GlobalTransactional来看看没有Seata的分布式事务管理会发生什么情况:/**

* 订单业务实现类

* Created by macro on 2019/11/11.

*/

@Service

public class OrderServiceImpl implements OrderService{

/**

* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态

*/

@Override

//    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)

public void create(Order order){

LOGGER.info("------->下单开始");

//省略代码...

LOGGER.info("------->下单结束");

}

}由于seata-account-service的超时会导致当库存和账户金额扣减后订单状态并没有设置为已经完成,而且由于远程调用的重试机制,账户余额还会被多次扣减。

536139b8914598c35c43d86ca9668216.png

参考资料

Seata官方文档:https://github.com/seata/seata/wiki

使用到的模块springcloud-learning

├── seata-order-service -- 整合了seata的订单服务

├── seata-storage-service -- 整合了seata的库存服务

└── seata-account-service -- 整合了seata的账户服务

项目源码地址

https://github.com/macrozheng/springcloud-learning

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294729.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于netty的企业即时通讯系统的设计与实-离线消息处理

2019独角兽企业重金招聘Python工程师标准>>> 原文地址:http://www.cookqq.com/blog/8a10a5f35382ba2e0153c7a1125c20e7 客户端与服务器保持长连接,服务器可以很轻松的向客户端推送消息。由于网络不稳定、程序不可能24小时都被程序运行&…

GeneralUpdate实现应用程序更新

微软中国MSDN 点击上方蓝字关注我们大家好,我是本期的实验室研究员——朱震。今天我将通过实验和完整的操作过程,向大家介绍如何基于开源项目GeneralUpdate打造一个能自动升级的客户端应用。接下来就让我们一起到实验室中一探究竟吧!微软MVP…

看完数学概念背后的故事,让孩子的数学兴趣激增1000倍!

▲ 点击查看英国著名科学家霍金在撰写《时间简史》的时候,出版商郑重其事地建议道:“你的书里多一条数学公式,就会失去一部分读者。”可见对数理化的害怕,也没有国界,大家都一样。看着满满都是概念和数字的课本&#x…

主存和cache每一块相等_笔记:cpu中的cache(一)

前言:绝大部分内容来源于北京大学的慕课《计算机组成原理》,地址:https://www.coursera.org/learn/jisuanji-zucheng存储体系(《深入理解计算机系统》)cache是为了解决cpu和内存速度不对等的问题。一,cache…

设计模式学习笔记(十七)——Command命令模式

设计模式学习笔记(十七)——Command命令模式 Command命令模式介绍: Command命令模式是一种对象行为型模式,它主要解决的问题是:在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”的问…

MAUI中Maui.Graphics.Controls绘制控件

简介Microsoft.Maui.Graphics是一个完全采用C#的iOS,Android,Windows,macOS,Tizen和Linux的跨平台图形库。对于MAUI项目当中绘制的方案是使用不同平台的控件来而非自绘。当然MAUI当中也使用了Microsoft.Maui.Graphics,MAUI Previe…

BGP聚合as-set advertise-map

advertise-map xxx 用来挂汇总路由的,当route-map xxx 中的路由存在时,才会出现汇总路由。否则没有汇总路由只继承advertise-map xxx 中路由的属性当汇总路由携带了多个明细属性,可用advertise-map xxx 来移除某个明细路由的路由属性实验拓扑…

Android之如何解决popupWindow(pw.setFocusable(true))按返回键和menu键退出

先爆照: 问题: 使用过popupWindow的时候,我们为了让其它地方不可点击,我们一般会pw.setFocusable(true),但是当我们这样设置之后,问题出现了,按返回键或者menu菜单键没有反应,这是原因呢? PopupWindow 跟我们的 Activity 不一样,因为我们在构造 PW 的时候往往不是继…

上班骚扰男同事被抓到......

1 不同年代的辞职理由(素材来源网络,侵删)▼2 月壤不能种菜太让人失望了▼3 原来,我也努力过▼4 忘了上班还有监控(作者来源最右,ID:8943090 )▼5 这是台风被黑的最惨的一次&…

mysql8 修改权限_MySQL8修改重置root密码,远程连接权限设置

MySQL8 修改重置root密码这里要单独强调是MySQL8,因为在MySQL 8.04前,执行:SET PASSWORDPASSWORD([新密码]);可行,但是MySQL8.0.4开始,这样默认是不行的。因为之前,MySQL的密码认证插件是“mysql_native_pa…

用wordpress制作网站的总结

在没有自己的网站的时候很像拥有一个属于自己的网站,可以说是自己的一个愿望吧。但是当我真正的运行起来了之后觉得没有多么的兴奋,自己也折腾了几天wordpress,从买域名空间,到安装WP,遇到了一些问题,自己到处搜索答案…

Android之使用PopupWindow使用和总结

不废话,先爆照 说明: 那个灰色背景是不能滑动里ListView里面的内容的 第一步:我们需要背景view 下面是我的background.xml文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/t…

Cell发文!施一公科研团队取得重大突破

全世界只有3.14 % 的人关注了爆炸吧知识本文来源&#xff1a;科学网&#xff08;有删减&#xff09;北京时间2020年12月29日凌晨0时&#xff0c;《细胞》&#xff08;Cell&#xff09;发表中科院院士、西湖大学校长施一公课题组的一项新研究。研究首次报道了γ-分泌酶&#xff…

MAUI 跨平台播客应用程序(Conf 2021)

介绍在.NET Conf 2021大会上&#xff0c;微软展示了基于.NET6 跨平台应用程序, 具有ASP.NET Core、Blazor、.NET MAUI、微服务等功能。浏览由 ASP.NET Core 和 Blazor 提供支持的 .NET Podcasts 应用的实时运行版本&#xff1a;https://dotnetpodcasts.azurewebsites.net/项目体…

Android之如何实现通讯录的搜索并且让匹配到的数据变颜色

不废话,先爆照 第一步:实现搜索 已经实现了通讯录功能,但是需要搜索,可以支持中文名字搜索,写入电话号码搜索,还有名字拼音,以及名字第一个字的首字母来搜索,这里介绍名字搜索,然后数据是我们公司TCL(020)所有员工的信息,目前还没有写到后…

dotnet 将自动代码格式化机器人带入团队 GitLab 平台

给团队带入一个 代码格式化机器人 能提升团队的幸福度&#xff0c;让团队的成员安心写代码&#xff0c;不用关注代码格式化问题&#xff0c;将格式代码这个粗活交给机器人去做。同时也能减少在代码审查里撕格式化问题的时间&#xff0c;让更多的时间投入到更有价值的工作上本文…

Android 之自定义view实现水波纹效果

在实际的开发中&#xff0c;很多时候还会遇到相对比较复杂的需求&#xff0c;比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果&#xff0c;兴致高昂的来找你&#xff0c;看了之后目的很明确&#xff0c;当然就是希望你能给她&#xff1b; 在这样的关键时候&#xff0c;身子板就一…

FFLIb Demo CQRS

使用FFLIB 构建了一个demo&#xff0c;该demo模拟了一个常见的游戏后台架构&#xff0c;该demo主要有一下亮点&#xff1a; FFLIB 实现进程间通信非常方便基于CQRS 思想构建LogicServer使用Event Publish/Subscribe&#xff0c; 实现各个模块的解耦合基于Event 实现实体对象的单…

【自定义标签开发】01-标签简介和开发第一个标签

自定义标签简介自定义标签主要用于移除Jsp页面中的java代码。要使用自定义标签移除jsp页面中的java代码&#xff0c;只需要完成以下两个步骤:1.编写一个实现Tag接口的java类&#xff0c;把页面java代码移到这个java类中(标签处理器类)。2.编写标签库描述(tld)文件&#xff0c;在…

三联《少年》创刊,各领域佼佼者畅言新知,帮少年建立思维素养体系!

▲点击查看很多中国小孩的成长是断层的。10岁前被视作可爱稚子&#xff0c;被大人护着走&#xff1b;18岁猛然被定义为成年人&#xff0c;要选择大学、专业&#xff0c;开始面对感情。中间的人生呢&#xff1f;“你是个学生&#xff0c;学习是本职&#xff0c;现在谈什么人生&a…