分布式系统服务框架Zookeeper介绍与原理实现

分布式数据管理之痛点

为了确保微服务之间松耦合,每个服务都有自己的数据库, 有的是关系型数据库(SQL),有的是非关系型数据库(NoSQL)。

开发企业事务往往牵涉到多个服务,要想做到多个服务数据的一致性并非易事,同样,在多个服务之间进行数据查询也充满挑战。

我们以一个在线B2B商店为例,客户服务 包括了客户的各种信息,例如可用信用等。

管理订单,提供订单服务,则需要验证某个新订单与客户的信用限制没有冲突。

在单体应用中,订单服务只需要使用传统事务交易就可以一次性检查可用信用和创建订单。

相反微服务架构下,订单和客户表分别是相应服务的私有表,如下图所示:

订单服务不能直接访问客户表,只能通过客户服务发布的API来访问或者使用分布式事务, 也就是众所周知的两阶段提交 (2PC)来访问客户表,2PC意义图如下所示:

这里存在两个挑战,第一个挑战是2PC除要求数据库本身支持外,还要求服务的数据库类型需要保持一致。

但是现在的微服务架构中,每个服务的数据库类型可能是不一样的,有的可能是MySQL数据库,有的也可能是NoSQL数据库;

第二个挑战是如何实现从多个服务中查询数据。假设应用程序需要显示一个客户和他最近的订单。如果订单服务提供用于检索客户订单的API,那么应用程序端可以通过JOIN方式来检索此数据,即应用程序首选从客户服务检索客户,并从订单服务检索客户的订单。

然而,如果订单服务仅支持通过其主键查找订单(也许它使用仅支持基于主键的检索的NoSQL数据库), 在这种情况下,就没有方法来检索查询所需的数据。

为解决这两大痛点,就需要我们使用到分步式数据管理了。

分布式数据管理之举措

在介绍分布式数据管理(CRUD)解决方案之前,有必要介绍下CAP原理和最终一致性相关概念。

CAP原理和最终一致性

CAP原理(CAP Theorem)

在足球比赛里,一个球员在一场比赛中进三个球,称之为帽子戏法(Hat-trick)。在分布式数据系统中,也有一个帽子原理(CAP Theorem),不过此帽子非彼帽子。CAP原理中,有三个要素:

1)一致性(C onsistency)

2)可用性(A vailability)

3)分区容忍性(P artition tolerance)

CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

因此在进行分布式架构设计时,必须做出取舍。而对于分布式数据系统,分区容忍性是基本要求 ,否则就失去了价值,因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。

对于大多数web应 用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向。

当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。

牺牲一致性,只是不再要求关系型数 据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障“用户感知到的一致性”。

通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则 取决于数据复制到一致状态的时间。

最终一致性(eventually consistent)

对于一致性,可以分为从客户端和服务端两个不同的视角。

从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。

从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。

对于关系型数据库,要求更新过的数据能被后续的 访问都能看到,这是强一致性 ;如果能容忍后续的部分或者全部访问不到,则是弱一致性 ; 如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。

从服务端角度,如何尽快将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验非常重要的方面。

那么问题来了,如何实现数据的最终一致性呢?答案就在事件驱动架构。

事件驱动架构简介

Chris Richardson作为微服务架构设计领域的权威,给出了分布式数据管理的最佳解决方案。

对于大多数应用而言,要实现微服务的分布式数据管理,需要采用事件驱动架构(event-driven architecture)。

在事件驱动架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。

当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的事件发布,让其他相关服务进行数据更新,最终实现分布式数据最终一致性。

可以使用事件来实现跨多服务的业务交易。交易一般由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。

事件驱动示例1

下图展现如何使用事件驱动方法,在创建订单时检查信用可用度,微服务之间通过消息代理(Messsage Broker)来交换事件。

1. 订单服务创建一个带有NEW状态的Order (订单),发布了一个“Order Created Event(创建订单)”的事件。

2. 客户服务 消费Order Created Event事件,为此订单预留信用,发布“Credit Reserved Event(信用预留)”事件。

3. 订单服务消费Credit Reserved Event,改变订单的状态为OPEN。

 

事件驱动示例2

下图展现如何使用事件驱动方法,在创建订单时触发支付业务的数据更新,微服务之间通过消息代理(Messsage Broker)来交换事件。

1. 订单服务创建一个待支付的订单,发布一个“创建订单”的事件。

2. 支付服务消费“创建订单”事件,支付完成后发布一个“支付完成”事件。

3. 订单服务消费“支付完成”事件,订单状态更新为待出库。

事件驱动架构之分布式数据更新

上节通过示例概要介绍了通过事件驱动方式,实现了分布式数据最终一致性保证。纵观微服务架构下的事件驱动业务处理逻辑,其核心要点在于,可靠的事件投递和避免事件的重复消费。

可靠事件投递有以下两个特性:

1) 每个服务原子性的完成业务操作和发布事件;

2) 消息代理确保事件投递至少一次(at least once);

而避免事件重复消费则要求消费事件的服务实现幂等性,比如支付服务不能因为重复收到事件而多次支付。

BTW:当前流行的消息队列如Kafka等,都已经实现了事件的持久化和at least once的投递模式,所以可靠事件投递的第二条特性已经满足,这里就不展开。接下来章节讲重点讲述如何实现可靠事件投递的第一条特性和避免事件重复消费,即服务的业务操作和发布事件的原子性和避免消费者重复消费事件要求服务实现幂等性。

如何实现事件投递操作原子性?

事件驱动架构会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向ORDER表插入一行,然后发布Order Created event,这两个操作需要原子性。比如更新数据库后,服务瘫了(crashes)造成事件未能发布,系统变成不一致状态。那么如何实现服务的业务操作和发布事件的原子性呢?

1.2.3.1.1 使用本地事务发布事件

获得原子性的一个方法是将服务的业务操作和发布事件放在一个本地数据库事务里,也就是说,需要在本地建立一个EVENT表,此表在存储业务实体数据库中起到消息列表功能。当应用发起一个(本地)数据库交易,更新业务实体状态时,会向EVENT表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此EVENT表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示:

订单服务向ORDER表插入一行,然后向EVENT表中插入Order Created event,事件发布线程或者进程查询EVENT表,请求未发布事件,发布他们,然后更新EVENT表标志此事件为已发布。

此方法也是优缺点都有。优点是可以确保事件发布不依赖于2PC,应用发布业务层级事件而不需要推断他们发生了什么;而缺点在于此方法由于开发人员必须牢记发布事件,因此有可能出现错误。

使用事件源

Event sourcing (事件源)通过使用以事件中心的数据存储方式来保证业务实体的一致性。事件源保存了每个业务实体所有状态变化的事件,而不是存储实体当前的状态。应用可以通过重放事件来重建实体现在的状态。只要业务实体发生变化,新事件就会添加到事件表中。因为保存事件是单一操作,因此肯定是原子性的。

为了理解事件源工作方式,考虑以事件实体作为一个例子说明。传统方式中,每个订单映射为ORDER表中一行。但是对于事件源方式,订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够信息来重建订单的状态。

事件源方法有很多优点:解决了事件驱动架构关键问题,使得业务实体更新和事件发布原子化,但是也存在缺点,因为是持久化事件而不是对象,导致数据查询时,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,从开发角度看,存在一定挑战。

如何避免事件重复消费?

要避免事件重复消费,需要消费事件的服务实现服务幂等,因为存在重试和错误补偿机制,不可避免的在系统中存在重复收到消息的场景,服务幂等能提高数据的一致性。在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同,因此需要开发人员在功能设计实现时,需要特别注意服务的幂等性。

事件驱动架构之分布式数据查询

微服务架构下,由于分布式数据库的存在,导致在执行用户业务数据查询时,通常需要跨多个微服务数据库进行数据查询,也就是分布式数据查询。那么问题来了,由于每个微服务的数据都是私有化的,只能通过各自的REST接口获取,如果负责业务查询的功能模块,通过调用各个微服务的REST接口来分别获取基础数据,然后在内存中再进行业务数据拼装后,再返回给用户。该方法无论从程序设计或是查询性能角度看,都不是一个很好的方法。那么如何解决微服务架构下的分布式数据查询问题呢? 在给出解决方案之前,需要读者首先了解下物化视图和命令查询职责分离等相关概念。


总结
想了解学习Java方面的技术以及学习进阶哪方面的技术知识点等
可加群:722040762 验证码:666(CSDN)欢迎大家的加入哟
微服务、分布式、高并发、高可用,性能优化丶源码分析等等一些技术等着你来探讨学习!
欢迎关注CSDN:JAVA编程大飞哥
觉得收获的话可以点个关注评论一波喔,谢谢大佬们支持!
最后,每一位读到这里的Java程序猿朋友们,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序猿的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章