1.1 分布式数据管理之痛点nginx
为了确保微服务之间松耦合,每一个服务都有本身的数据库, 有的是关系型数据库(SQL),有的是非关系型数据库(NoSQL)。web
开发企业事务每每牵涉到多个服务,要想作到多个服务数据的一致性并不是易事,一样,在多个服务之间进行数据查询也充满挑战。数据库
咱们以一个在线B2B商店为例,客户服务 包括了客户的各类信息,例如可用信用等。编程
管理订单,提供订单服务,则须要验证某个新订单与客户的信用限制没有冲突。安全
在单体应用中,订单服务只须要使用传统事务交易就能够一次性检查可用信用和建立订单。架构
相反微服务架构下,订单和客户表分别是相应服务的私有表,以下图所示:并发
订单服务不能直接访问客户表,只能经过客户服务发布的API来访问或者使用分布式事物, 也就是众所周知的两阶段提交 (2PC)来访问客户表,2PC意义图以下所示:异步
这里存在两个挑战,第一个挑战是2PC除要求数据库自己支持外,还要求服务的数据库类型须要保持一致。分布式
可是如今的微服务架构中,每一个服务的数据库类型多是不同的,有的多是MySQL数据库,有的也多是NoSQL数据库;微服务
第二个挑战是如何实现从多个服务中查询数据。假设应用程序须要显示一个客户和他最近的订单。若是订单服务提供用于检索客户订单的API,那么应用程序端能够经过JOIN方式来检索此数据,即应用程序首选从客户服务检索客户,并从订单服务检索客户的订单。
然而,若是订单服务仅支持经过其主键查找订单(也许它使用仅支持基于主键的检索的NoSQL数据库), 在这种状况下,就没有方法来检索查询所需的数据。
为解决这两大痛点,就须要咱们使用到分步式数据管理了。
1.2 分布式数据管理之举措
在介绍分布式数据管理(CRUD)解决方案以前,有必要介绍下CAP原理和最终一致性相关概念。
1.2.1 CAP原理和最终一致性
1.2.1.1 CAP原理(CAP Theorem)
在足球比赛里,一个球员在一场比赛中进三个球,称之为帽子戏法(Hat-trick)。在分布式数据系统中,也有一个帽子原理(CAP Theorem),不过此帽子非彼帽子。CAP原理中,有三个要素:
1)一致性(C onsistency)
2)可用性(A vailability)
3)分区容忍性(P artition tolerance)
CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
所以在进行分布式架构设计时,必须作出取舍。而对于分布式数据系统,分区容忍性是基本要求 ,不然就失去了价值,所以设计分布式数据系统,就是在一致性和可用性之间取一个平衡。
对于大多数web应 用,其实并不须要强一致性,所以牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向。
固然,牺牲一致性,并非彻底无论数据的一致性,不然数据是混乱的,那么系统可用性再高分布式再好也没有了价值。
牺牲一致性,只是再也不要求关系型数 据库中的强一致性,而是只要系统能达到最终一致性便可,考虑到客户体验,这个最终一致的时间窗口,要尽量的对用户透明,也就是须要保障“用户感知到的一致性”。
一般是经过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则 取决于数据复制到一致状态的时间。
1.2.1.2 最终一致性(eventually consistent)
对于一致性,能够分为从客户端和服务端两个不一样的视角。
从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
一致性是由于有并发读写才有的问题,所以在理解一致性的问题时,必定要注意结合考虑并发读写的场景。
从客户端角度,多进程并发访问时,更新过的数据在不一样进程如何获取的不一样策略,决定了不一样的一致性。
对于关系型数据库,要求更新过的数据能被后续的 访问都能看到,这是强一致性 ;若是能容忍后续的部分或者所有访问不到,则是弱一致性 ; 若是通过一段时间后要求能访问到更新后的数据,则是最终一致性。
从服务端角度,如何尽快将更新后的数据分布到整个系统,下降达到最终一致性的时间窗口,是提升系统的可用度和用户体验很是重要的方面。
那么问题来了,如何实现数据的最终一致性呢?答案就在事件驱动架构。
1.2.2 事件驱动架构简介
Chris Richardson做为微服务架构设计领域的权威,给出了分布式数据管理的最佳解决方案。
对于大多数应用而言,要实现微服务的分布式数据管理,须要采用事件驱动架构(event-driven architecture)。
在事件驱动架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。
当订阅这些事件的微服务接收此事件时,就能够更新本身的业务实体,也可能会引起更多的事件发布,让其余相关服务进行数据更新,最终实现分布式数据最终一致性。
可使用事件来实现跨多服务的业务交易。交易通常由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。
1.2.2.1 事件驱动示例1
下图展示如何使用事件驱动方法,在建立订单时检查信用可用度,微服务之间经过消息代理(Messsage Broker)来交换事件。
1. 订单服务建立一个带有NEW状态的Order (订单),发布了一个“Order Created Event(建立订单)”的事件。
2. 客户服务 消费Order Created Event事件,为此订单预留信用,发布“Credit Reserved Event(信用预留)”事件。
3. 订单服务消费Credit Reserved Event,改变订单的状态为OPEN。
1.2.2.2 事件驱动示例2
下图展示如何使用事件驱动方法,在建立订单时触发支付业务的数据更新,微服务之间经过消息代理(Messsage Broker)来交换事件。
1. 订单服务建立一个待支付的订单,发布一个“建立订单”的事件。
2. 支付服务消费“建立订单”事件,支付完成后发布一个“支付完成”事件。
3. 订单服务消费“支付完成”事件,订单状态更新为待出库。
1.2.3 事件驱动架构之分布式数据更新
上节经过示例概要介绍了经过事件驱动方式,实现了分布式数据最终一致性保证。纵观微服务架构下的事件驱动业务处理逻辑,其核心要点在于,可靠的事件投递和避免事件的重复消费。
可靠事件投递有如下两个特性:
1) 每一个服务原子性的完成业务操做和发布事件;
2) 消息代理确保事件投递至少一次(at least once);
而避免事件重复消费则要求消费事件的服务实现幂等性,好比支付服务不能由于重复收到事件而屡次支付。
BTW:当前流行的消息队列如Kafka等,都已经实现了事件的持久化和at least once的投递模式,因此可靠事件投递的第二条特性已经知足,这里就不展开。接下来章节讲重点讲述如何实现可靠事件投递的第一条特性和避免事件重复消费,即服务的业务操做和发布事件的原子性和避免消费者重复消费事件要求服务实现幂等性。
1.2.3.1 如何实现事件投递操做原子性?
事件驱动架构会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向ORDER表插入一行,而后发布Order Created event,这两个操做须要原子性。好比更新数据库后,服务瘫了(crashes)形成事件未能发布,系统变成不一致状态。那么如何实现服务的业务操做和发布事件的原子性呢?
1.2.3.1.1 使用本地事务发布事件
得到原子性的一个方法是将服务的业务操做和发布事件放在一个本地数据库事务里,也就是说,须要在本地创建一个EVENT表,此表在存储业务实体数据库中起到消息列表功能。当应用发起一个(本地)数据库交易,更新业务实体状态时,会向EVENT表中插入一个事件,而后提交这次交易。另一个独立应用进程或者线程查询此EVENT表,向消息代理发布事件,而后使用本地交易标志此事件为已发布,以下图所示:
订单服务向ORDER表插入一行,而后向EVENT表中插入Order Created event,事件发布线程或者进程查询EVENT表,请求未发布事件,发布他们,而后更新EVENT表标志此事件为已发布。
此方法也是优缺点都有。优势是能够确保事件发布不依赖于2PC,应用发布业务层级事件而不须要推断他们发生了什么;而缺点在于此方法因为开发人员必须牢记发布事件,所以有可能出现错误。
1.2.3.1.2 使用事件源
Event sourcing (事件源)经过使用以事件中心的数据存储方式来保证业务实体的一致性。事件源保存了每一个业务实体全部状态变化的事件,而不是存储实体当前的状态。应用能够经过重放事件来重建实体如今的状态。只要业务实体发生变化,新事件就会添加到事件表中。由于保存事件是单一操做,所以确定是原子性的。
为了理解事件源工做方式,考虑以事件实体做为一个例子说明。传统方式中,每一个订单映射为ORDER表中一行。可是对于事件源方式,订单服务以事件状态改变方式存储一个订单:建立的,已批准的,已发货的,取消的;每一个事件包括足够信息来重建订单的状态。
事件源方法有不少优势:解决了事件驱动架构关键问题,使得业务实体更新和事件发布原子化,可是也存在缺点,由于是持久化事件而不是对象,致使数据查询时,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,从开发角度看,存在必定挑战。
1.2.3.2 如何避免事件重复消费?
要避免事件重复消费,须要消费事件的服务实现服务幂等,由于存在重试和错误补偿机制,不可避免的在系统中存在重复收到消息的场景,服务幂等能提升数据的一致性。在编程中,一个幂等操做的特色是其任意屡次执行所产生的影响均与一次执行的影响相同,所以须要开发人员在功能设计实现时,须要特别注意服务的幂等性。
1.2.4 事件驱动架构之分布式数据查询
微服务架构下,因为分布式数据库的存在,致使在执行用户业务数据查询时,一般须要跨多个微服务数据库进行数据查询,也就是分布式数据查询。那么问题来了,因为每一个微服务的数据都是私有化的,只能经过各自的REST接口获取,若是负责业务查询的功能模块,经过调用各个微服务的REST接口来分别获取基础数据,而后在内存中再进行业务数据拼装后,再返回给用户。该方法不管从程序设计或是查询性能角度看,都不是一个很好的方法。那么如何解决微服务架构下的分布式数据查询问题呢? 在给出解决方案以前,须要读者首先了解下物化视图和命令查询职责分离等相关概念。
1.2.4.1 什么是物化视图(merialized views)?
物化视图是包括一个查询结果的数据库对像,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表。物化视图存储基于远程表的数据,也能够称为快照。这个基本上就说出了物化视图的本质,它是一组查询的结果,这样势必为未来再次须要这组数据时大大提升查询性能。物化视图有两种刷新模式ON DEMAND和ON COMMIT,用户可根据实际状况进行设置。
物化视图对于应用层是透明的,不须要有任何的改动,终端用户甚至都感受不到底层是用的物化视图。总之,使用物化视图的目的一个是提升查询性能,另外一个是因为物化视图包含的数据是远程数据库的数据快照或拷贝,微服务可经过物化视图和命令查询职责分离(CQRS)技术(参见如下章节)实现分布式数据查询。
1.2.4.2 什么是命令查询职责分离(CQRS)?
在经常使用的单体应用架构中,一般都是经过数据访问层来修改或者查询数据,通常修改和查询使用的是相同的实体。在一些业务逻辑简单的系统中可能没有什么问题,可是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能问题;另外更重要的是,在微服务架构下,一般须要跨多个微服务数据库来查询数据,此时,咱们可借助命令查询职责分离(CQRS)来有效解决这些问题。
CQRS使用分离的接口将数据查询操做(Queries)和数据修改操做(Commands)分离开来,这也意味着在查询和更新过程当中使用的数据模型也是不同的。这样读和写逻辑就隔离开来了。使用CQRS分离了读写职责以后,能够对数据进行读写分离操做来改进性能,同时提升可扩展性和安全。以下图:
主数据库处理CUD,从库处理R,从库的的结构能够和主库的结构彻底同样,也能够不同,从库主要用来进行只读的查询操做。在数量上从库的个数也能够根据查询的规模进行扩展,在业务逻辑上,也能够根据专题从主库中划分出不一样的从库。从库也能够实现成ReportingDatabase,根据查询的业务需求,从主库中抽取一些必要的数据生成一系列查询报表来存储。
使用ReportingDatabase的一些优势一般可使得查询变得更加简单高效:
· ReportingDatabase的结构和数据表会针对经常使用的查询请求进行设计。
· ReportingDatabase数据库一般会去正规化,存储一些冗余而减小必要的Join等联合查询操做,使得查询简化和高效,一些在主数据库中用不到的数据信息,在ReportingDatabase能够不用存储。
· 能够对ReportingDatabase重构优化,而不用去改变操做数据库。
· 对ReportingDatabase数据库的查询不会给操做数据库带来任何压力。
· 能够针对不一样的查询请求创建不一样的ReportingDatabase库。
1.2.4.3 如何实现事件驱动架构下的数据查询服务?
事件驱动不只能够用于分布式数据一致性保证,还能够借助物化视图和命令查询职责分离技术,使用事件来维护不一样微服务拥有数据预链接(pre-join)的物化视图,从而实现微服务架构下的分布式数据查询。维护物化视图的服务订阅了相关事件并在事件发生时更新物化视图。例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件(您还可使用事件来维护由多个微服务拥有的数据组成的物化视图。 维护该视图的服务订阅了相关事件来触发更新该物化视图)。
例如上图中间的 “客户订单视图更新”服务,主要负责客户订单视图的更新。该服务订阅了客户服务和订单服务发布的事件。当“客户订单视图更新”服务收到了上图左侧的客户或者订单更新事件,则会触发更新客户订单物化视图数据集。这里可使用文档数据库(例如MongoDB)来实现客户订单视图,为每一个用户存储一个文档。而上图右侧的客户订单视图查询服务负责响应对客户以及最近订单(经过查询客户订单视图数据集)的查询。
总之,上图所示业务逻辑,用到了事件驱动、物化视图和命令查询职责分离等技术,有效解决了微服务架构下分布式数据查询的问题。
1.2.5 事件驱动架构优缺点
事件驱动架构既有优势也有缺点,此架构能够实现跨多个服务的事务实现,且提供最终数据一致性,而且使得服务可以自动维护查询视图;而缺点在于编程模式比传统基于事务的交易模式更加复杂,必须实现补偿事务以便从应用程序级故障中恢复,例如,若是信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,好比当应用读取未更新的最终视图时也会碰见数据不一致问题。另一个缺点在于订阅者必须检测和忽略冗余事件,避免事件重复消费。
1.3 总结
在微服务架构中,每一个微服务都有本身私有的数据集。不一样微服务可能使用不一样的SQL或者NoSQL数据库。尽管数据库架构有很强的优点,可是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务数据一致性;第二个挑战是如何从多服务环境中获取一致性数据。
最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法能够解决此问题,包括将数据库视为消息队列和事件源等。
从目前技术应用范围和成熟度看,推荐使用第一种方式(本地事务发布事件),来实现事件投递原子化,便可靠事件投递。
须要提醒:!!!数据一致性是微服务架构设计中惟恐避之不及却又不得不考虑的话题。经过保证事件驱动实现最终数据的一致性,此方案的优劣,也不能简单的一言而概之,而是应该根据场景定夺,适合的才是最好的。另外,咱们在对微服务进行业务划分的时候就尽量的避免“可能会产生一致性问题”的设计。若是这种设计过多,也许是时候考虑改改设计了。
1.4 参考资料
https://www.nginx.com/blog/event-driven-data-management-microservices/
本文连接:https://mp.weixin.qq.com/s/WYNrcBe0h_o7whRGxqsqwg
更多参考内容:http://www.roncoo.com/article/index?title=%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1