ACID到底是个啥?
在传统的单体应用中,其后端通常会有一个关系型数据库,如Oracle DB、MySQL等。通过关系型数据库,有助于保证业务的ACID。
ACID这个贯口,相信大多数同学都能说出一二,但真正把这四点脱稿讲清楚,相信也不太容易,尤其是隔离性(I)方面。因此,我先介绍ACD,将I放在最后。
原子性A :定义:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。
例如,大卫向小卫转账1000元。这个要么转成了,1000元到账;要不转账失败,不存在转账成功一半,转了500元的情况。
一致性C :定义:事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
还拿转账举例子:张三、李四、王五是三个好基友。三个人每人账户有100元,共有300元。张三给李四转账10,李四给王五转账50,王五给张三转账100。那么,最后三个人账户的总额仍是300。
持久性D: 定义:在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。一旦交易提交了就不可回滚.
这点是可以很容易理解的。大卫给小卫转账,只要转成功了,就不能撤销转账了,后悔也来不及了。除非大卫再忽悠小卫把钱转回来。
隔离性I :定义:如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。即使交易并发执行,看起来也是串行的(Isolation: Concurrently executing transactions see the stored information as if they were running serially (one after another).)
这段话其实比较绕。别着急,看完以下内容,就清晰了。
先看一张大卫根据查阅到的资料总结的表,级别的数字越高,隔离的级别也就最高,牺牲的性能也就最多,最高的级别就是将多个并发事务串行执行:
事务隔离级别 | 事务隔离描述 | 解决的问题 | 不能解决的问题 |
---|---|---|---|
最低1 | Read Uncommitted | 无 | 脏读 |
2 | Read Committed | 脏读 | 不可重复读 |
3 | Repeated Read | 脏读、不可重复读 | 幻读 |
最高4 | Serialization | 脏读、不可重复读、幻读 | 无 |
事务的隔离级(每个事务之间)别从低到高有:
1.Read Uncommitted:定义:最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
这个级别的隔离,会造成脏读。事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。
举个例子,网购:卖家是先确认到买家把钱打过来,再给卖家确认发货。如果卖家访问到的是买家正在付款时的事务信息,但并没有提交(卖家并不知道买家没有提交事务),然后就把货发给卖家。而在几个小时候,买家因为一些原因,撤销订单。这时候,卖家访问到的数据,就是脏数据。
2.Read Committed:定义:只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
通过Read Committed这种方式,可以解决Read Uncommitted举的网购尴尬的例子。但解决不了不可重复读的问题。
不可重复读(Non-repeatable read) : 在同一个事务中,对于同一份数据读取到的结果不一致。比如,事务B在事务A提交前读到的结果,和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这导致锁竞争加剧,影响性能。
举个例子:网购的时候,汤姆、杰瑞和凯特三个人都想同时买一箱牛栏山二窝头酒。而网站上只有两箱库存。三个人分别登录网站的时候,都看到还有两箱库存,三个人同时下单。结果在后台,汤姆和杰瑞的网速快一点,下单成功,而过了一天,卖家告诉凯特,你的酒买不成了,原因缺货。凯特暴怒:“TM我买的时候显示有货啊!” 结果很尴尬。。。
3.Repeated Read:定义:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
Repeated Read隔离级别可以解决脏读和不可重复读的问题,但解决不了幻读(Phantom Read)的问题。
什么是幻读?在同一个事务中,同一个查询多次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读仅指由于并发事务增加记录导致的问题,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。
继续上面的例子网购的时候,汤姆、杰瑞和凯特三个人都想同时买一箱红星二窝头酒。而网站上只有两箱库存。三个人分别登录网站的时候,都看到还有两箱库存,同时下单,结果汤姆和杰瑞的动作快一点,买成功。到凯特确认提交订单的时候,显示没库存了,因此无法提交订单。。凯特一愣:靠!之前不是显示库存还有一箱的么?!
4.Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。
所以说,解决事务的隔离问题的终极方案,是将所以所有的并行事务改成串行执行,但显然,这将大幅度牺牲性能。
最后,我们再看一下上面列出过的表格,应该体会就比较深了:
事务隔离级别 | 事务隔离描述 | 解决的问题 | 不能解决的问题 |
---|---|---|---|
最低1 | Read Uncommitted | 无 | 脏读 |
2 | Read Committed | 脏读 | 不可重复读 |
3 | Repeated Read | 脏读、不可重复读 | 幻读 |
最高4 | Serialization | 脏读、不可重复读、幻读 | 无 |
其实,大多数有过网购同学,都有过双十一抢购货物,到最后显示无货的经历。当然,这无伤大雅。
但对于银行而言,宁可牺牲一部分性能,也要保证数据的ACID,很简单,这个玩意和money有关。
利用关系型数据库,尽量保证事务ACID和CAP的方式,貌似无任何问题,也天下无敌了。大卫半路出家学过一点Oracle DB的知识,知道那个经典的
但是,在微服务架构中,情况有变。对于微服务架构来说,数据都是微服务私有的,唯一可访问的方式就是通过API。这种打包数据访问方式使得微服务之间松耦合,并且彼此之间独立。传统式关系型数据库方式就不是很适合。
在上图中,订单服务不能直接访问客户表,只能通过客户服务发布的API来访问。订单服务也可以使用分布式事务处理(两阶段提交 2PC,这个概念在文后会有介绍)。
在服务和数据库之间维护数据一致性是非常根本的需求,在微服务架构中,2PC并不是一个十分理想的方案,因此我们需要找其他的方案。
事件驱动架构
在微服务架构中,我们将传统单体应用的各个功能模块拆分成多个更细小的服务,部署在容器或者虚拟机中。因此保证事务的一致性有点费劲。
为了方便理解,我们举个例子。有四个不错的朋友,以前一起工作,一起租房子住,想打麻将的时候,喊一嗓子,就可以开始打了。后来大家换了工作,有了自己的家庭。想再约打麻将,就可以建个微信群,约起来。在这四个微信群里,大家除了可以约打麻将,还可以约喝酒。
如果说以前大家住在一起的时候,可以简单地理解成一个单体应用。四个组件之间紧耦合。而后来大家分开以后,实现了松耦合,四个人之间的通讯,就需要微信群这个消息代理(Messsage Broker)来交换事件。也就是事务驱动型架构(event-driven architecture)。
在事务驱动型架构这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的时间发布。
接下来,举的一个例子是,有两个微服务:Order Service和Customer Service。而这两个微服务之间的交互还是比较多的。两个微服务之间,有个消息代理。
第一步:客户端(通过API Gateway)对Order Service的微服务发起请求,创建个订单。而创建订单这个请求,在Order Service的数据库表中增加一行记录。而Order Service收到创建订单的请求的同时,向消息代理发一个创建订单的事件:
第二步:Customer Service这个微服务,通过消息代理获取到了创建订单的这个事件,然后,Customer Service在其后端数据库中,为此订单预留信用,然后向消息代理发布一个“Credit Reserved Event(信用预留)”事件
第三步:Order Service收到了Credit Reserved Event,改变订单的状态为OPEN
事件驱动架构优点是:使得交易跨多个服务且提供最终一致性,并且可以使应用维护最终视图;缺点在于编程模式比ACID 交易模式更加复杂。
消息代理的功能,在中间件范畴,如 JBoss A-MQ中 。顺便提一句,JBoss目前是红帽企业版中间件产品,社区的开源项目叫WildFly。
在以容器为基础的为微服务解决方案中,红帽官网提供了容器化的JBoss A-MQ docker image,可以直接部署在Openshift中。
由于篇幅有限,本文不做试验展示。后续文章会补上。
备注:
什么是2PC?
两阶段提交顾名思义它分成两个阶段,先由一方进行提议(propose)并收集其他节点的反馈(vote),再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator),其他参与决议节点称为参与者(participants, 或cohorts)。
在阶段1中,coordinator发起一个提议,分别问询各participant是否接受。
在阶段2中,coordinator根据participant的反馈,提交或中止事务,如果participant全部同意则提交,只要有一个participant不同意就中止。
参考文献:
http://www.cnblogs.com/ws-astrologer/p/6681089.html https://baike.baidu.com/item/acid/10738?fr=aladdin
http://czmmiao.iteye.com/blog/1967034
http://www.cnblogs.com/bangerlee/p/5268485.html
https://en.wikipedia.org/wiki/Message_broker