微服务架构下的数据管理

2020-04-08 · 树下魅狐 · · 本文共2,602个字,预计阅读需要13分钟。

使用微服务架构后,数据访问变得非常复杂,这是因为数据都是微服务私有的,唯一可访问的方式就是通过API。

不同的微服务经常使用不同的数据库,关系型数据库并不一定是最佳选择。某些场景,某个NoSQL数据库可能提供更方便的数据模型,提供更加的性能和可扩展性。例如,某个产生和查询字符串的应用采用例如Elasticsearch的字符搜索引擎。同样的,某个产生社交图片数据的应用可以采用图片数据库,例如,Neo4j。
因此,基于微服务的应用一般都使用SQL和NoSQL结合的数据库,也就是被称为polyglot persistence的方法。

分区的polyglot-persistent架构用于存储数据有许多优势,包括松耦合服务和更佳性能和可扩展性。然而,随之而来的则是分布式数据管理带来的挑战。

挑战一:如何完成一笔交易的同时保持多个服务之间数据一致性?

以一个在线B2B商店为例,客户服务维护包括客户的各种信息,例如credit lines。订单服务管理订单,需要验证某个新订单与客户的信用限制没有冲突。
在微服务架构下,订单和客户表分别是相对应服务的私有表,如下图所示:

订单服务不能直接访问客户表,只能通过客户服务发布的API来访问。

挑战二:如何从多个服务中检索数据?

假设有个需求要显示客户和他的订单。而用户服务只接受用户信息,订单服务只支持私有主键来查询订单。

挑战三:多个服务访问同一数据时,如果保证数据的唯一性?

如果多个服务访问同一个数据,schema会更新访问时间,并在所有服务之间进行协调。

1. 事件驱动架构

事件驱动架构可以解决上述两个挑战。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,这个更新可能会触发更多的事件发布。

1.1 使用事件来实现跨多服务的业务交易

交易一般由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。
下图展现如何使用事件驱动方法,在创建订单时检查信用可用度,微服务通过消息代理(Messsage Broker)来交换事件。
(1)订单服务创建一个带有NEW状态的Order (订单),发布了一个“Order Created Event(创建订单)”的事件。

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

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

更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。

这种模式提供弱确定性,保证数据的最终一致性。其特点是先由每个服务原子性更新数据库和发布事件;然后通过消息Broker确保事件传递至少一次,从而完成跨多个服务的业务交易。

1.2 创建一个新的视图,维护此视图的服务订阅相关事件并且更新视图

例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如MongoDB)来实现客户订单视图,为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。

小结:事件驱动架构可以使得交易跨多个服务且提供最终一致性,并且可以使应用维护最终视图;其缺点在于编程模式比较复杂:为了从应用层级失效中恢复,还需要完成补偿性交易,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)交易造成的改变是可见的,另外当应用读取未更新的最终视图时也会遭遇数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事 件。

2. 原子操作

事件驱动架构会遇到数据库更新和发布事件原子性问题。例如,订单服务必须向ORDER表插入一行,然后发布Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫掉了,会造成事件未能发布,系统变成不一致状态。确保原子操作的标准方式是使用一个分布式交易,其中包括数据库和消息代理。然而,基于以上描述的CAP理论,这却并不是我们想要的,因为这里我们不能接受最终一致性,必须all or nothing。

2.1 使用本地交易发布事件

获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions。其技巧在于使用一个EVENT表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向EVENT表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此EVENT表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示:

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

笔者注:我觉得还是有问题,发布事件和更新EVENT表必须要在一个事务里,如何保证?

2.2 挖掘数据库交易日志

应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见:

成功案例:LinkedIn Databus 项目,Databus 挖掘Oracle交易日志,根据变化发布事件,LinkedIn使用Databus来保证系统内各记录之间的一致性。

交易日志挖掘的优点是将发布事件和应用业务逻辑分离,缺点在于交易日志对不同数据库有不同格式,甚至不同数据库版本也有不同格式;而且很难从底层交易日志更新记录转换为高层业务事件。

2.3 使用事件源

Event sourcing (事件源)保存业务实体一系列状态改变事件,而不是存储实体现在的状态。应用可以通过重放事件来重建实体现在状态。只要业务实体发生变化,新事件就会添加到时间表中。因为保存事件是单一操作,因此肯定是原子性的。
为了理解事件源工作方式,考虑事件实体作为一个例子。订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。
事件是长期保存在事件数据库中,提供API添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供API来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。

笔者注:事件数据库是事件微服务使用的数据库,那么创建订单服务和生成事件记录如何保证在一个事务中?