Seata 1.6.x带来了什么?
- AT模式语法及特性增强
首先在1.6上,我们针对核心独创的AT模式进一步的进行了增强,支持了更多的常用语法特性,如mysql下的 update join,oracle及pgsql中的多主键。
请求响应通信模型优化
而在整体的通信架构上,进一步优化了网络通信模型,将batch及pipeline特性运用到极致,进一步提升 Seata Server到Client的整体吞吐量。
- 全局锁支持策略控制
全局锁方面增加了更加贴近业务使用场景的乐观/悲观获取锁的配置项,根据不同的业务场景选择不同的 锁策略,以上三点将会在后续的AT模式增强篇章中详细介绍。
- 支持JDK17&Spring Boot 3.0
在技术探索及先进性上,我们早早的在1.5.x上便支持了jdk17,而在1.6.1中社区更进一步,率先支持了 spring boot3并且是完全兼容springboot2的形式,而非单独分支形式支持,这两个重大feature将使业务 同学选型底层内核版本时更加从容自由。
- 支持多注册中心服务暴露
服务暴露发现模型上,我们增加了对多个注册中心同时暴露Seata-Server的feature,这将在后续篇章中与 大家进一步分享 。
- ......
除此之外,Seata 1.6.x有更多的optimize和bugfix,这里不再一一展开介绍,欢迎大家尝鲜Seata最新 release版本,有任何问题欢迎在github中沟通及讨论。
AT模式增强
AT模式语法及特性支持
AT模式原理回顾
首先我们先来回忆下AT模式的流程和原理,以便更容易理解相关的feature,可以看到如图所示,应用启动 时,Seata会自动把用户的DataSource代理,还对JDBC操作熟悉的用户其实对DataSource还是比较熟悉 的,拿到了DataSource,就等于掌握了数据源连接,也就能在背后做些”小动作”,此时该对用户也是无感知无 入侵。
之后业务有请求进来,执行业务sql时,Seata会解析用户的sql,提取出表元数据,生成前镜像,再通过执行业 务sql,保存执行sql后的后镜像(至于后镜像的用户之后会说到),再生成全局锁,再注册分支是携带到Seata- Server 也就是TC端。
update join原理剖析
首先,我们根据回顾的AT原理可以知晓,当一个dml语句执行的时候,其语句会被解析后会生成对应的前镜 像查询语句及后镜像查询语句,以update join为例.熟悉sql的同学应该道,update join 会连接多张表, 且修改的行可能是多张表都涉及,所以我们第一部要提取join的表数量,得到join的数量,为涉及到修改表内 行数据的表,进行构建select语句查询行在改动之前的数据,如下:
update table1 inner join table2 on table1.name = table2.name set table1.money = money+100 where table2.name=“张三”
可以发现涉及了2张表,1个表内的多条数据变更,所以我们要对name=张三的 table1的表进行前镜像生成 。
Select table1.pk,table1.money from table1 inner join table2 on table1.name = table2.name where table2.name=“张三” for update
如上,我们将join的语句和where的条件进行了提取,如果还牵涉到另一个表如table2,那么也会生成一条 table查询name=张三的语句进行查询 。
通过以上的前镜像sql后,我们便能构建前镜像的信息,而后镜像的话就会更加简单,以前镜像sql为例,那么 后镜像只需将前镜像查出来的主键作为条件,也避免了再次对非索引列进行检索,提升了效率,sql如下:
Select table1.pk,table1.money from table1 inner join table2 on table1.name = table2. name where table1.pk=xxx
Support Multi-PK 原理
接下来我们聊聊如何对多主键进行了支持。
支持多主键无非最终目的就是为了拿到主键,而主键对于seata而言是不可或缺的,他是定位一条数据被修 改的唯一索引,如果这条数据没有主键,那么也就代表后续二阶段回滚可能无法定位准确数据,而无法回滚. 所以带着这个目的我们来分析一下上图对自增主键和非自增多主键的支持 。
INSERT INTO TABLE_NAME (pk1, pk2, column3,...columnN) VALUES (value1, value2, value3,...valueN);
首先第一种sql会在插入列中直接指定多个主键的值,所以seata可以轻松的可以通过插入列的value读取 到多个主键,由于seata会获取表的元数据,所以像主键到底是哪个字段就都一清二楚了。
INSERT INTO TABLE_NAME (pk1, column2,...columnN) VALUES (value1, value2,...valueN);
至于第二种,seata缓存着表的元数据,也就发现从插入数据中只能得到一个pk的值,所以这个时候就要调 driver的统一api,getGeneratedkeys,由驱动层面将主键的值返回给seata,由于两种情况下都能获取到主 键,所以对后续AT模式的二阶段回滚也就不成问题了。
LockStrategyMode 原理
现在我们介绍一个新的概念,在seata1.6中我们引入了lockStrategyMode概念,目前有OPTIMISTIC和 PESSIMISTIC两种模式,分别对应乐观和悲观.首先在以往的Seata获取全局锁的流程如下 。
当注册分支时,会将前镜像/插入数据时获取到的主键值作为全局锁带到TC,而此时就会通过这个全局锁信 息去查询数据库,当这个锁在数据库没有记录时会去插入这个锁,看似非常简单的流程,却存在着一定问题, 那就是第二步画了虚线的地方,可能心细的同学会有疑问,为什么要查询锁有没有记录呢,数据库有唯一索引,redis提供setnx,直接无脑尝试插入即可呗 。
- 这个查询是为了保证事务的全部流程的信息透明和故障排查时的便捷性,比如我有个锁争抢不到了,作 为业务的同学肯定得去排查这个锁抢不到到底是被谁拿走了呢?比如一个商品在被大量秒杀的时候,商家 正在补货,由于有大量的并发在秒杀同一个商品,很有可能商家补货的请求会失败,这个时候开发同学肯定 要去看这个锁被哪个事务持有,再通过持有这个锁的xid去查看业务中哪个接口涉及了这个锁的争取,一查 查到是扣库存的接口,那如果你是业务同学,是不是就可以通过锁竞争周期的配置去调优,将增加库存的接 口锁竞争周期调长增加成功率呢?
- 其二呢,如A服务调B服务,更新了B服务对应数据库中id为1的数据,B响应成功给A,A进行了一段业务处 理,再次发了一个请求修改id为1的数据,这种场景我们叫做锁重入,而这个情况在xa事务中是无法支持的, 因为xa的事务使用的connection在一阶段进行了prepare后,就无法再使用这个connection或者这个xa 事务去修改涉及的数据,如果有这种场景,那么在使用xa模式一定会变成一个死锁,而在AT模式上,Seata使 用了先查询锁持有者,再插入锁的方式,很轻松的就可以在同一个xid中,多次对一个数据进行修改,因为这 个全局事务只要持有这个锁,那么无论是多少次的修改,最终回滚一定是会按照修改数据顺序回滚完毕。
但是基于以上的2点,很多同学应该会发现,90%以上的业务场景中不存在锁重入,而锁重入又会额外造成磁 盘和网络io开销,导致应用一旦使用了seata at模式,吞吐量进一步下降,所以在1.6上我们推出了乐观和悲 观的锁策略,悲观情况下我们认为锁一定是被其它人持有,所以逻辑是先查后插,而乐观下,对分支事务而言 锁就一定是可以被持有,所以减少了查询的一次开销,提升了整体的吞吐.但由于第一点事务链路异常排查, 追踪等问题,Seata只会在第一次竞争锁时放弃查询锁,当重试获取全局锁>1时便会自动转为悲观模式,这 样既能在特定场景有一定提升吞吐量的优势,又能在排查问题上不造成额外的排查成本。
请求响应模型
1. 合并请求并行处理(Pipeline+Parallel)
2. 批量响应(batch response)
1.5之前的请求响应模型
首先我们来回顾下1.5之前的网络模式,如图所示T1,T2,T3是3个线程,同时并发在同一个Client中时,T1 T2 T3各自都会将各自的rpcmessage放入本地的batch队列中,并futureget等待服务端的响应,而此时RM侧 会有一个批量merge线程,将同一毫秒并发内的请求合并发到TC,而当TC侧接受到请求后,将会按顺序将t1 t2 t3的消息进行处理,再一并下发。
我们根据上述的信息其实可以发现1.5之前的网络通信模型是存在一定问题的。
- 队头阻塞,批量到达TC端的请求会被串行处理,导致效率都不如单个请求(单个独立请求的线程池核心 线程数为50),假设两个请求,1个请求竞争10000个全局锁,而另一个请求至竞争一个全局锁,如果两者 被合并,那么对后者而言将是极其不公平和拖慢效率的。
- 请求需要有序处理。
- 响应需要等待其它请求处理完毕。
而在发现这些问题后,社区做了及时的调整和重构,我们先来看下每一条request/response长什么样。
批量消息结构
可以看到每一条消息都拥有id,messagetype,codec,compressor,headmap,body,而合并请求的话会将 同一钟序列化类型和压缩方式的消息合并在一起(另外创建一个mergerequest,将其body里放入其它请 求的msg),这样在TC侧解压缩和反序列化就会非常容易。
请求并行处理
对于队头阻塞问题,这就像http1.1推出的pipeline一样,批量发生到Server端后要一并响应,处理时长较高 的请求就会影响其它请求,所以基本上这个http1.1的pipeline都是无用武之地的。
而在seata1.5后,将多个合并的请求到达Server侧时会通过CompletableFuture来提交多个request的处 理到forkjoinpool中,这样既避免了额外的线程池开销(seata的业务线程池外),且在线程数上也是以cpu核 数为准,当所有请求都处理完毕后,再将多个response一并响应回Client 。
批量响应
其次在第二,第三点中的问题,社区实现了乱序批量响应的功能,无需有序的等待请求执行完毕,只要某个请 求的处理完毕后,就可以与其它同时完成处理的请求(注意,不一定是t1,t2,t3,可能是其它的线程)一并返回 响应内容,具体示意可看下图 。
当t1,t2,t3的请求被合并至TC时,此时TC将并行处理1,2,3的请求,而其中1,2,3请求的response会与客户端一 致,等待最多1ms的时间,这这个response是被交到一个batch线程中异步等待和响应,如果1ms到时还没 有一起可返回Client的响应,便会直接响应回Client。
以上图所示,在1,2,3的response等待1ms时,假设有另外的t4线程的请求到达TC并也完成了处理,此时1,2,3 线程的response正在等待0.5ms,便会被t4线程的response唤醒,此时batch线程由于投递了新的 response被激活后,便将此刻所有相同压缩方式和序列化的response合并为一个response批量响应,此 处理无顺序关系,1,2,3可以任意一个请求处理完成后便丢到batch线程队列中即可,而Client收到响应时会 从rpcmsg中的id匹配response中的id,找到对应的Client的future对象进行setresult,此时Client future.get的线程便会得到响应,再继续进行Client侧相应的处理。
Seata 服务发现模型
如图所示,Seata的Client和Server的服务发现模型基本上与传统的rpc框架所使用的模型一致,TC暴露服务 地址至注册中心,TM和RM从注册中心发现TC地址,然后进行一个连接,但是在这种模型中会存在特殊的问 题,我们接下来看 。
现存服务发现模型缺陷
我们以上图为例,假设某公司在微服务框架中选择了spring-cloud+dubbo,或存在dubbo迁移到spring- cloud或相反的情况下,可能就会存在dubbo的服务使用http调用springcloud的服务,互相调用,而我们知 道springcloud的服务大多数会使用eureka或nacos,dubbo大多会使用zookeeper和nacos,那么如果两 者注册中心不是同一个,那么在使用上就会出现,dubbo这块的应用通过zookeeper发现seata- Server,spring-cloud通过eureka发现seata-Server,由于seata-Server不存在同时对多个注册中心进行服 务暴露,所以用户很可能为了两边的事务能串起来,便会搭建2个集群,而且这两个集群仅仅是将事务串起 来,但并不能在二阶段下发的时候顺利下发,因为其中一个Client节点只与对应注册中心的TC阶段通信,那么 就会导致下发失败,二阶段的执行滞后.所以让我们来总结梳理下存在的问题。
- 维护多套集群,人力及资源成本略高。
- 二阶段下发存在滞后,导致AT以外模式的事务被动降级成最终一致性。
而最新的服务发现模型变能解决以上问题。
改进后的服务注册模型
在1.6上我们支持了多注册中心的服务暴露,这就轻松的将上述问题消灭,从Seata-Server侧可一次性配置 多个注册中心,并配置好多个注册中心相关配置后,启动Seata-Server便会注册到对应的注册中心中,这就 使相关企业及用户在面对微服务架构选型,迁移,混部混用等场景上不再因为Seata-Server不支持多个注册 中心暴露所掣肘,降低用户对多个集群的维护成本.
总结
Update join&Multi pk 支持就是将Seata独有的AT模式特性进一步的扩展,将更多的sql特性融合在Seata 分布式事务中,乐观悲观的全局双策略兼顾了多种业务场景,提升性能 。
全新设计的网络通信模型使请求不在堵塞,将批量和多线程合理应用,极大的利用现代化高性能多核心的 服务器资源提升Seata-Server吞吐量 。
多注册中心服务注册发现助力企业降本增效,赋能业务多种微服务架构选型 而业务在基于 Seata以上特性,应用架构又可以根据实际场景灵活选择。