一、序言
本来想用一天时间更完的,后来写完才发现,我去,想的太简单了,草率了草率了;由于强迫症的原因,再加上平时要搞其他的,结果就导致我花了近3天的碎片时间梳理、配图、配文去完成这篇博文;
本来后续还有讲nacos和sentinel的,还有围绕seata分布式事务中间件,结合电商商品下单业务场景搭建的微服务模块的详细说明;现在看来,一是时间不允许了(再更下去,估计要没完没了了),二是seata的这篇博文中我已经说了太多太多了,至于大家关心的微服务架构的项目案例是如何一步步搭建和使用的,我想已经没必要再细说了,所以,大家直接克隆GitHub上最终的项目源码到本地就行了,里面的模块和注释都很清晰,没有什么难点,就是CRUD操作+Seata和Nacos配置。
下面将相关的项目地址、安装的服务、软件环境及seata使用过程中容易出现的两个问题放出来,仅供各位参考:
GitHub项目地址:
seata-spring-boot-demos
Gitee码云项目地址
seata-spring-boot-demos
附带两个文档,文档中有一些内容可能说的不对,懒得更新了(有时间再说,有问题留言吧)
附带sentinel限流的简单应用
定义限流规则并加载
使用限流规则
调用接口,测试限流规则是否生效
正常一秒刷新一次接口
正常一秒刷新两次+接口
还有一种方式是通过sentinel控制台来使用的,可以参考官网文档进行quick-start
我的Seata(fork)项目地址:
GitHub - kobeyk/seata at 1.3.0
文中涉及到的修改均已推上去了
Nacos(1.3.2)服务包下载地址:
链接:https://pan.baidu.com/s/1V8B_xBL25HzUJY6-yHljJg
提取码:pcr9
MySql8(win64)软件安装包下载地址:
链接:百度网盘 请输入提取码
提取码:itle
Navicat 15 for MySql 工具(内含pojie工具,无毒,放心使用)
链接:https://pan.baidu.com/s/1-yyx7hz_a-9jPoqZySy0hg
提取码:lb9n
Seata AT模式注意事项:
切记不要再RM业务(service)层中的方法上加@Transactional注解:
二、什么是Seata?
官网地址:https://seata.io
提炼关键词:
特性:
1、一致性(这个是痛点,解决的就是微服务分布式事务一致性的问题)
2、高性能(这个是卖点,如果用了反而拖累整个微服务架构的性能,试问谁还会用呢?)
3、易用性(这个是亮点,大家开发时引入新技术,都希望简单好用,如果技术太过于复杂,反而会提升使用门槛,吓跑用户)
事务模式
- AT (Automatic Transaction,对业务系统无侵入,省去了用的人很多编码的工作)
- TCC(Try-Confirm-Cancel,对业务系统有侵入,用的时候需要自己结合业务系统写大量的代码)
- Saga (Long Live Transaction,长事务解决方案,对业务系统有侵入,同TCC)
- XA ( X/Open 组织提出的分布式事务处理的规范,对业务系统无侵入,只需要DB实现了XA协议即可)
文章推荐:带你读透 SEATA 的 AT 模式
这篇文章讲的很好,很适合结合着Seata官方文档细细品读,下面放几张文中的关键图

- 每个应用服务对应一个本地数据库,即database per service;
- 每个应用服务之间互相协作,调用接口;
- 每个应用服务执行自己的SQL脚本;
- 每个应用服务执行自己的SQL语句时,是基于本地事务的;
- 每个应用服务对应的本地库中,都存在一个UNDO Log(回滚日志记录);
- 每个应用服务实现自己的事务提交,如果有一个事务提交失败,则全局回滚;
- 发生全局事务回滚时,则每个分支(本地)事务按照UNDO log的内容对数据集进行反向SQL补偿

搞懂上图,需要知道Seata的三个角色概念:
- TC(Trasaction Coordinator,事务协调器)
- TM(Transaction Manager,事务管理器)
- RM (Resource Manager,资源管理器)
其中TC对应的应用服务就是seata-server
TC角色主要用来和TM和RM进行“交流”的,通过RPC(Netty)+服务注册发现(Nacos等),实现与各个微服务模块之间的通信
其中TM对应的应用服务就是开启了@GlobalTransactional注解的业务方法所在的微服务模块,即seata-business-server
TM角色主要定义了全局事务的边界,用来向TC开启一个全局事务,拿到全局事务唯一的XID,并在调用的微服务之间的上下文中进行传递,同时,该服务还掌握着是否全局提交还是全局回滚的决议权!




TM客户端调用过程结束,接下我们来看下TC服务端的






executeUpdate()方法执行完后,我们来看下TC服务对应的全局表中的记录
全局提交的条件是:各个微服务的分支事务均提交成功;
全局回滚的条件是:只要有一个微服务的分支事务提交失败,就会触发TM发起整个全局事务的回滚!然后由TC基于XID分别异步调用各个RM,并对RM的数据源进行代理,通过RM数据源的代理,一一基于各个RM的undo_log表进行反向SQL的补偿,以达到整个微服务架构的数据结果的一致性;
RM角色对应的应用服务就是在业务上存在SQL语句执行的微服务模块,如order-server
RM角色主要用来向TC注册分支事务(保存到seata库对应的branch_table表中),并上报分支事务提交和回滚的状态给TC,TC根据分支事务提交和回滚的状态来驱动TM最终进行全局的提交或全局的回滚;
(关于RM和TC交互的代码调试就不放了,下面放几张和分支事务有关的截图吧,有问题的可以留言)
库存服务(RM)对应的初始化商品的库存记录如下:
TC分支事务表记录如下:
库存服务(RM)在TM开启全局事务后,执行减库存操作但事务未提交时的分支事务记录如下:
库存服务(RM)在TM开启全局事务后,执行减库存操作,且在事务未提交时的回滚日志记录如下:
每个分支事务都对应一条undo_log记录,我们导出减库存XID对应的的log后,查看其内容如下:
最后执行成功后,相应的undo_log记录会被删除,刷新库存的undo_log表记录(空)如下:
最终,我们可以看到,水杯的库存量为:
如果TM发起全局提交,则TC会分别调用各个微服务,并对各个RM中的undo_log表中的XID记录进行异步删除;
如果TM发起全局回滚,则TC会分别调用各个微服务,并对各个RM中的undo_log表中的XID对应的记录进行反向SQL补偿,以恢复事务提交失败前的数据;
三、Seata源码编译
Seata项目开源地址:GitHub - seata/seata: Seata is an easy-to-use, high-performance, open source distributed transaction solution.
登录GitHub,将其fork到自己的仓库中
有两种方式可以对seata1.3.0源码进行编译:
-
Clone到本地,导入到idea中进行编译
-
下载源码包,解压到本地,导入到idea中进行编译
最新seata1.3.0封版时间在:2020年7月16号(离本篇博客写的时候,时间也就差了3个月)
第一种方式:
这种方式拉代码有些慢,稍作等待即可
clone完成后,记得切换下分支(修改的代码后面提交都是基于这个最新分支的):
(1)使用【Module from Existing Sources】构建Maven项目
(2)使用下面的方式,逐一添加pom进行Maven构建
大致简单地来说一下seata各个模块的功能:
由于目前只导入了all模块中的pom(seata项目中所有模块依赖的jar)和父pom,其他子pom还未导入,因此,接下来就是一个个点,将各个模块的pom添加进来了,如将config配置模块中涉及的所有pom文件进行导入如下:
其他模块导入不在一一说明,最后全部导入完,Maven也完成相应的jar包下载后,且IDEA编译无误后,即可使用seata服务了
插曲:这里构建编译项目的时候,会报下面这个错误
Windows下配置protobuf的文章可以参考我之前的博文:
Google Protocol Buffer -- Windows下Python的应用
不想编译转换proto文件到java类的,直接注释掉这个Grpc测试类再次编译即可
第二种方式(同样,也是要注意切换分支的):
压缩包2.3M可以看出整个seata项目是很小的
解压后的目录结构和导入IDEA中编译的方式同第一种一样,这里就不再重复说了!
四、Seata服务参数配置
上一步源码的编译工作完成后,接下来我们看下关于Seata的相关配置
(1)Seata库对应的数据库脚本文件(以mysql为例)
二话不说,先在mysql中建一个seata库,然后将上述的脚本执行后,效果如下:
(2)Seata分布式事务解决方案,AT事务模式下,客户端相关的SQL脚本(以mysql为例)
这个在构建微服务模块的时候,把该脚本执行在各个RM对应的数据库中即可
(3)Seata服务参数配置
这里直接贴出来,修改后的config.txt如下(只放修改的参数key和value,没修改的不放):
service.vgroupMapping.seata_example_tx_group=default
store.mode=db
store.db.datasource=hikari
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&autoReconnect=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
store.db.user=root
store.db.password=root123
注意四个地方:
-
seata_example_tx_group要和客户端的配置保持一致
-
存储模式由原来的file-->db
-
只有当存储模式改为db后,数据库连接池配置才起作用,将原来的druid-->hikari
-
使用mysql8+库,注意驱动包名一定要改过来,将原来com.mysql.jdbc.Driver-->com.mysql.cj.jdbc.Driver
一旦将mysql驱动包升级到8后(由于seata1.3.0中使用的是mysql5.+)
启动seata服务时,会报如下的错误(前提是上述seata参数信息被成功推送到了配置中心,如nacos;后面再说到seata启动的时候,会讲一下,这种情况是如何解决的):
Exception in thread "main" io.seata.common.loader.EnhancedServiceNotFoundException:
not found service provider for : io.seata.server.session.SessionManager caused by
java.lang.IllegalStateException: Extension instance(definition:
io.seata.common.loader.ExtensionDefinition@199a697f, class: interface
io.seata.server.session.SessionManager) could not be instantiated: not found service
provider for : io.seata.core.store.db.DataSourceProvider caused by
java.lang.IllegalStateException: Extension instance(definition:
io.seata.common.loader.ExtensionDefinition@c5a49597, class: interface
io.seata.core.store.db.DataSourceProvider) could not be instantiated: Failed to load
driver class com.mysql.cj.jdbc.Driver in either of HikariConfig class loader or Thread
context classloader
(4)Seata服务(TC)配置服务注册+发现和配置中心
4.1 服务注册+发现中心,改成Nacos
关于Seata和Nacos的配置,在GitHub项目中也有相应的doc说明:
Nacos(1.3.2)服务包下载地址
百度网盘:百度网盘 请输入提取码
提取码:pcr9
4.2 配置中心,改成Nacos
(5)将Seata配置文件(config.txt)中的内容推送到Nacos配置中心
定位sh脚本文件如下:
在当前目录空白处右键打开git bash终端,执行命令如下:
sh nacos-config.sh -h 127.0.0.1
总过79个数据配置项被成功的推到了nacos的配置中心,失败0个
我们看下nacos库中的配置表,查询如下:
至此,整个seata的相关配置就全部完成了,接下来,就是启动seata服务了
五、Seata服务启动
源码也编译了,配置也配完了,接下来,我们启动下seata服务:
我去,启动居然报错了???
仔细查看报错的信息,发下内容如下:
Caused by: java.lang.RuntimeException: Failed to load driver class com.mysql.cj.jdbc.Driver
in either of HikariConfig class loader or Thread context classloader
at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:486)
很明显,基于mysql8的最新驱动名称“com.mysql.cj.jdbc.Driver”无法被加载,前面seata参数配置的时候,说过,seata默认mysql连接驱动是5.+的,你现在给他mysql8的驱动包名,让他去找,当然是找不到的,怎么办,不要慌,我们把源码(server)中的mysql依赖包换成8不就行了吗,具体操作如下:
(1)首先,看下父pom中有没有关于mysql8的版本定义
(1)其次,我们修改server模块中的pom,将mysql驱动包的版本更换下
刷新之后,我们再看下
mysql驱动版本换过来后,我们再次启动server,看下是否成功(卖个关子):
居然又TM的失败了,莫慌,我们看下具体错误原因是什么:
Caused by: java.sql.SQLException: The server time zone value '�й���ʱ��' is
unrecognized or represents more than one time zone. You must configure either the server or
JDBC driver (via the 'serverTimezone' configuration property) to use a more specifc time
zone value if you want to utilize time zone support.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-
connector-java-8.0.19.jar:8.0.19]
错误原因很明显,必须配置driver的服务端时区(MySQL JDBC 6.0 版本以上必须配置此参数),这就很奇怪了,我不是在config.txt中指定了吗??? 纳尼!!!
而且往nacos配置中心推送也成功了,我曾一度怀疑config.txt没有保存或者nacos没有刷新到最新内容:
实操证明,两者都不是:
那究竟是因为什么呢?试过来试过去,最终发现"&"符号很可疑,而且&符号及其后面的内容都被截断了??? 解铃还须系铃人,我们看下nacos-config.sh脚本是怎么写的
很明显,这是一个curl命令,向nacos提供的config配置接口发送seata的数据配置项
话又说回来,在url传值中,有时候我们需要加很多参数,但是有的时候用url传递多个参数的时候&后面的参数被自动截取了,后面的参数显示不出来了,这不是我们所希望的
怎么办呢?好办,把&用asc值表示,即%26
再次修改store.db.url的内容如下
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?
useUnicode=true%26autoReconnect=true%26characterEncoding=utf-
8%26useSSL=false%26allowMultiQueries=true%26serverTimezone=UTC