seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。
两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
一阶段本地事务提交前,需要确保先拿到 全局锁 。
拿不到 全局锁 ,不能提交本地事务。
拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
一般情况下seata的下载与Alibaba的其他两款组件一样,好使但是在官网的发布页上死活下载不下来,下载速度奇慢无比。因此笔者还是带来和Nacos下载方式一样的办法对seata进行下载。
还是直接在码云上搜索并通过git进行拉取
得到源码包后并将seata的源码包放在没有中文字,符号的目录下,之前也说过了方便我们通过maven进行打包,否则会报错。
通过cmd进入目录的命令行界面,通过mave命令进行打包。
mvn -Prelease-seata -Dmaven.test.skip=true clean install -U
等到完全BUILD SUCCESS后就能在seata/distribution/target目录下得到我们需要的zip包,以及应用在Linux系统上的压缩包。
这里笔者已经对zip的压缩包进行解压了,因此只有一个压缩包。
然后在bin目录下就能找到启动seata服务的方式了。
但是此时并不能与Nacos或其他服务注册与发现的服务进行协调使用,还需要我们对其配置进行手动修改。
seata的配置在conf文件下,打开后及能找到其yml文件配置,在原配置文件中只有一些seata的基础配置,并未对其有更详细的配置,大多需要根据个人使用情况参照模板配置去配置一些自定义的配置文件。
默认情况下seata的存储模式是file,但是我们希望能通过本地数据库来进行存储以方便我们进行查看,因此在需要在原配置文件中添加模板配置文件上的如下配置:
seata:
store:
# support: file 、 db 、 redis
mode: db
# session:
# mode: file
# lock:
# mode: file
file:
dir: sessionStore
max-branch-session-size: 16384
max-global-session-size: 512
file-write-buffer-cache-size: 16384
session-reload-read-size: 100
flush-disk-mode: async
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
user: root
password: 123456
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
这里需要手动修改存储模式,并配置数据库信息,如果像我一样是8.0+的mysql数据库直接相似修改就行了,主要需要注意数据库驱动配置和数据库url,8.0以上的mysql配置懂得都懂,然后改自己的mysql用户名密码即可。
既然配置了mysql数据库,那么同时也要在mysql数据库上建立对应的seata数据库,并导入seata准备的数据表。
seata1.6.1的版本数据表信息主要存储在如下目录。
可以看到它不仅支持mysql,还可以是oracle和postgresql,具体情况具体配置就行。
然后直接在新建的seata数据库中导入对应的sql信息即可看到表信息了。
另外需要注意的一点是:seata-1.6.1默认的mysql配置的数据库驱动是8.0以下的,因此如果我们使用8.0的配置的话,除了更改配置信息还需要检查运行jar包是否存在。
在lib文件下的jdbc目录下可以看到已经包含了8.0+的jar包了,不需要我们额外导入,如果其他版本需要但是没有的话还是要自己手动导入jar包的!
通过模板配置其实可以看到seata支持很多注册中心的配置使用。
registry:
# support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa
type: file
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: seata_GROUP
namespace:
cluster: default
username:
password:
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
eureka:
service-url: http://localhost:8761/eureka
application: default
weight: 1
redis:
server-addr: localhost:6379
db: 0
password:
cluster: default
timeout: 0
zk:
cluster: default
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username:
password:
consul:
cluster: default
server-addr: 127.0.0.1:8500
acl-token:
etcd3:
cluster: default
server-addr: http://localhost:2379
sofa:
server-addr: 127.0.0.1:9603
application: default
region: DEFAULT_ZONE
datacenter: DefaultDataCenter
cluster: default
group: seata_GROUP
address-wait-time: 3000
但是为了方便使用Alibaba全家桶,这里笔者仅以配置Nacos为例。
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
# preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: seata_GROUP
namespace:
cluster: default
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
最后保存配置信息即可。
既然我们配置的注册中心为Nacos,因此首先需要启动Nacos后然后手动启动seate服务,双击seata-server.bat即可启动成功!。
……
1:04:16.478 INFO --- [ main] o.a.coyote.http11.Http11NioProtocol : Starting ProtocolHandler ["http-nio-7091"]
21:04:16.505 INFO --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7091 (http) with context path ''
21:04:16.521 INFO --- [ main] io.seata.server.ServerApplication : Started ServerApplication in 4.956 seconds (JVM running for 5.884)
21:04:18.556 INFO --- [ main] i.s.core.rpc.netty.NettyServerBootstrap : Server started, service listen port: 8091
21:04:18.576 INFO --- [ main] com.alibaba.nacos.client.naming : initializer namespace from System Property :null
21:04:18.708 INFO --- [ main] com.alibaba.nacos.client.naming : [BEAT] adding beat: BeatInfo{port=8091, ip='192.168.109.1', weight=1.0, serviceName='seata_GROUP@@seata-server', cluster='default', metadata={}, scheduled=false, period=5000, stopped=false} to beat map.
21:04:18.710 INFO --- [ main] com.alibaba.nacos.client.naming : [REGISTER-SERVICE] public registering service seata_GROUP@@seata-server with instance: Instance{instanceId='null', ip='192.168.109.1', port=8091, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='default', serviceName='null', metadata={}}
21:04:18.716 INFO --- [ main] io.seata.server.ServerRunner : seata server started in 2192 millSeconds
最后,如果有需要seata目前(2023.1.20)最新版1.6.1的可自行在笔者资源上获取。