微服务原则:去中心化数据管理

微服务的理念主张将软件设计的各方各面进行去中心化。这种对去中心化的关注不仅指导业务逻辑的组织,它还会指导人们如何对数据进行存储。

在传统的整体式软件设计方法中,我们通常使用整体式的数据存储,例如包含诸多表格(Table)的单个数据库的 SQL 服务器。这种中央数据库作为全体数据的持久性引擎而被使用,并且通常应用程序逻辑的一部分以使用复杂连接(甚至存储过程)的查询的形式被卸载到 SQL 服务器中。

相比之下,微服务架构则更倾向于去中心化的数据管理,正如 Martin Fowler 在 2014 年发表的定义微服务的原始论文中所描述的那样。本文通过展示一些现代的数据管理架构模式来扩展去中心化数据管理的概念,这些模式带来了一些高度成功的去中心化应用程序。

REST 的思想

为了以去中心化的方式正确地组织数据,首先要了解如何使用表述性状态转移(Representational State Transfer,简称 REST)对数据建模。REST 是由 Roy Fielding 于 2000 年定义的,从那以后它一直指导着许多大规模可扩展的无状态系统(Stateless system)的开发。

REST 的核心原则是,为每个属于应用程序一部分的资源提供一个 URL,然后使用标准的 HTTP 动词(Standard HTTP verbs)与资源进行交互。例如,一个基础的社交消息应用程序的 API 可能是这样组织的:

这个 API 有三种基本资源类型:用户,消息和朋友。每种基本类型都由一组资源路径提供服务。在整体式架构中,一个中央服务器将处理所有对资源路径的请求,通常这个服务是由单一数据库支持的,并且它也将所有资源类型存储为表格:

一个使用去中心化数据管理的微服务的部署,将使用三个服务来服务这三种资源类型:一个服务用于用户资源,另一个服务用于消息资源,还有一个服务用于朋友关系。另外,每个服务都有独自的数据库。

事实上,每个微服务都拥有各自的数据库,这并不意味着需要三个数据库服务器。在平台初期,这三个数据库可能仅仅具有逻辑上的区别,即三个数据库全部由单个物理 SQL 服务器托管。

但是,创建这种逻辑区别将为后续的物理扩展奠定基础。如果此平台得到大量采用,数据库管理员可以将三个逻辑数据库分割为由三个不同物理服务器进行服务的数据库。

回避 SQL JOIN

良好的去中心化数据管理的一个关键点是:回避 SQL JOIN(SQL 语言中的 Join 连接语句)。对连接的需求通常始于尽量使客户端更容易地使用 API​​。例如,我们正在使用的消息传递应用程序可能有一个时间轴视图(Timeline view)。时间轴需要获得来自经过验证的用户的每位朋友的最新消息,以及消息旁边的该朋友的姓名和头像信息。

使用我们定义的基础 REST API,客户端需要进行多次 API 调用才能填充此视图。例如,有两位朋友的用户,客户端需要发出以下 API 请求才能填充视图:

总共会发出五个请求。一个请求用于获取用户的朋友列表,随后两个请求获取每个朋友的姓名和头像,最后两个请求获取每个朋友发来的最新消息。

显然从性能的角度来看,这是无法接受的,因为在准备好显示视图之前,客户端和服务器之间存在非常多的往返延迟。该问题的一个明智的解决方案是添加一条到 API 的新路由:

然后,客户端就可以获取此单个时间轴的资源,以得到用于呈现时间轴视图所需的全部数据。

用于实现这种新资源的技术,是集中式和去中心化数据管理之间差异的一个主要例子。在一个整体式应用中,服务于这种路由的逻辑可能会被编码为 SQL 连接,并且被卸载到数据库服务器,这将访问全部三个表以产生结果:

SELECT  
  m.id id,
  m.user `user`,  
  u.name name,  
  u.avatar avatar,  
  m.text `text`,  
  m.when `when`
FROM messages m
INNER JOIN (
  SELECT `from`, 
  `to`  FROM `friends`
  WHERE `from`=1) f
ON m.user = f.to
INNER JOIN users u on m.user = u.id
ORDER BY m.when DESC;

在去中心化的数据管理架构中,我们不仅不建议这样的 SQL 连接,而且如果数据使用逻辑和(或)物理边界恰当地进行了分割,则实际上不可能进行连接。

相反地,每个微服务应该是访问其自己的表的唯一途径。没有一个微服务可以访问全部三个表。为了将时间轴资源展示给客户端,我们创建了一个额外的时间轴微服务,它位于三个底层数据微服务之上,并将每个微服务视为获取资源的源头。这个顶层的微服务从底层的微服务中连接数据并将连接的结果展示给客户端:

此时间轴服务可以在几毫秒内向支持微服务器发出请求,因为时间轴服务和其他微服务托管在同一个数据中心中,并且可能托管在同一台物理机器上的容器中。为了进一步减少往返网络的开销,时间轴服务还可以利用“批量提取(Bulk fetch)”端点。例如,用户微服务可以有一个接收用户 ID 列表,并返回所有匹配的用户对象的端点,以便时间轴服务只需要向朋友的服务、用户服务,以及消息服务各发出一个请求就行了。

时间轴服务作为一个中心位置来定义时间轴的逻辑。如果业务需求发生了变化,现在客户端需要显示来自每位朋友的最新两条消息,则可以在时间轴服务中轻松更改需求,而无需修改实际托管基础资源的其他支持微服务。

此外,数据如何存储,以及数据如何被操作以供用户显示,两者之间的分离使得底层微服务可以被重构,只要它们继续遵循时间轴服务所期望的资源格式。朋友服务的维护人员可以轻松地重写朋友关系的存储方式,而不会中断时间轴服务。另一方面,当需要更新表结构之时,使用连接查询方式需要检查和更新针对表的所有连接。

最终一致性

去中心化数据管理的副作用之一,就是需要处理最终的一致性(Eventual Consistency)。在集中式数据存储中,开发人员可以使用事务功能来确保数据在多个表中处于一致状态。但是,当数据分为不同的逻辑或物理数据库时,情况就并非如此了。

例如,假设用户在其某个朋友删除其帐户的同一时间获取了他们的时间轴,会发生什么情况:

  1. 时间轴服务从朋友服务中获取朋友列表,并查看需要解析的朋友 ID。
  2. 朋友删除了自己的帐户,这会从用户服务中删除用户对象,以及朋友服务中的所有朋友引用。
  3. 时间轴服务尝试通过向用户服务发出请求来将朋友 ID 转换为用户详细信息,但接收到 404 Not Found 响应。

去中心化的数据建模需要额外的条件处理,以检测与处理基础数据在请求之间发生变化的竞争条件。通常对于简单的社交媒体应用来说,这是很简单的。但是对于更复杂的应用程序,就可能需要将某些表保存在同一个数据库中,这样便可以利用数据库事务。通常,这些链接的表格也会由单个微服务处理。或者,如果相关数据需要很强的一致性,但仍然需要去中心化,则可能需要使用两阶段提交(Two-phase commit)以便安全地操作它。

混合持久化

去中心化数据管理的一个显着优势是能够利用混合持久化(Polyglot Persistence)。不同类型的数据具有不同的存储需求:

  • 读/写平衡(某些类型的数据具有非常高的写入量,与具有低写入量但读取量高的数据相比,这可能需要不同类型的数据存储。)
  • 数据结构(某些类型的高度结构化数据,如 JSON 文档可能更好地存储在诸如 MongoDB 这样的 NoSQL 数据库中,而平面关系对象存储在 SQL 数据库中可能会更有效。)
  • 数据查询(某些数据可能使用简单的键值存储进行访问,而其他类型的数据可能需要基于多列值的高级查询。)
  • 数据生命周期(某些数据本质上属于临时数据,可以存储在快速的内存存储中,例如 Redis 或 Memcached,而其它数据必须一直保存,并且需要在磁盘上非常持久地存储。)
  • 数据尺寸(某些数据由相当一致的字节大小的相当同一的行组成,而其他数据可能包含需要存储在类似 AWS S3 中的大对象。)

对于社交消息传递应用程序的例子,每条消息实际上是一个结构化的 JSON 文档,其中包含了媒体文件、地理位置等元数据。此外可以预期的是,将会有许多用户发布消息,并且需要保留的消息总数将迅速增长。对于这种情况,负责消息的团队可能会选择使用分片的 MongoDB 集群来保存这些结构化的 JSON 数据。

另一方面,用户和朋友关系表具有简单,扁平的数据模型,并且不会快速增长,因此这些服务则由 Postgres 服务器来支持。

由于该应用程序使用着去中心化的数据管理原则,因此它可以利用混合持久化,并将不同类型的数据存储在满足特定数据类型需求的不同数据库中。

总结

去中心化数据管理可以从 REST 基础出发,找出不同资源类型之间的分隔来适当地部署。这些分离将推动微服务和数据库的边界。在为客户端提供复合资源所需的多种资源类型的情况下,我们可以使用更高层的微服务来构建这种资源,该微服务可以连接来自不同底层微服务的数据。这需要仔细处理最终一致性,但它允许使用混合持久化技术,将不同类型的数据存储在最适合处理该类型数据的存储提供者中。

本文的版权归 StoneDemo 所有,如需转载请联系作者。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

10个免费的服务器监控工具

监控你的WEB服务器或者WEB主机运行是否正常与健康是非常重要的。你要确保用户始终可以打开你的网站并且网速不慢。服务器监控工具允许你收集和分析有关你的Web服务...

7906
来自专栏晨星先生的自留地

我是如何通过手机定位妹子的位置的

2304
来自专栏Golang语言社区

Go 语言构建高并发分布式系统实践

你知道互联网最抢手的技术人才有哪些吗?最新互联网职场生态报告显示,最抢手的十大互联网技术人才排名中Go语言开发人员位居第三,从中不难见得,Go语言的渗透率越来越...

4909
来自专栏大数据文摘

首席工程师揭秘:LinkedIn大数据后台是如何运作的

2575
来自专栏JAVA高级架构

多研究些架构,少谈些框架(3)-- 微服务和事件驱动

接上篇,我们采用了领域驱动的开发方式,使用了充血模型,享受了他的好处,但是也不得不面对他带来的弊端。这个弊端在分布式的微服务架构下面又被放大。 事务一致性 事务...

3234
来自专栏Golang语言社区

Go 语言构建高并发分布式系统实践

你知道互联网最抢手的技术人才有哪些吗?最新互联网职场生态报告显示,最抢手的十大互联网技术人才排名中Go语言开发人员位居第三,从中不难见得,Go语言的渗透率越来越...

3604
来自专栏后端技术探索

携程异步消息系统实践

今天会跟大家分享一下我们在携程,现在应该是正在推广的一个新的消息系统,主要会偏重于讲一些架构和实现方面的内容。目前我在携程大概一年多都在做新的消息系统Herme...

1233
来自专栏IT笔记

微服务架构实践之邮件通知系统改造

拆分背景 随着平台业务增长,功能耦合度越来越高,部署周期变长,代码样式混乱、新人入手复杂、独立功能影响系统的稳定性等等,等等,等等问题。 以邮件通知为案例对服务...

3756
来自专栏Golang语言社区

Go 语言构建高并发分布式系统实践

你知道互联网最抢手的技术人才有哪些吗?最新互联网职场生态报告显示,最抢手的十大互联网技术人才排名中Go语言开发人员位居第三,从中不难见得,Go语言的渗透率越来越...

4044
来自专栏EAWorld

微服务的持续集成,四步“构建”一个代码世界

大师Martin Fowler对持续集成是这样定义的:持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天...

3115

扫码关注云+社区