云原生数据模式

小窍门

此内容摘自电子书《为 Azure 架构云原生 .NET 应用程序》,可在 .NET 文档 查阅或下载免费的 PDF 离线阅读。

Azure 平台的云原生 .NET 应用电子书封面缩略图。

正如我们在整个书中所见,云原生方法改变了设计、部署和管理应用程序的方式。 它还会更改管理和存储数据的方式。

图 5-1 对比了差异。

云原生应用程序中的数据存储

图 5-1. 云原生应用程序中的数据管理

经验丰富的开发人员可以轻松识别图 5-1 左侧的体系结构。 在此 整体应用程序中,业务服务组件并置在共享服务层中,共享单个关系数据库中的数据。

在许多方面,单一数据库保持数据管理简单。 跨多个表查询数据非常简单。 对数据所做的更改会一起更新,或者全部回退。 ACID 事务保证了强一致性和即时一致性。

我们采用不同的方法对云原生应用程序进行设计。 在图 5-1 右侧,请注意业务功能如何划分为小型独立 微服务。 每个微服务封装特定的业务功能及其自己的数据。 整体数据库分解成具有许多较小数据库的分布式数据模型,每个模型都与微服务保持一致。 当一切明了时,我们得到了一个设计,该设计为每个微服务公开一个数据库

每个微服务一个数据库,为什么?

每个微服务的此数据库提供了许多优势,尤其是对于必须快速发展并支持大规模规模的系统。 使用此模型...

  • 域数据封装在服务中
  • 数据架构可以演变,而不会直接影响其他服务
  • 每个数据存储可以独立扩展
  • 一个服务中的数据存储失败不会直接影响其他服务

隔离数据还允许每个微服务实现最适合其工作负荷、存储需求和读/写模式的数据存储类型。 选择包括关系、文档、键值,甚至基于图形的数据存储。

图 5-2 显示了云原生系统中多方持久性的原则。

Polyglot 数据持久性

图 5-2. 多语言数据持久性

请注意上图中每个微服务如何支持不同类型的数据存储。

  • 产品目录微服务使用关系数据库来容纳其基础数据的丰富关系结构。
  • 购物车微服务使用支持其简单键值数据存储的分布式缓存。
  • 订单微服务使用 NoSql 文档数据库进行写入操作,并使用高度非规范化的键/值存储以适应大量读取操作。

虽然关系数据库仍与具有复杂数据的微服务相关,但 NoSQL 数据库非常受欢迎。 它们提供大量规模和高可用性。 其无架构性质使开发人员能够远离类型化数据类和 ORM 的体系结构,这些体系结构使得更改成本高昂且耗时。 本章稍后将介绍 NoSQL 数据库。

虽然将数据封装到单独的微服务可以提高敏捷性、性能和可伸缩性,但它也带来了许多挑战。 在下一部分中,我们将讨论这些挑战以及有助于克服这些挑战的模式和做法。

跨服务查询

虽然微服务是独立的,并且专注于特定的功能功能,例如清单、发货或订购,但它们通常需要与其他微服务集成。 通常,集成涉及一个微服务向另一个微服务查询数据。 图 5-3 显示了方案。

跨微服务进行查询

图 5-3. 跨微服务查询

在上图中,我们看到一个购物篮微服务,该微服务将项添加到用户的购物篮。 虽然此微服务的数据存储包含篮子和行项数据,但它不维护产品或定价数据。 相反,这些数据项由目录和定价微服务拥有。 这一方面存在问题。 当购物篮微服务没有产品或定价数据时,如何向用户的购物篮添加产品?

第 4 章中讨论的一个选项是进行直接 HTTP 调用,从购物车到目录和定价微服务。 但是,在第 4 章中,我们指出同步 HTTP 调用将微服务连接在一起,从而减少其自治性并削弱其架构优势。

我们还可以为每个服务实现具有单独的入站和出站队列 的请求-回复模式 。 但是,此模式很复杂,需要管道来关联请求和响应消息。 尽管它确实将后端微服务调用分离,但调用服务仍必须同步等待调用完成。 网络拥塞、暂时性故障或微服务过载可能会导致操作长时间运行,甚至失败。

相反,用于删除跨服务依赖项的广泛接受模式是 具体化视图模式,如图 5-4 所示。

具体化视图模式

图 5-4. 具体化视图模式

使用此模式,可以在购物篮服务中放置本地数据表(称为 读取模型)。 此表包含产品和定价微服务所需的数据的非规范化副本。 将数据直接复制到购物篮微服务无需进行昂贵的跨服务调用。 借助服务本地数据,可以改进服务的响应时间和可靠性。 此外,拥有自己的数据副本会使购物篮服务更具弹性。 即使目录服务不可用,它也不会直接影响购物车服务。 购物篮可以继续使用自己的商店的数据。

这种方法的问题是,你的系统中现在存在重复的数据。 但是, 从战略上 复制云原生系统中的数据是一种既定的做法,不被视为反模式或不良做法。 请记住, 唯一的一个服务 可以拥有一个数据集,并对其拥有控制权。 更新记录系统时,需要同步读取模型。 同步通常通过 具有发布/订阅模式的异步消息传送来实现,如图 5.4 所示。

分布式事务

尽管跨微服务查询数据比较困难,但跨多个微服务实现事务更为复杂。 无法低估在不同微服务中跨独立数据源维护数据一致性的固有挑战。 云原生应用程序中缺少分布式事务意味着必须以编程方式管理分布式事务。 你将从 立即一致性 的世界转移到 最终一致性的世界。

图 5-5 显示了问题。

Saga 模式的事务

图 5-5. 在微服务之间实现事务处理

在上图中,五个独立的微服务参与创建订单的分布式事务。 每个微服务维护自己的数据存储,并针对其存储实现本地事务。 若要创建订单,每个单独的微服务的本地事务必须成功,否则所有微服务都必须中止并回退操作。 虽然每个微服务内都提供内置事务支持,但不支持跨所有五个服务的分布式事务,以保持数据一致。

相反,必须 以编程方式构造此分布式事务。

添加分布式事务支持的常用模式是 Saga 模式。 它通过以编程方式按顺序调用每个事务的方式将本地事务组合在一起来实现。 如果任何本地事务失败,Saga 将中止操作并调用一组补偿事务。 补偿事务撤消上述本地事务所做的更改并还原数据一致性。 图 5-6 显示了具有 Saga 模式的失败事务。

在 saga 模式中回滚

图 5-6. 回滚事务

在上图中,库存微服务中的更新库存操作失败。 Saga 调用一组补偿事务(红色)来调整库存计数、取消付款和订单,并将每个微服务的数据返回一致状态。

Saga 模式通常编排为一系列相关事件,或安排为一组相关命令。 在第 4 章中,我们讨论了服务聚合器模式,该模式是协调 saga 实现的基础。 我们还讨论了事件以及 Azure 服务总线Azure 事件网格主题,它们将成为精心设计的 saga 实现的基础。

大容量数据

大型云原生应用程序通常支持大量数据要求。 在这些情况下,传统数据存储技术可能会导致瓶颈。 对于大规模部署的复杂系统,命令和查询责任分离(CQRS)和事件溯源都可能会提高应用程序性能。

CQRS

CQRS 是一种体系结构模式,可帮助最大程度地提高性能、可伸缩性和安全性。 该模式将读取数据的作与写入数据的作分开。

在正常情况下,相同的实体模型和数据存储库对象用于读取和写入操作。

但是,大容量数据方案可以从单独的模型和数据表中受益,以便进行读取和写入。 为了提高性能,读取操作可以针对高度非规范化表示的数据进行查询,以避免成本高昂的重复表联接和表锁定。 写入操作(称为命令)将更新完全规范化的数据表示形式,以保证一致性。 然后,需要实现一种机制来使这两种表示形式保持同步。通常,每当修改写入表时,它都发布一个将修改复制到读取表 的事件

图 5-7 显示了 CQRS 模式的实现。

命令和查询责任分离

图 5-7. CQRS 实现

在上图中,实现了单独的命令和查询模型。 将每个数据写入操作保存到写入存储,然后传播到读取存储。 密切关注数据传播过程如何按照 最终一致性原则进行作。 读取模型最终与写入模型同步,但过程中可能存在一些滞后。 我们将在下一部分中讨论最终一致性。

这种分隔使读取和写入能够独立缩放。 读取操作使用针对查询优化的架构,而写入操作使用针对更新优化的架构。 读取查询针对非规范化数据,而复杂的业务逻辑可以应用于写入模型。 可能会对写入操作施加比公开读取操作更严格的安全性。

实现 CQRS 可以提高云原生服务的应用程序性能。 但是,它确实会导致更复杂的设计。 将此原则仔细和战略性地应用于云原生应用程序的这些部分,这些部分将受益于此原则。 有关 CQRS 的详细信息,请参阅Microsoft书籍 .NET 微服务:容器化 .NET 应用程序的体系结构

事件溯源

优化大容量数据方案的另一种方法涉及 事件溯源

系统通常存储数据实体的当前状态。 例如,如果用户更改其电话号码,则客户记录会使用新号码进行更新。 我们始终知道数据实体的当前状态,但每个更新都会覆盖以前的状态。

在大多数情况下,此模型可以正常工作。 但是,在大容量系统中,事务锁定和频繁更新作的开销可能会影响数据库性能、响应能力和限制可伸缩性。

事件溯源采用不同的方法来捕获数据。 每个影响数据的操作都会保存到事件存储中。 我们不会更新数据记录的状态,而是将每个更改追加到过去事件的顺序列表,类似于会计师的账本。 事件存储成为数据的记录系统。 它用于在微服务的边界上下文中传播各种具体化视图。 图 5.8 显示了模式。

事件溯源

图 5-8. 事件溯源

在上图中,请注意如何将用户购物车的每个条目(蓝色)追加到基础事件存储中。 在相邻的具体化视图中,系统通过重播与每个购物车关联的所有事件来投影当前状态。 然后,这个视图或读取模型随后被返回给 UI。 事件还可以与外部系统和应用程序集成,也可以查询以确定实体的当前状态。 使用此方法,可以维护历史记录。 你不仅知道实体的当前状态,还了解了如何达到此状态。

从机械上讲,事件溯源简化了写入模型。 没有更新或删除。 将每个数据条目追加为不可变事件可最大程度地减少与关系数据库关联的争用、锁定和并发冲突。 使用具体化视图模式生成读取模型,使你可以将视图与写入模型分离,并选择最佳数据存储来优化应用程序 UI 的需求。

对于此模式,请考虑直接支持事件溯源的数据存储。 Azure Cosmos DB、MongoDB、Cassandra、CouchDB 和 RavenDB 是很好的候选项。

与所有模式和技术一样,在战略上和在需要时实施。 虽然事件溯源可以提供更高的性能和可伸缩性,但代价是复杂性和学习曲线。