【分布式系统工程实现】分布式事务
CAP理论虽然告诉我们,一致性和可用性二者不可兼得,但这并不代表互联网系统都应该牺牲一致性,哪个特性更重要只有业务需求才能决定。
ACID是单机事务的特性,不过在分布式系统中,由于两台机器理论上无法达到一致的状态(参考Fischer等的论文),需要引入一个单点进行协调,这就出现了著名的两阶段锁协议。两阶段锁(Two-phasecommit)协议是每个分布式工程师必须掌握的协议,大致如下:
1,Prepare:协调者(Coordinator)给每个参与者(Participants)发送Prepare消息,每个参与者要么直接返回失败,比如权限验证失败,要么在本地执行但不提交,到达一种“万事俱备,只欠东风”的状态;
2,Commit/Rollback:如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作;
两阶段锁是一种悲观锁,第一个问题是协议本身的成本:整个协议过程是需要加锁的,比如锁住数据库的某条记录,且需要持久化大量事务状态相关的操作日志。更为重要的是,两阶段锁在出现故障时表现出来的脆弱性,比如两阶段锁的致命缺陷:当协调者出现故障,整个事务需要等到协调者恢复后才能继续执行,如果协调者出现类似磁盘故障等永久性错误,该事务将成为永久遗弃的孤儿。两阶段锁的更为详细的描述可以参考AndrewS.Tanenbaum的大作<>。
针对两阶段锁协调者故障的问题,有不同的解决方法:第一种是三阶段锁(Three-phasecommit),这种方法纯粹是理论上的方法,工程上不具备可操作性;第二种方法是对协调者进行Replication,当主协调者出现故障时,可以由备机接替其继续服务。如果需要将这个过程自动化,可以引入Paxos协议执行主协调者选举(参考JimGray和Lamport的论文)。
当然,这里需要简单说明,并不是所有和多机有关的事务都需要两阶段锁,比如MicrosoftAzure系统中存三个副本,主副本的事务操作以操作日志的形式同步到辅副本,虽然三台机器都进行了事务操作,不过本质上还是单机的事务操作。
和两阶段锁这种悲观锁相对应的就是乐观锁:大多数操作成功失败的可能性很小,所以不需要类似两阶段锁的Prepare阶段,直接在多个参与者上执行,当某个参与者执行出现问题,再执行补偿操作。又如存储系统中经常出现的Read-Test-Write操作,我们不会将整个过程锁住,而是允许Read-Test-Write过程中其它客户端对互斥资源的访问,比如Read阶段记录数据版本,Write的时候检查版本,如果发现不一致则重试整个Read-Test-Write过程。
BASE主要是针对多套业务系统而言,在存储系统内部一般不提这个概念。如果是数据库这样的SQL系统,就是ACID的一致性模型;如果是类似GFS+Bigtable这样的NOSQL系统,就是单行事务的一致性模型;如果是类似GoogleMegastore这样的NOSQL系统,就是支持跨机多行事务的一致性模型;如果是Dynamo这样的去中心化系统,就是基于冲突合并的一致性模型。多套业务系统之间可以采用BASE的原则进行架构设计,比如采用消息中间件进行可靠的消息传输,保证多台业务系统最终达到一致的状态。
几乎所有的NOSQL存储系统都不支持分布式事务,因为需求不够强烈,且实现极其复杂。然而,如果业务确实需要分布式事务,那就支持吧,虽然影响扩展性,不过不可能因为我们是NOSQL系统就可以找到其它不影响扩展性的方法。