Seata分布式锁是分布式系统中的一种锁实现,主要用于控制不同进程共同访问共享资源。在分布式系统中,不同机器或主机之间可能共享某些临界资源,为了避免彼此之间的干扰和保证数据一致性,需要使用互斥机制来防止同时访问。Seata分布式锁的作用就是提供这种互斥机制。
Seata分布式锁具有以下特点:
1.互斥性:在任何时刻,对于同一项数据,只有一个客户端可以获取到分布式锁。
2.高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署。
3.防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁。
4.独占性:加锁和解锁必须由同一个客户端进行,也就是锁的持有者才可以释放锁,不能出现非持有者解锁的情况。
Seata分布式锁的解决方案主要有三种:关系型数据库、Redis和ZooKeeper。其中,Redis的分布式锁可以通过SETNX(SET if Not eXists)命令来实现,即先用SETNX来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。
开始TM向TC请求开启全局事务,TM调用每个RM分支,每个RM向TC注册自己分支事物,RM开始执行业务SQL,执行完成后,不提交事务并向TC报告事务的状态,业务执行完成后,TM向TC表明各个RM分支已经结束,此时TC检查分支事物的状态来决定是回滚还是进行提交事务。
优点:
(1)能够保证强一致性,满足ACID原则
(2)常用的数据库都支持,如mysql,实现比较简单且没有代码的侵入
缺点:
(1)因为第一阶段需要锁定资源等待其他分支执行完成,所以性能比较差
(2)依赖关系型数据库实现事物
开始TM向TC请求开启全局事务,TM调用每个RM分支,每个RM向TC注册自己分支事物,RM开始执行业务SQL,执行完成后并提交事务,向TC报告自己的事物状态,在提交事务时会记录更新前后的快照到undo log表当中,TM发现事务完成后,向TC提交或回滚事务的请求,此时TC回去检查每个分支事务的情况,如果正常则删除undo log对应的数据,否则会通过undo log进行回滚。
AT模型的脏写问题
并发模式下,如果事务一先获取锁,将id为1的money由100减为了90,此时事务二进来,但获取不到锁,所以等待,事务一执行完毕后释放数据库锁,事务二此时获取到了锁,又将id为1的money由90减为80,此时事务一需要回滚,但是事务二正在获取锁,所以等待事务二释放锁后,事务一拿到锁后就根据undo log中的快照进行回滚,id为1的money此时就变为了原来的100,从而出现了脏写。
解决方法
情况一:
在事务一执行业务SQL时,在提交事务前先获取全局锁,即将当前事务id和当前table(表)和当前行id进行记录,在执行完之后,事务二来修改,但在获取全局锁时发现已经有其他事务获取到了锁,所以会进行重试获取全局锁,此时如果事务一需要回滚,在获取数据库锁的时候就会失败,因为此时事务二占有数据库锁,但不会造成死锁,因为事务二在重试获取全局锁只有30次,并且每次10毫秒,所以当事务二获取全局锁失败后,就会进行事务回滚,此时事务一就占有了锁并进行回滚,所以此时回滚就不会照成脏写,因为事务二并没有成功更新。
情况二:
如果此时来更新此数据的事务并没有交给seata管理,所以在修改数据时,并不会去检查是否能获取全局锁,所以此时更新会成功,但解决方法是,在事务一对数据进行更新时会记录更新前数据和更新后的数据,如果事务一进行回滚时,会去将当前数据和事务一修改后的快照进行比较,如果相同则正常回滚,如果不同则会记录异常,发送警告,并进行人工介入,而不会进行正常的回滚。
优点:
(1)一阶段完成直接提交事务,释放数据库资源,性能比较好
(2)利用全局锁实现事务隔离
(3)没有代码侵入,框架自动完成回滚和提交
缺点:
(1)两阶段之间属于软状态,属于最终一致性
(2)框架的快照功能会影响性能,但比XA模式要好的多
通过将扣减的金额进行冻结(记录到一个表当中),如果进入到提交阶段,则删除冻结的余额,如果进入到回滚阶段则将冻结的金额重写添加到原来的数据当中。
整体流程图
TM向TC开启全局事务请求,然后调用各分支事务,RM注册分支事务,通过在Try中进行资源预留并提交事务,RM报告事务状态给TC,事务完成后,TM向TC告知事务执行完毕,TC就会去检查各分支事务的状态,来决定是进行confirm还是cancel,confirm删除预留资源,否则执行cancel删除预留资源并恢复可用资源。
TCC模式的空回滚和业务悬挂问题
允许空回滚的办法是在执行cancel时去判断try是否已经执行,如果已经执行则进行空回滚,而避免业务悬挂的办法是在执行try时去判断是否执行过了cancel,如果执行过了就不执行业务,就避免了悬挂。(0: try、1: confirm、2:cancel)
Springboot整合Seata的TCC模式
说明:获取全局事务id,可以使用Seata的RootContext.getXID()来获取
优点:
(1)一阶段完成直接提交事务,释放数据库资源,性能好。
(2)相比AT模型,无需生成快照,无需使用全局锁,性能最强。
(3)不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库。
缺点:
(1)有代码侵入,需要人为编写try、confirm、cancel接口,太麻烦
(2)软状态,事务是最终一致性
(3)需要考虑confirm和cancel的失败情况,做好幂等性处理