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
- 1. TC (Coordinador de transacciones): el coordinador de transacciones
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 .