背景
随着业务的快速发展、业务复杂度越来越高,传统单体应用逐渐暴露出了一些问题,例如开发效率低、可维护性差、架构扩展性差、部署不灵活、健壮性差等等。而微服务架构是将单个服务拆分成一系列小服务,且这些小服务都拥有独立的进程,彼此独立,很好地解决了传统单体应用的上述问题,但是在微服务架构下如何保证事务的一致性呢?
1、事务的介绍
1.1 事务
1.1.1 事务的产生
数据库中的数据是共享资源,因此数据库系统通常要支持多个用户的或不同应用程序的访问,并且各个访问进程都是独立执行的,这样就有可能出现并发存取数据的现象,这里有点类似Java开发中的多线程安全问题(解决共享变量安全存取问题),如果不采取一定措施会出现数据异常的情况。列举一个简单的经典案例:比如用户用银行卡的钱还京东白条,银行卡扣款成功了,但是白条因为网络或者系统问题没有还款成功,就会出大问题,这时候我们就需要使用事务。
1.1.2 事务的概念
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
1.1.3 事务的特性
事务的四大特征主要是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),这四大特征大家或多或少都听说过,这里我做下简单介绍。
(1)原子性(Atomicity):事务内的操作要么全部成功,要么全部失败,不会在中间的某个环节结束。假如所有的操作都成功了,那么事务是成功的,只要其中任何一个操作失败,那么事务会进行回滚,回滚到操作最初的状态。
begin transaction;
update activity_acount set money = money-100 where name = '小明';
update activity_acount set money = money+100 where name = '小红';
commit transaction;
(2)一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。换一种说法是数据按照预期生效,数据的状态是预期的状态。比如数据库在一个事务执行之前和执行之后,都必须处于一致性状态,如果事务执行失败,那么需要自动回滚到原始状态,也就是事务一旦提交,其他事务查看到的结果一致,事务一旦回滚,其他事务也只能看到回滚前的状态。
举个通俗一点的例子:小明给小红转账100元,转账前和转账后数据是正确的状态,这叫一致性,如果小红没有收到100元或者收到金额少于100元,这就出现数据错误,就没有达到一致性。
(3)隔离性(Isolation):在并发环境中,不同事务同事修改相同的数据时,一个未完成的事务不会影响另外一个未完成的事务。
例如当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
(4)持久性(Durability):事务一旦提交,其修改的数据将永远保存到数据库中,改变是永久性,即使接下来数据库发生故障也不应对其有任何影响。
通俗一点例子:A卡里有2000块钱,当A从卡里取出500,在不考虑外界因素干扰的情况下,那么A的卡里只能剩1500。不存在取了500块钱后,卡里一会剩1400,一会剩1500,一会剩1600的情况。
1.1.4 Mysql隔离级别
如果不考虑事务隔离性产生问题:脏读、不可重复读和幻读。
Mysql隔离级别分为4种:Read Uncommitted(读取未提交的)、Read Committed(读取提交的)、Repeatable Red(可重复读)、Serializaable(串行化)
(1)Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。
(2)在Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
(3)在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了,就好象发生了幻觉一样。
(4)Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。
如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read。
1.1.5 启动事务
在说明启动事务之前,首先大家先想一下事务的传播行为,事务传播行为用于解决两个被事务管理的方法互相调用问题。实际开发中将事务在service控制,如以下方法调用存在传播行为,如果serviceB也会产生一个代理对象,同时也会进行事务管理,执行serviceA和serviceB分别开启事务,上边的serviceA中funA方法内容不处于一个事务中了。
class serviceA{
//此方法进行事务控制
funA(){
//在此方法中操作多个dao的操作,处于一个事务中
userDao