浅析Java中的事务,从ACID到BASE

本文收录在javaskill.cn中,内有完整的JAVA知识地图,欢迎访问

1. 数据库中的事务

Java中的事务管理,最终都是体现在数据上,因此,了解数据库对事务的处理是非常必要的

1.1 ACID

Atomicity、Consistency、Isolation、Durability
原子性、一致性、隔离性、持久性

  1. 原子性
    事务中的操作必须全部成功或全部失败
  2. 一致性
    事务必须使数据库从一个一致性状态转变到另一个一致性状态
    栗子:A有100元,B有100元,AB共200元,无论A和B怎么转账(不考虑手续费),A和B一共有200元
  3. 隔离性
    事物之间不互相干扰,存在多种隔离级别
  4. 持久性
    事务一旦提交,对数据的改变就是永久性的,即使遇到故障,也不会丢失提交事务的操作

1.2 脏读、不可重复读和幻读

  1. 脏读
    指一个事务读取了另一个未提交的事务中的数据
  2. 不可重复读
    指一个事务中,对一个值多次读取返回的值不一致(读取了其他已提交事务的数据)
  3. 幻读
    栗子: T1把表A中的某个字段从1改为2,T2对表A进行了插入并提交,并且该字段为1,T1修改提交后,发现还有一条数据没有修改(注意和不可重复读的区别)

1.3 数据库的四种隔离级别

  1. Serializable
    避免脏读、不可重复读、幻读
  2. Repeatable Read
    避免脏读、不可重复读
  3. Read Committed
    避免脏读
  4. Read Uncommitted
    毛都避免不了

1.4 如何保证持久性和一致性

持久性和一致性的概念清楚了,那么数据库如何保证这一点呢?如果事务提交后,主机突然断电了呢?
概念很简单,数据库操作事务的时候,会记下这个事务的redo操作日志,在真正操作数据库之前,会把日志写入磁盘,发生异常情况后,会根据当前数据的情况进行undo或者redo,以此保证一致性和持久性,这里不再深究

2. Spring中的事务

上升到Java中,最常使用的应该是Spring中的事务操作。不管是声明式事务还是手动开启事务,在Java中,所关注的不再是数据层面的一致性(数据库已经帮我们保证了),而是事务之间的关系。
通常,事务边界都是设定在Service层,如果一个Service层中的事务方法,调用另一个事务方法,事务是怎样传播的呢?

事务传播

Spring中共有7种不同的传播行为,以被调用方法的视角,可以把它们分为两类

  1. 被调用方支持事务
    1.1 PROPAGATION_REQUIRED 必须要有事务,有就加入,无则创建
    1.2 PROPAGATION_SUPPORTS 支持当前事务,有就加入,没有拉倒
    1.3 PROPAGATION_MANDATORY 使用当前事务,有就加入,没有报错
    1.4 PROPAGATION_REQUIRES_NEW 使用新事务,外层事务挂起,独立提交回滚
    1.5 PROPAGATION_NESTED 使用嵌套事务,独立回滚(出错回滚自身),不独立提交,没有事务则创建
  2. 被调用方不支持事务
    2.1 PROPAGATION_NOT_SUPPORTED 不使用事务,有就挂起,没有拉倒
    2.2 PROPAGATION_NEVER 坚决不使用事务,有就报错

需要注意1.4和1.5的区别,关键在于是否独立提交和回滚

3. 分布式事务

首先要明确的一点,在分布式事务中,ACID已经不适用了。在集群环境下,想要保证ACID几乎是不可能的任务,即使能够达到,效率也是非常低下的。所以,在集群环境下,分布式事务一般追求的是最终一致性。

3.1 BASE理论

Basically Available 基本可用
Soft state 软状态
Eventually consistent 最终一致
分布式系统中,可用性往往比一致性更重要(想象一下,支付宝为了保证强一致性,即A转100给B,A账户马上扣100,B账户马上加100,但是三天两头无服务),BASE理论就是在可用性和一致性中做出了权衡,核心思想是,我们无法做到强一致性,但是每个应用可以结合自身的特点,用适当的方式来达到最终一致性(A支付100元给B,B可能马上收到,也可能5分钟后收到,但是最终一定会收到)。

3.2 TCC补偿事务

TCC的核心是采用了补偿机制,针对每个操作,都要有一个与之对应的补偿(回滚)操作,分为三个阶段:

  1. Try 预留业务资源
    尝试执行业务
    完成所有业务检查
    预留必须业务资源
  2. Confirm 确认执行业务操作,需幂等
    真正执行业务
    不做业务检查
    只使用try阶段预留的资源
  3. Cancel 取消执行业务操作,需幂等
    释放try阶段预留的资源

和数据库中的事务操作进行对比,可以找到类似之处,锁定行->操作行->出错回滚

举个实际的例子来加深理解
假设有A、B、C三个账户,A和B向C支付100元,A支付40元,B支付60元,需要在一个事务中完成

  • try
    检测A、B、C三个账户的状态,是否允许转账
    检测A账户是否有40元,有则冻结
    检测B账户是否有60元,有则冻结
  • confirm
    扣除A、B的冻结金额,增加C账户的金额,不做任何业务检查
  • cancel
    恢复A或B的冻结金额

如果在try阶段发现,A的账户冻结40元成功,B冻结失败,则调用A的cancel方法,恢复A的冻结金额

3.3 本地消息表

这种思路来源于ebay


  • 在本地新建消息表
  • 消息和业务在同一个事务里提交
  • 通过MQ通知消费方
  • 消费方处理消息后通知修改消息状态
  • 消息发送失败,重试
  • 定时扫描未处理的消息进行重发
  • 消费方业务失败,调用生产方补偿方法进行回滚

这种方式遵循BASE理论,保证的是最终一致性,在实际使用中,比TCC更好处理,少写很多代码。需要注意的是,消息处理需要幂等

3.4 MQ事务消息

阿里巴巴的Rocket MQ支持事务消息,Rabbit MQ和Kafka都不支持

3.3中之所以要使用本地消息表,因为更新数据库和发送MQ消息不是一个原子操作,无论谁先谁后,都会有问题

  • 先更新DB,发送消息失败了,怎么办?
  • 先发送消息,DB更新失败了,消息已经发了,怎么办?

3.3中采用了本地消息表,通过消息表中的消息状态来控制重发,以达到最终一致的目的
事务消息模拟了这种操作,只不过把维护消息状态的过程,从数据库转移到了MQ中间件

具体来说,就是把消息发送,分解成两个阶段,准备和确认
具体到业务中,分解成了三步操作

  1. 发送Prepared消息
  2. 更新数据库
  3. 根据2的结果,发送Confirm或Cancel,确认或取消消息

取消的消息会被丢弃,确认后的消息才会真正的发送给消费者

如果第三步失败了,RocketMQ会主动(默认1分钟)询问发送方,喂?这条消息还要吗?此时发送方可以查询本地业务状态,确定消息是否需要发送,以此确保最终一致性

总结

从ACID到BASE,对于事务,不同视角,对它的理解也不同
在数据库层面,通过日志文件确保了事务的一致性,以及确定了不同的事务隔离级别
在Java代码层面,更多的是关注事务之间的关系
而在分布式事务中,为了高可用,在事务一致性上进行了妥协,一般只保证最终一致性

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351