分布式系统数据一致性的解决方案

分布式系统数据一致性的解决方案 数据库

随着互联网的发展,计算机系统规模变得愈来愈大,常规的将全部业务单元集中部署在一个或者若干个大型机上的集中式架构,已经愈来愈不能知足当今大型互联网系统的快速发展。分布式服务架构以及微服务架构已经愈来愈受到业界的青睐。而在目前的应用系统中,无论是集中式的部署架构仍是分布式的系统架构,数据的一致性是每一个应用系统都须要面临的问题。 编程

在传统的集中式部署的系统中,应用通常不存在横跨多数据库的状况,借助关系型数据库自带的事务管理机制,关系型数据库具备ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),保证数据一致性是比较容易的。而在分布式系统中,一个普通的业务功能,内部可能须要调用部署在多个服务器上的服务,并操做多个数据库或分片来实现,状况每每会复杂不少。保证数据一致性也变得更加艰难,再经过传统的关系型数据库的事务机制等单一的技术手段和解决方案,已经没法应对和知足这些复杂的场景了。固然这些问题单纯依靠特定的开源框架和组件并不能解决,更多的仍是须要根据本身的业务场景,选择合适的解决方案。本文笔者将给你们介绍几种业界比较经常使用的解决数据一致性的实现方案,但愿能够起到一个抛砖引玉的做用。 服务器

 

 

一.CAP理论和BASE理论 网络

 

一、CAP理论架构

谈及分布式系统,就不能不提到CAP理论,CAP理论由Berkeley的计算机教授Eric Brewer在2000年提出,其核心思想是任何基于网络的数据共享系统最多只能知足数据一致性(Consistency)、可用性(Availability)和网络分区容忍(Partition Tolerance)三个特性中的两个,三个特性的定义以下: 并发

 

 

 

 

 

 

 

 

 

 

 

  • 一致性(Consistency):一致性,这个和数据库ACID的一致性相似,但这里关注的是全部数据节点上的数据一致性和正确性,而数据库的ACID关注的是在一个事务内,对数据的一些约束。系统在执行过某项操做后仍然处于一致的状态。在分布式系统中,更新操做执行成功后全部的用户都应该读取到最新值。
  • 可用性(Availability):每个操做老是可以在必定时间内返回结果。须要注意"必定时间"和"返回结果"。"必定时间"是指,系统结果必须在给定时间内返回。"返回结果"是指系统返回操做成功或失败的结果。
  • 分区容忍性(Partition Tolerance):是否能够对数据进行分区。这是考虑到性能和可伸缩性。

    在分布式系统中,同时知足"CAP定律"中的"一致性"、"可用性"和"分区容错性"三者是不可能的。在互联网应用的绝大多数的场景,都须要牺牲强一致性来换取系统的高可用性,系统每每只须要保证"最终一致性",只要这个最终时间是在用户能够接受的范围内便可。 框架

     

 

二、BASE理论 分布式

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果, 是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即便没法作到强一致性,但每一个应用均可以根据自身业务特色,采用适当的方式来使系统达到最终一致性接下来看一下BASE中的三要素: 微服务

  • 基本可用: 基本可用是指分布式系统在出现不可预知故障的时候,容许损失部分可用性。注意,这毫不等价于系统不可用。
  • 软状态: 软状态是指容许系统存在中间状态,而且该中间状态不会影响系统总体可用性。即容许系统在不一样节点间副本同步的时候存在延时。
  • 最终一致性:系统中的全部数据副本通过必定时间后,最终可以达到一致的状态,不须要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊状况。

BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的,它彻底不一样于ACID的强一致性模型,而是经过牺牲强一致性来得到可用性,并容许数据在一段时间内是不一致的,但最终达到一致状态但同时,在实际的分布式场景中,不一样业务单元和组件对数据一致性的要求是不一样的,所以在具体的分布式系统架构设计过程当中,ACID特性和BASE理论每每又会结合在一块儿。 高并发

根据CAP理论和BASE理论,在分布式系统中,咱们没法找到一种可以知足分布式系统全部系统属性的一致性解决方案,若是不想牺牲一致性,咱们只能放弃可用性,这显然不能接受。所以,为了保证数据的一致性同时又不影响系统运行的性能,许多分布式系统采用弱一致性来提升性能,一些不一样的一致性模型也相继被提出:

  • 强一致性: 当更新操做完成以后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现须要牺牲可用性。
  • 弱一致性: 系统并不保证跨进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功以后,不承诺当即能够读到最新写入的值,也不会具体的承诺多久以后能够读到。
  • 最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操做的值。在没有故障发生的前提下,不一致窗口的时间主要受通讯延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

 

 

2、数据一致性的几种解决方案

一、分布式事务

要想理解分布式事务,咱们须要先介绍一下两阶段提交协议。

两阶段提交协议(Two-phase Commit,2PC)常常被用来实现分布式事务。通常分为协调器和若干事务执行者两种角色。这里的事务执行者就是具体的数据库,抽象点能够说是能够控制给数据库的程序。 协调器能够和事务执行器在一台机器上。

在分布式系统中,每一个节点虽然能够知晓本身的操做的成功或者失败,却没法知道其余节点的操做的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,须要引入一个做为协调者的组件来统一掌控全部节点(称做参与者)。

 

两阶段提交协议在主流开发语言平台,数据库产品中都有普遍应用和实现:

一、在Java平台下,WebLogic、Webshare等主流商用的应用服务器提供了JTA的实现和支持。

二、在Windows .NET平台中,则能够借助ado.net中的TransactionScop API来编程实现,还必须配置和借助Windows操做系统中的MSDTC服务。

 

总结:这种实现方式实现比较简单,比较适合传统的单体应用,在同一个方法中存在跨数据库操做的状况。可是由于两阶段的提交会建立屡次节点的网络通讯,通讯时间变长后,事务的时间也相对变长,锁定的资源时间也变长,形成资源等待时间也变长,这会带来严重的性能问题,所以大部分高并发服务每每都避免使用二阶段提交协议,因此后来业界又引入了三阶段提交协议来解决该类问题。

 

二、非事务型消息队列+本地消息表

此方案关键是要有个本地消息表,基本思路就是:

  1. 消息生产方,须要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交。
  2. 消息消费方:处理消息并完成本身的业务逻辑。此时若是本地事务处理成功,那发送给生产方一个confirm消息,代表已经处理成功了。若是处理失败,则将消息放回MQ。
  3. 生产方定时扫描本地消息表,把还没处理完成的消息从新发送一遍,直到本地消息表中记录的该消息为已成功状态。

 

 

 

 

 

 

 

 

 

 

 

 

经过上图能够看出,消费方会面临一个问题就是,当消费方完成本地事务处理,给生产方发送CONFIRM消息失败时,生产方因为本地消息表的消息状态没有更新,会进行重试,那么这时候就存在了消息重复投递的问题,这时候消费方收到重复投递过来的消息后,要保证消费者调用业务的服务接口的幂等性,即:若是重复消费,也不能所以影响业务结果,同一消息屡次被执行会获得相同的结果。

 

总结:这种方式的根本原理就是:将分布式事务转换为多个本地事务,而后依靠重试等方式达到最终一致性。这种方式比较常见。若是MQ自身和业务都具备高可用性,理论上是能够知足大部分的业务场景的。可是因为可能存在的长时间处于中间状态,不建议交易类业务直接使用。

 

三、事务型消息队列

事务型消息其实是一个很理想的想法,目前市面上大部分MQ都不支持事务消息,其中包括目前比较火的kafka。阿里的RocketMQ是能够支持事务型消息的MQ,根据网传的资料,大概了解到RocketMQ的事务消息至关于在普通MQ的基础上,提供了2PC的提交接口。把非事务型消息队列中的消息状态和重发等用中间件形式封装了。

举个例子,Bob向Smith转帐,那咱们究竟是先发送消息,仍是先执行扣款操做?

好像均可能会出问题。若是先发消息,扣款操做失败,那么Smith的帐户里面会多出一笔钱。反过来,若是先执行扣款操做,后发送消息,那有可能扣款成功了可是消息没发出去,Smith收不到钱。除了上面介绍的经过异常捕获和回滚的方式外,还有没有其余的思路呢?

下面以阿里巴巴的RocketMQ中间件为例,分析下其设计和实现思路。

RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段经过第一阶段拿到的地址去访问消息,并修改状态。细心的读者可能又发现问题了,若是确认消息发送失败了怎么办?RocketMQ会按期扫描消息集群中的事物消息,这时候发现了Prepared消息,它会向消息发送者确认,Bob的钱究竟是减了仍是没减呢?若是减了是回滚仍是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚仍是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。以下图:

 

 

 

 

 

 

 

 

 

 

 

总结:目前各大知名的电商平台和互联网公司,几乎都是采用相似的设计思路来实现"最终一致性"的。这种方式适合的业务场景普遍,并且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,因此需二次开发。

 

后记: 上述所介绍的几种方案,笔者也只是大体总结了其基本设计思路,这几种方案只是众多解决数据一致性的方案中比较经典的几种。在现在愈来愈复杂的分布式系统架构下,数据的一致性,并非说简单的引入某个中间件可以解决的,最终一致性并无一个放之四海而皆准的成功实践。更多的仍是根据业务场景、业务特性以及业务不一样的发展阶段,选择合适的方式来灵活应对。

相关文章
相关标签/搜索