1. Introduction
Adresse du site officiel: http://seata.io/zh-cn/
1. Concept
Seata est une solution de transaction distribuée open source, dédiée à la fourniture de services de transaction distribués simples et performants dans l'architecture de microservices.
2. Traitement
ID de transaction XID: ID de transaction unique au monde
Transaction Coordinator (TC): Maintient le statut des transactions globales et des succursales, et dirige la validation ou l'annulation des transactions globales.
Transaction Manager ™: définissez la portée des transactions globales: démarrez des transactions globales, validez ou annulez des transactions globales.
Resource Manager (RM): Gérez les ressources pour le traitement des transactions de la succursale, parlez à TC pour enregistrer les transactions de la succursale et rapportez le statut des transactions de la succursale, et pilotez la validation ou l'annulation des transactions de la succursale.
- TM s'applique à TC pour démarrer une transaction globale, la transaction globale est créée avec succès et un XID unique au monde est généré
- XID se propage dans le contexte du lien d'appel du microservice
- RM enregistre les transactions des succursales auprès de TC et les inclut sous la juridiction des transactions globales correspondantes XID
- TM lance une résolution globale de soumission ou de restauration pour XID à TC
- TC planifie toutes les transactions des succursales sous la juridiction de XID pour répondre aux demandes de soumission ou d'annulation
Deuxièmement, l'installation de Seata-Server
1. Télécharger
http://seata.io/zh-cn/blog/download.html Choisissez la version spécifiée à télécharger (j'utilise la 0.9.0 ici)
2. Modifiez le fichier de configuration
Modifier seata / conf / file.conf
#将service中修改group
vgroup_mapping.my_test_tx_group = "my_group"
#将store模块修改为db并修改数据连接,将conf目录下的db_store.sql文件导入到数据库中
mode = "db"
db {
datasource = "dbcp"
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "123456"
}
Modifier seata / conf / registry.conf
registry {
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
Trois, application Seata
1. Service de commande
Code source: seata-order-service2001
a, configurez pom
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--web-actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--mysql-druid-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
View Code
b, configurez yaml
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与seata-server中的对应
tx-service-group: my_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: 123456
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
View Code
c, ajoutez file.conf (identique à la configuration de seata-server)
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
vgroup_mapping.my_group = "default"
default.grouplist = "127.0.0.1:8091"
enableDegrade = false
disable = false
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
disableGlobalTransaction = false
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
report.retry.count = 5
tm.commit.retry.count = 1
tm.rollback.retry.count = 1
}
## transaction log store
store {
## store mode: file、db
mode = "db"
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## 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
}
}
lock {
## the lock store mode: local、remote
mode = "remote"
local {
## store locks in user's database
}
remote {
## store locks in the seata's server
}
}
recovery {
#schedule committing retry period in milliseconds
committing-retry-period = 1000
#schedule asyn committing retry period in milliseconds
asyn-committing-retry-period = 1000
#schedule rollbacking retry period in milliseconds
rollbacking-retry-period = 1000
#schedule timeout retry period in milliseconds
timeout-retry-period = 1000
}
transaction {
undo.data.validation = true
undo.log.serialization = "jackson"
undo.log.save.days = 7
#schedule delete expired undo_log in milliseconds
undo.log.delete.period = 86400000
undo.log.table = "undo_log"
}
## metrics settings
metrics {
enabled = false
registry-type = "compact"
# multi exporters use comma divided
exporter-list = "prometheus"
exporter-prometheus-port = 9898
}
support {
## spring
spring {
# auto proxy the DataSource bean
datasource.autoproxy = false
}
}
View Code
d, ajoutez registry.conf (identique à la configuration de seata-server)
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
View Code
e, appel Fegin (ici l'un des comptes est pris comme exemple)
@FeignClient(value = "seata-account-service")
public interface AccountService {
@RequestMapping("/account/decrease")
public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
View Code
f, service de transaction
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderDao orderDao;
@Autowired
AccountService accountService;
@Autowired
StorageService storageService;
@Override
@GlobalTransactional(name = "my-order-test",rollbackFor = Exception.class) //加注解使用全局的事务,name 为事务名称不重复就行
public Long create(Order order) {
log.info("=========================下订单,开始");
orderDao.create(order);
log.info("=========================下订单,完成");
log.info("=========================减库存,开始");
storageService.decrease(order.getProductId(), order.getCount());
log.info("=========================减库存,完成");
log.info("=========================减积分,开始");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("=========================减积分,完成");
log.info("=========================订单状态修改,开始");
orderDao.update(order.getId(),1);
log.info("=========================订单状态修改,完成");
return order.getId();
}
}
View Code
g, commencer la classe
View Code
2. Service d'inventaire
Code source: seata-storage-service2002
Identique aux étapes de configuration de a, b, c, d, g dans le service de commande
3. Service de compte
Code source: seata-account-service2003
Mêmes étapes de configuration que le service d'inventaire
Quatrièmement, l'analyse principale de Seata
Document de référence: http://seata.io/zh-cn/docs/overview/what-is-seata.html
1. Mode AT
Une étape
1,解析SQL语义,找到"业务SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image"
2,执行"业务SQL"更新业务数据,在业务数更新之后
3,将其保存成"after image",最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
Engagement en deux phases
Parce que "Business SQL" dans une phase a été soumis à la base de données, si seata frame juste pour enregistrer un instantané d'une étape et de supprimer des données de verrouillage de ligne , terminer le nettoyage des données peut être.
Restauration en deux étapes
Si la deuxième étape est une restauration, seata doit annuler le "SQL métier" qui a été exécuté lors de la première étape pour restaurer les données métier.
La façon de revenir en arrière consiste à utiliser «avant l'image» pour restaurer les données d'entreprise, mais avant la restauration, vérifiez d'abord les écritures incorrectes, comparez les «données d'entreprise actuelles de la base de données» et «l'image après»
Si les deux données sont parfaitement cohérentes, cela signifie qu'il n'y a pas d'écriture sale et que les données métier peuvent être restaurées. Si elles sont incohérentes, cela signifie qu'il y a une écriture sale, et si une écriture sale se produit, un traitement manuel est nécessaire.