HTML5技术

从银行转账失败到分布式事务:总结与思考 - xybaby

字号+ 作者:H5之家 来源:H5之家 2017-10-23 13:21 我要评论( )

思考这个问题的初衷,是有一次给朋友转账,结果我的钱被扣了,朋友没收到钱。而我之前一直认为银行转账一定是由事务保证强一致性的,于是学习、总结了一下分布式事务的各种理论、方法。 事务是一个非常广义的词汇,各行各业解读都不一样。对于程序员,事务等

  思考这个问题的初衷,是有一次给朋友转账,结果我的钱被扣了,朋友没收到钱。而我之前一直认为银行转账一定是由事务保证强一致性的,于是学习、总结了一下分布式事务的各种理论、方法。

  事务是一个非常广义的词汇,各行各业解读都不一样。对于程序员,事务等价于Transaction,是指一组连续的操作,这些操作组合成一个逻辑的、完整的操作。即这组操作执行前后,系统需要处于一个可预知的、一致的状态。因此,这一组操作要么都成功执行,要么都不能执行;如果部分成功,部分失败,成功的部分需要回滚(rollback)。

  本文地址:

关系型数据库事务

  大多数人可能和我一样,第一次听说事务是在学习关系型数据库(mysql、sql server、Oracle)的时候,在关系型数据库中,如果一组操作满足ACID特性,那么称之为一个事务。关于关系型数据库的ACID特性,不管是教材还是网络上都有大量的资料,这里只简单介绍。

  A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况
  C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。这里的一致性含义后面会详细解释
  I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务运行过程的中间状态
  D(Durability):持久性,事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。

  我们举一个简单的转账的例子,用户A给玩家B转100块钱,那么涉及到两个操作:玩家A的账户扣100元,玩家B的账户加100元。即

UserA.account -= 100
UserB.account += 100

  原子性很好理解,这两个操作要么都成功,要么都不执行(更准确的是从效果上来看等价于都没有执行)。不可能出现用户A的钱减少了而用户B的钱没增加的情况,用户是不允许的;更不可能出现用户B的钱增加 而 用户A的钱没有减少的情况,银行是绝对不干的。

 

  一致性说一起来大家都懂,但是深究起来也是似懂非懂。ACID中的一致性,网络上的介绍都很模糊,都是说要处于一致的状态,那什么是一致的状态呢,比如转账操作中,A扣钱,B加钱,AB的钱的综合是一定的,这个是否属于ACID中的Consistency呢?我觉得不是的,Wiki Transaction_processing和Wiki: ACID分别是这么描述的

 Consistency: A transaction is a correct transformation of the state. The actions taken as a group do not violate any of the integrity constraints associated with the state.

The consistency property ensures that any transaction will bring the database from one valid state to another. Any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This does not guarantee correctness of the transaction in all ways the application programmer might have wanted (that is the responsibility of application-level code), but merely that any programming errors cannot result in the violation of any defined rules.

  上面黑色加粗的部分指出,ACID中的一致性是指完整性约束不被破坏,完整性包含实体完整性(主属性不为空)、参照完整性(外键必须存在原表中)、用户自定义的完整性。用户自定义的完整性比如列值非空(not null)、列值唯一(unique)、列值是否满足一个bool表达式(check语句,如性别只能有两个值、岁数是一定范围内的整数等),例如age smallint CHECK (age >=0 AND age <= 120).数据库保证age的值在[0, 120]的范围,如果不在这个范文,那么更新操作失败,事务也会失败。另外,向mysql中的cascade,以及触发器(trigger)都属于用户自定义的完整性约束。在MongoDB3.2中document validation就是用户自定义的完整性约束,在插入或者更新docuemnt的时候检查,不过用户可以自行设定validationAction,确定当数据不符合约束时的表现,默认为error,即拒绝数据写操作。

  因此,用户A,B在这次事务操作前后,账户的总和一定,是应用层面的一致性,而不是数据库保证的一致性,应用层面的一致性事实上是由原子性来保证的。

 

  隔离性说起来简单,但事实上背后的事情很复杂,数据库的隔离性依赖于加锁或者多版本控制。简单来说,如果UserA.account初始值为500,执行完第一条指令(即减去100),但事务还没有提交,其他的事务是不能读到这个中间结果(UserA.account的值为400)的。这就是避免了脏读(Drity Read),对应的隔离级别就是READ_COMMITTED。在SQL标准中,定义了四个隔离级别:

  READ_UNCOMMITTED
  READ_COMMITTED
  REPEATABLE_READ
  SERIALIZABLE

  来解决事务并发中带来的一下几个问题脏读(Dirty Read)、不可重复读(Non-repeatable Read)、幻读(Phantom Read)

  不同的数据库或者说存储引擎默认支持不同的隔离级别,比如InnoDB存储引擎默认支持REPEATABLE_READ,而Mongodb只支持READ_UNCOMMITTED

 

  持久性需要考虑到一个事务在执行过程中的各种情况的异常。一个事务的流程是这样的:

开启一个事务
执行一组操作
如果都执行成功,那么提交并结束事务
如果任何操作失败,那么回滚已经执行的操作,结束事务

  在事务执行过程中,如果出现故障,比如断电、宕机,这个时候就要利用日志(redo log或者undo log) 加上 checkpoint来保证事务的完整结束。

 

分布式事务

  当数据的规模越来越大,超出了单个关系型数据库的处理能力,这个时候就出现了关系型数据的垂直分表或者分表,也出现了天然支持水平扩展(sharding)的NoSql。另外,大型网站的服务化(SOA)以及这两年非常火的微服务,往往将服务进行拆分,单独部署,自然也使用独立的数据库,甚至是异构的数据库。这个时候,关系型数据库保证事务的手段,比如加锁、日志就行不通了。当然,本文讨论的不仅仅是数据库,也包含分布式存储、消息队列,以及任何要保证原子性、持久性的逻辑。

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • canvas一周一练 -- canvas绘制中国银行标志(4) - 张不丢

    canvas一周一练 -- canvas绘制中国银行标志(4) - 张不丢

    2017-08-01 08:02

  • 样式化加载失败的图片 - royalrover

    样式化加载失败的图片 - royalrover

    2017-02-21 08:03

  • 《七年失败的程序之路》读后感续:积累和包装 - 自由飞

    《七年失败的程序之路》读后感续:积累和包装 - 自由飞

    2016-08-20 10:00

  • 《七年失败的程序之路》读后感 - 自由飞

    《七年失败的程序之路》读后感 - 自由飞

    2016-08-15 13:00

网友点评
h