Chris Richardson 微服务系列翻译全7篇链接:

原文链接:Event-Driven Data Management for Microservices


微服务与分布式数据管理问题

单体应用一般只有一个关系型数据库,这样的好处是可以实现 ACID 保证:

  • 原子性(Atomicity):原子粒度的更改
  • 一致性(Consistency)数据库的状态始终保持一致
  • 隔离性(Isolation):并发的事务表现的像是串行执行,事务之间不会互相影响
  • 持久性(Durable):一旦事务提交就不会撤销

因此,应用可以简单的开始事务,更改(增删改)多行数据,然后提交事务。

使用关系型数据库另一好处是支持 SQL。SQL 是一种丰富的、声明式的标准查询语言,用户能简易的关联查询多个表中的数据,然后RDBMS 查询调度器会执行最优的查询方式,用户不必关系底层的细节。所有的数据在一个数据库中也方便查询。

然而微服务架构中数据访问变的复杂,因为每个微服务都拥有独立的数据库,仅能通过 API 来访问。数据封装保证了微服务的松耦合,各个服务可以独立其他服务演进。而如果多个服务访问同样的数据,架构更新会更耗费时间,也需要更多的服务协调。

不同服务可能使用不同类型的数据库,现代应用存储和处理各种各样的数据,关系数据库并不总是最好的选择。一些场景下,一种特殊的 NoSQL 能提供更方便的数据模型、更好的性能和可扩展性。例如:使用 ElasticSearch 这样的搜索引擎来进行文本的存储和查询;使用 Neo4j 这样的图谱数据库来存储社交图谱数据。因此,微服务应用会混合使用 SQL 和 NoSQL 数据库,即多态型数据持久方案。这也带来了一些挑战:

1)如何跨多个服务实现事务,维护数据的一致性。我们以 B2B 商店为例:客户服务维护用户信用额度等相关的信息,订单服务管理订单并确保新订单没有超过用户的信用额度。单体应用中,订单服务可以使用 ACID 事务来核对用户信用额度并新建订单。而在微服务架构中, order 和 customer 表是各个服务私有的:

订单服务无法直接访问 customer 表,只能通过客户服务的 API。订单服务可能使用分布式事务,也被称为两阶段提交(2PC),然而 2PC 在现代应用通常不是很好的选择。CAP 定理需要用户在可用性和 ACID 的一致性中二选一,通常可用性是更好的选择。此外,很多NoSQL 并不支持 2PC,维护服务和数据库中数据的一致性是很重要的,因此我们可以选择另一种方案。

2)另一个挑战是如何检索多个服务中的数据,例如应用需要显示一位客户和他最近的订单,如果订单服务提供了用户订单的查询 API,那么可以在应用端获取该数据,应用端通过客户服务检索客户,再通过订单服务检索该客户的订单。假设订单服务只支持通过主键来查询订单,此时就没有合适的方法来检索所需数据了。

事件驱动的架构

对于许多应用,解决方案就是事件驱动的架构:服务在业务发生时(例如更新一条记录信息)会发布一个事件,其他微服务订阅该事件,当某一微服务接收到事件就会更新自己的业务记录,然后其他更多的事件会被发布。下图展示了如何使用事件驱动的方式在创建订单时检查可用信用,微服务间通过 MQ 来交换事件:

1)订单服务创建状态为 NEW 的订单,然后发布『订单创建』的事件

2)客户服务获取『订单创建』事件,为此订单保留信用,发布『信用保留』事件

3)订单服务获取『信用保留』事件,将订单状态修改为 OPEN

更为复杂的场景会涉及更多的步骤,比如核对用户信用时预留库存。

基于(a)每个服务原子性的更新数据库并发布事件;(b)MQ 确保事件至少交付一次,那么就可以实现跨服务的业务逻辑了。这并非 ACID 事务,他提供更弱一点的最终一致性。这种事务模型可称为 BASE model

也可以使用事件维护关联多个微服务的物化视图。维护此视图的服务订阅相关事件并更新视图,例如:用户订单视图通过订阅订单事件和用户事件来进行更新:

当用户订单服务收到用户服务或订单服务的事件时,会更新视图。可以使用类似 MongoDB 的文档数据库为每个用户存储一份用户订单的文档。

事件驱动架构的优点:

  1. 他使得事务能跨多个服务并提供最终一致性;
  2. 使得应用可以维护物化视图。

不足之处:

  1. 编程模型比 ACID 事务更加复杂,为了从应用级别的错误中恢复,需要完成补偿事务,例如:信用检查不成功则必须取消订单;
  2. 临时事务会导致不一致的数据。另外应用从物化视图中读取的数据未能及时更新,也会产生不一致的问题;
  3. 必须检测并忽略重复事件

实现原子化

事件驱动架构还存在一个问题:以原子粒度更新 DB 与发布事件。例如:订单服务在订单表中 insert 一行记录,然后发布『订单创建』的事件,这两个操作需要是原子性的,否则,更新 DB 后,发布事件前服务崩溃了,系统将存在不一致。确保原子性的方法是使用 分布式事务 调用 DB 和 MQ。然而由于 CAP 理论,我们是想避免这么做。

使用本地事务发布事件

应用发布事件并保证原子性的方法之一就是:多步骤本地事务方法。技巧是 DB 中有一张 EVENT 表(模拟消息队列),存储业务数据的状态。首先启动一个本地数据库事务,更新业务数据记录并往 EVENT 表中插入一条数据,最后提交事务。一个单独的线程会轮询 EVENT 表,将查询结果往 MQ 中发送事件消息,然后使用本地事务标注事件状态为已发布。如下图所示:

订单服务首先往 ORDER 表中 insert 一行记录,然后在 EVENT 表插入类型为 Order Created 的事件(状态为 NEW )。事件发布线程或进程轮询 EVENT 表中未发布的事件,发布事件然后更新 EVENT 表事件状态为已发布。

这种方法的优点:

  1. 保证每次更新都有对应的事件发布,不使用两阶段提交(2PC);
  2. 应用发布了业务级别的事件,消除推断事件类型的麻烦。

不足:

  1. 易出错,因为要求开发者必须记得更新后去发布事件;
  2. 当使用 NoSQL 时,因为 NoSQL 的事务和查询能力有限,实现起来较麻烦。

挖掘数据库事务日志

另一种不需要两阶段提交就能实现原子性的方法是:分析数据库事务日志或提交日志来发布事件。应用更新 DB时,DB的事务日志会记录这些变更,事务日志挖掘线程或进程读取这些日志,并将事件发布到 MQ,如下图所示:

范例之一就是开源的 LinkedIn Databus 项目,Databus 挖掘 Oracle等数据库的事务日志并发布相应的事件,LinkedIn 使用 Databus 来保持各种派生数据存储和记录系统的一致。

另一范例就是 streams mechanism in AWS DynamoDB,AWS DynamoDB 流包括 DynamoDB 表在过去 24 小时内的时序变化,包括新建、更新和删除操作。应用能读取这些变更,将其作为事件发布。

事务日志挖掘的优点:

  1. 能保证无需使用两阶段提交就能对每个更新发布事件;
  2. 简化应用,将事件发布与主业务逻辑分离。

不足:

  1. 每个 DB 或 同一 DB 的不同版本的事务日志格式不同;
  2. 很难从低级别事务日志的更新记录中反推高级别的业务事件。

使用事件源

事件源通过采用一种截然不同的、以事件为中心的方法来保存业务实体,而不需要 2PC 来实现原子性。这种方法存储一系列状态变动的事件,而不是实体的当前状态。应用通过重放事件来构建实体的当前状态,每当业务实体的状态改变,就往事件列表中添加新的事件。由于保存事件是唯一操作,本质上就是原子性的。

以订单为例:传统方案中,每个订单为 ORDER 表中的一行记录。使用事件源时,订单服务存储导致订单状态变化的事件,包括创建、批准、配送、取消。每个事件由充足的信息来重新构建订单:

事件被存储 DB 中,可使用 API 添加或查找实体的事件。事件存储类似上文提及的 MQ,提供 API 让其他服务订阅事件,将事件传达至感兴趣的订阅者。事件存储是事件驱动的微服务架构的支柱。

事件源的优点:

  1. 解决了事件驱动的微服务架构的关键问题,能够可靠的发布事件;
  2. 解决了数据一致性问题,由于存储事件而不是领域对象,也避免了面向对象到关系数据库的不匹配问题;
  3. 为实体提供了100%可靠的审计日志,使得获取任何时间点的实体状态成为可能;
  4. 业务逻辑与事件交互的业务实体是松耦合的,这使得单体服务迁移到微服务更容易。

不足:

  1. 采用了陌生的编程风格,学习曲线陡峭;
  2. 事件数据库只支持通过主键查询业务实体,必须使用 CQRS(Command Query Responsibility Segregation)来完成查询,因此应用程序必须采用最终一致性。

总结

微服务架构中,每个微服务都有自己的数据存储,不同的微服务可能使用不同的 SQL 和 NoSQL 数据库。这些数据库架构有很多优势,也带来了分布式数据管理的挑战。第一个挑战就是如何实现跨服务的业务事务,并保证一致性;第二个挑战就是如何从多个服务中查询数据。

对于许多应用,解决方案就是使用事件驱动的架构。事件驱动的架构带来的挑战是如何原子化地更新状态和发布事件。有几种方案可以考虑,包括把数据库用作消息队列、事务日志挖掘和事件源。

Chris Richardson微服务翻译:微服务之事件驱动的数据管理的更多相关文章

  1. Chris Richardson微服务翻译:重构单体服务为微服务

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关 构建微服务之微服务架构的进程通讯 微服务架构中的服务发现 微服务之事件驱动的数据管理 微服务部署 ...

  2. Chris Richardson微服务翻译:微服务部署

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关 构建微服务之微服务架构的进程通讯 微服务架构中的服务发现 微服务之事件驱动的数据管理 微服务部署( ...

  3. Chris Richardson微服务翻译:微服务架构中的服务发现

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关 构建微服务之微服务架构的进程通讯 微服务架构中的服务发现(本文) 微服务之事件驱动的数据管理 微服 ...

  4. Chris Richardson微服务翻译:构建微服务之微服务架构的进程通讯

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关 构建微服务之微服务架构的进程通讯(本文) 微服务架构中的服务发现 微服务之事件驱动的数据管理 微服 ...

  5. Chris Richardson微服务翻译:构建微服务之使用API网关

    Chris Richardson 微服务系列翻译全7篇链接: 微服务介绍 构建微服务之使用API网关(本文) 构建微服务之微服务架构的进程通讯 微服务架构中的服务发现 微服务之事件驱动的数据管理 微服 ...

  6. Chris Richardson微服务翻译:微服务介绍

    作者简介:Chris Richardson,世界著名的软件架构师,经典著作<POJOS IN ACTION>的作者,cloudfoundry.com 的创始人 微服务目前正受到大量的关注, ...

  7. 【转】「Chris Richardson 微服务系列」微服务架构的优势与不足

    Posted on 2016年5月4日 编者的话|本文来自 Nginx 官方博客,是微服务系列文章的第一篇,主要探讨了传统的单体式应用的不足,以及微服务架构的优势与挑战. 作者介绍:Chris Ric ...

  8. 【CHRIS RICHARDSON 微服务系列】微服务架构中的进程间通信-3

    编者的话 |本文来自 Nginx 官方博客,是微服务系列文章的第三篇,在第一篇文章中介绍了微服务架构模式,与单体模式进行了比较,并且讨论了使用微服务架构的优缺点.第二篇描述了采用微服务架构的应用客户端 ...

  9. 【CHRIS RICHARDSON 微服务系列】事件驱动的数据管理-5

    编者的话 |本文来自 Nginx 官方博客,是「Chris Richardson 微服务」系列的第五篇文章.第一篇文章介绍了微服务架构模式,并且讨论了使用微服务的优缺点:第二和第三篇描述了微服务架构模 ...

随机推荐

  1. Javascript自动化文档工具JSDuck在Windows下的使用心得

    作者: zyl910 一.工具比较 为了让前端JavaScript程序更具可维护性,更利于团队开发,文档非常重要.此时便需要使用自动化文档工具了. 我对比了各种JavaScript自动化文档工具,发现 ...

  2. 解决 ASP.NET Core Hangfire 未授权(401 Unauthorized)

    相关文章:ASP.NET Core 使用 Hangfire 定时任务 ASP.NET Core Hangfire 在正式环境发布之后,如果访问 http://10.1.2.31:5000/hangfi ...

  3. CentOS 7下Samba服务器的安装与配置

    文基于<CentOS 6.3下Samba服务器的安装与配置>,参照原博文,自己在CentOS7环境上实现,并按照自己的环境修改博文内容 一.简介 Samba是一个能让Linux系统应用Mi ...

  4. Springboot 之 解决IDEA读取properties配置文件的中文乱码问题

    问题描述 当在.properties的配置文件中有中文时,读取出来的总是乱码.比如我的application.properties配置文件的内容如下: server.port=9090 test.ms ...

  5. (五):C++分布式实时应用框架——支撑复杂的业务通讯关系

    C++分布式实时应用框架--支撑复杂的业务通讯关系 技术交流合作QQ群:436466587 欢迎讨论交流 版权声明:本文版权及所用技术归属smartguys团队所有,对于抄袭,非经同意转载等行为保留法 ...

  6. iOS 通过UIControl,自定义控件

    如:自定义一个可以点击的 图文 #import <UIKit/UIKit.h> @interface UD_Button : UIControl @property(nonatomic,s ...

  7. boost::algorithm(字符串算法库)

    没什么说的,需要 #include<boost/algorithm/string.hpp> 1.大小写转换 std::string s("test string"); ...

  8. ES6中Promise对象个人理解

    Promise是ES6原生提供的一个用来传递异步消息的对象.它减少了传统ajax金字塔回调,可以将异步操作以同步操作的流程表达出来使得代码维护和可读性方面好很多. Promise的状态: 既然是用来传 ...

  9. CDQ分治与整体二分小结

    前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完 ...

  10. NOI2009 植物大战僵尸

    啊一道好题感觉写得挺爽的啊这题这种有一点懵逼然后学了一点东西之后很明朗的感觉真是好!预处理参考 :http://www.cppblog.com/MatoNo1/archive/2014/11/01/1 ...