solución de transacciones distribuidas seata

Consulte gitee para ver todo el proyecto: https://gitee.com/xwb1056481167/spring-cloud

Para la configuración e instalación de nacos, vaya a: https://blog.csdn.net/www1056481167/article/details/113612177 ver

seata 官 网 :https://seata.io/zh-cn

El proceso de transacción distribuida es

Un modelo de ID + tres componentes

Concepto clave

  • ID de la
           transacción global
  • Concepto de tres componentes
    • 1. TC (Coordinador de transacciones): el coordinador de transacciones
      mantiene el estado de las transacciones globales y de las sucursales, e impulsa la confirmación o reversión de transacciones globales.
    • 2. TM (Administrador de transacciones): el administrador
      de transacciones define el alcance de las transacciones globales: iniciar transacciones globales, confirmar o revertir transacciones globales
    • 3. RM (Administrador de recursos): el administrador de
      recursos administra los recursos del procesamiento de transacciones de la sucursal, habla con el TC para registrar las transacciones de la sucursal e informar el estado de las transacciones de la sucursal e impulsar el envío o reversión de las transacciones de la sucursal

Diagrama de flujo de procesamiento

Descripción:
1. TM se aplica a TC para abrir una transacción global, y la transacción global se crea con éxito y se genera un XID globalmente único.
2. XID se propaga en el contexto del enlace de propagación de microservicios.
3. RM registra la transacción de sucursal con TC y lo incorpora a la correspondencia de XID Jurisdicción de asuntos globales
4. TM inicia una resolución global de remisión o reversión para XID a TC
5. TC programa todas las transacciones de sucursales bajo la jurisdicción de XID para completar solicitudes de remisión o reversión

Configuración de construcción del entorno de Seata

1. Descarga de seata

Enlace de descarga: https://github.com/seata/seata/releases
Versión 0.9: windows:  https://github.com/seata/seata/releases/download/v0.9.0/seata-server-0.9.0.zip

En segundo lugar, la instalación de seata

1. Modifique el nombre del grupo de transacciones personalizado file.conf + modo de registro de transacciones a db + conexión a la base de datos

1. vgroup_mapping. De Servicio my_test_tx_group atributo

service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "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"
}

2. Especifique el método de almacenamiento como base de datos.

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://127.0.0.1:3306/seata"
    user = "root"
    password = "123456"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}

3. Cree una base de datos seata y cree una tabla.

-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `global_table` (
  `xid` varchar(128)  not null,
  `transaction_id` bigint,
  `status` tinyint not null,
  `application_id` varchar(32),
  `transaction_service_group` varchar(32),
  `transaction_name` varchar(128),
  `timeout` int,
  `begin_time` bigint,
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`xid`),
  key `idx_gmt_modified_status` (`gmt_modified`, `status`),
  key `idx_transaction_id` (`transaction_id`)
);

-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
  `branch_id` bigint not null,
  `xid` varchar(128) not null,
  `transaction_id` bigint ,
  `resource_group_id` varchar(32),
  `resource_id` varchar(256) ,
  `lock_key` varchar(128) ,
  `branch_type` varchar(8) ,
  `status` tinyint,
  `client_id` varchar(64),
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`branch_id`),
  key `idx_xid` (`xid`)
);

-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
  `row_key` varchar(128) not null,
  `xid` varchar(96),
  `transaction_id` long ,
  `branch_id` long,
  `resource_id` varchar(256) ,
  `table_name` varchar(32) ,
  `pk` varchar(36) ,
  `gmt_create` datetime ,
  `gmt_modified` datetime,
  primary key(`row_key`)
);

2. Modifique la información de registro de registry.conf

Modifique el tipo de registro a nacos y especifique la dirección de registro de nacos como localhost: 8848 (según su configuración de nacos)

Para la instalación y configuración de nacos, vaya a https://blog.csdn.net/www1056481167/article/details/113612177 para ver

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  ...

Si la verificación de inicio está configurada correctamente

1. Inicie nacos
2. Inicie seata (haga doble clic en seata-server.bat para encontrar la declaración de registro registrada en nacos)

Tres, preparar la estructura de la mesa de negocios

DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) NULL DEFAULT NULL COMMENT '用户id',
  `total` decimal(10, 0) NULL DEFAULT NULL COMMENT '总额度',
  `used` decimal(10, 0) NULL DEFAULT NULL COMMENT '已用额度',
  `residue` decimal(10, 0) NULL DEFAULT NULL COMMENT '剩余可用额度',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4;

DROP TABLE IF EXISTS `seata_storage`;
CREATE TABLE `t_storage`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(11) NULL DEFAULT NULL COMMENT '产品id',
  `total` int(11) NULL DEFAULT NULL COMMENT '库存',
  `used` int(11) NULL DEFAULT NULL COMMENT '已用库存',
  `residue` int(11) NULL DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 ;

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) NULL DEFAULT NULL COMMENT '用户id',
  `product_id` bigint(11) NULL DEFAULT NULL COMMENT '产品id',
  `count` int(11) NULL DEFAULT NULL COMMENT '数量',
  `money` decimal(11, 0) NULL DEFAULT NULL COMMENT '金额',
  `status` int(1) NULL DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4;

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;

Estructura de la mesa final

Nuevo pedido, inventario, módulo de saldo

Requisito comercial: realizar un pedido -> reducir el inventario -> deducir el saldo -> cambiar el estado (del pedido)

Solo el código principal se enumera a continuación, verifique todos los códigos en gitee

Nuevo módulo de pedidos seata-order-service2001

1. pom.xml (parcialmente omitido)

<!--  SpringCloud alibaba nacos    -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!-- 排除自身附带的jar包 -->
        <exclusion>
            <artifactId>seata-all</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 引入和服务端相匹配的jar包 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>0.9.0</version>
</dependency>
<!--   openfeign     -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2 、 application.yml

server:
  port: 2001
spring:
  application:
    name: seata-order-service
  cloud:
    nacos:
      discovery: #Nacos注册中心地址
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: fsp_tx_group
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
#    type: com.alibaba.druid.pool.DruidDataSource
feign:
  hystrix:
    enabled: true

logging:
  level:
    io:
      seata: info
mybatis:
  mapper-locations: classpath:mapper/*.xml

3. Haga una copia de seguridad del archivo de configuración de seata file.conf  registry.conf
4. Proporcione dao, domain, mapper * .xml, controller .
5. El servicio  presta especial atención a los métodos de otros microservicios que se llaman. @FeignClient (valor = "xx") necesita indicar la interfaz del método a llamar. El método proporcionado por el servicio en sí no necesita esta anotación.

6, configuración de configuración

//1、mybatis扫描的包
@Configuration
@MapperScan("org.xwb.springcloud.dao.*")
public class MyBatisConfig {
}
//2、druid数据源  
@Configuration
public class DataSourceProxyConfig {
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSourceProxy);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources(mapperLocations));
        SqlSessionFactory factory;
        try {
            factory = bean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return factory;
    }
}

7, la clase de inicio principal

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderMainApp2001 {
    public static void main(String[] args) {
        SpringApplication.run(SeataOrderMainApp2001.class, args);
    }
}

Preste especial atención a  @SpringBootApplication (exclude = DataSourceAutoConfiguration.class) para
excluir su propia fuente de datos y utilizar su propia fuente de datos definida

Cree un nuevo módulo de inventario seata-storage-service2002

Igual que en 2001 anterior, solo las diferencias se explican a continuación
1. application.xml

server:
  port: 2002
spring:
  application:
    name: seata-storage-service
  cloud:
    nacos:
      discovery: #Nacos注册中心地址
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: fsp_tx_group
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
#    type: com.alibaba.druid.pool.DruidDataSource
feign:
  hystrix:
    enabled: true

logging:
  level:
    io:
      seata: info
mybatis:
  mapper-locations: classpath:mapper/*.xml

Otras empresas pueden ver directamente el proyecto.

Nuevo módulo de cuenta seata-account-service2003

Igual que en 2003, solo las diferencias se enumeran a continuación

server:
  port: 2003

spring:
  application:
    name: seata-account-service
  cloud:
    alibaba:
      seata:
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
#    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
feign:
  hystrix:
    enabled: true
logging:
  level:
    io:
      seata: info
mybatis:
  mapper-locations: classpath*:mapper/*.xml

Para otros proyectos originales de código de referencia comercial, vaya a gitee para ver

Prueba ( prueba normal, GlobalTransactional )

Prueba normal (prueba sin transacción unificada de GlobalTransactional )

1. Inicio nacos, seata, 2001, 2002, 2003 

2, navegador e ingrese  http: // localhost2001 / order / create userId = 1 & productId = 1 & count = 1 & money = 100? Verifique que el inventario sea correcto
3, entonces la necesidad de tratar los pagos de la cuenta seata-account-service2003 para realizar cambios ( modificar el tiempo de espera, (la deducción de la cuenta falló )

@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
    @Resource
    private AccountDao accountDao;
    @Override
    public void decrease(Long userId, BigDecimal money) {
        log.info("********->account-service中扣减账户余额开始");
        //此处故意超时
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        accountDao.decrease(userId, money);
        log.info("********->account-service中扣减账户余额结束");
    }
}

Resultados de la prueba

Verá que el pedido se creó correctamente, pero el estado del pedido es incorrecto.

El análisis muestra que:

Todo se divide en 4 partes 1. Nuevo pedido 2. Deducción del inventario 3. Deducción de la cuenta 4. Modificación del estado del pedido

Las 3 partes anteriores se ejecutaron con éxito, pero la necesidad final de modificar el estado de la orden falló. Debido a que establecimos un tiempo de espera, dejamos deliberadamente que se procesara el tiempo de espera. En este momento, debido a que no agregamos el control del transacción global, la falla a mitad de camino no se ejecutó con éxito, pero en su anterior

Si la ejecución tiene éxito, no se puede revertir, lo que hace que todo sea incorrecto. Se realizó el pedido, se dedujo el pago y se cambió el estado del pedido, pero era incorrecto. En este momento, se necesitan asuntos globales para resolver el problema de las cosas inconsistentes.

Utilice GlobalTransactional

A través de la prueba anterior, encontrará que el procesamiento de transacciones no es uniforme. A continuación, utilice la transacción global de seata para controlar

1. Agregue la anotación @GlobalTransactional (name = "fsp-create-order", rollbackFor = Exception.class) El nombre se puede personalizar

@Override
@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void create(Order order) {
    log.info("--------->开始新建订单");
    //1 新建订单
    orderDao.create(order);
    //2 扣减库存
    log.info("------------->订单微服务开始调用库存,做扣减Count");
    storageService.decrease(order.getProductId(), order.getCount());
    log.info("------------->订单微服务开始调用库存,做扣减end");
    //3 扣减账户
    log.info("------------->订单微服务开始调用账户,做扣减Money");
    accountService.decrease(order.getUserId(), order.getMoney());
    log.info("------------->订单微服务开始调用账户,做扣减end");
    //4 修改订单状态
    log.info("------------->修改订单状态开始");
    orderDao.update(order.getUserId(), 0);
    log.info("------------->修改订单状态结束");
    log.info("------------->下订单结束了");
}

Reinicie la aplicación, solicite el pedido nuevamente, encontrará que no se puede agregar el pedido completo y todos los pedidos se revierten

Análisis de principios

TM inicia transacciones distribuidas (TM registra registros de transacciones globales
con TC) de acuerdo con los escenarios comerciales, organiza la base de datos, el servidor y otros recursos de transacciones (RM informa el estado de preparación de recursos a TC
) TM finaliza las transacciones distribuidas y la primera etapa de la transacción está lista ( TM informa al TC que envíe, deshaciendo las transacciones distribuidas) El
TC informa la información de la transacción y decide si la transacción distribuida debe enviarse o deshacerse.
TC notifica a todos los RM que envíen los recursos de reversión y finaliza la segunda fase de la transacción

Análisis de principios básicos

Carga de una etapa

En la primera etapa, Seata interceptará "SQL empresarial"

1. Analice la semántica de SQL, busque los datos comerciales que se actualizarán en "Business SQL", guárdelos en "antes de la imagen" antes de que se actualicen los datos comerciales,
2. Ejecute "Business SQL" para actualizar los datos comerciales, después de los datos comerciales se actualiza.
3. Guárdelo en la ciudad "después de la imagen", y finalmente genere un bloqueo de fila.

Todas las operaciones anteriores se completan dentro de una transacción de base de datos, lo que garantiza la atomicidad de las operaciones de una etapa.

Fase dos (éxito, fracaso)

1. Si la segunda etapa se envía con éxito.
Debido a que el "SQL comercial" se envió a la base de datos en la primera etapa, el marco de Seata solo necesita limpiar los datos con los datos instantáneos y los bloqueos de nivel de fila guardados en la primera etapa.

2. La segunda etapa falló (reversión)

Si la segunda etapa es una reversión, Seata debe revertir el "SQL comercial" que se ejecutó en la primera etapa para restaurar los datos comerciales. El
método de reversión es restaurar los datos comerciales con la "imagen anterior"; pero antes la restauración, primero se debe verificar la escritura sucia., Comparando "datos comerciales actuales de la base de datos" y "imagen posterior", si los dos datos son exactamente iguales, significa que no hay escritura sucia y los datos comerciales se pueden restaurar Si son inconsistentes, significa que hay escritura sucia y la escritura sucia debe transferirse al procesamiento manual .

Supongo que te gusta

Origin blog.csdn.net/www1056481167/article/details/113700248
Recomendado
Clasificación