65.9K
CodeProject 正在变化。 阅读更多。
Home

分布式系统设计导论 - 1. 微服务架构中的拆分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9投票s)

2019年4月27日

CPOL

7分钟阅读

viewsIcon

27631

这是分布式系统设计导论 - 1. 微服务架构中的拆分

在我的文章《Web 服务的分布式方法》中,我介绍了分布式设计的方法。但读者的反馈是过于学术化,难以理解。因此我开始撰写本系列文章,以帮助新手在实践中运用分布式技术。尽管分布式是一个历史悠久的概念,最早的分布式系统出现在 20 世纪 60 年代末引入的 ARPANET 中。但到目前为止,分布式系统设计对新手非常不友好。你可能学了很多分布式理论,但在面对复杂软件系统时仍然感到束手无策。那我希望本系列文章能帮助你重新梳理分布式知识,建立正确的设计分布式系统的方法论。首先,引入分布式编程的要求不高。它要求你是一名具有一定开发经验的软件工程师,并了解并发编程的基础知识。并行编程是分布式设计的基础。你会发现并发编程的知识在分布式系统设计中也很常用。但不要将并发编程与分布式系统设计混淆,它们是两个完全不同的概念。这里的并发编程特指使用多线程开发软件系统的方法。分布式系统设计是比并发编程更高级的软件系统设计和开发行为。在本文中,我们首先描述一个典型的服务,以及如何逐步将其拆分为微服务。通过这个典型案例,介绍服务拆分的基本方法。然后,我们将逐步讨论为什么使用这种方法论以及使用它的条件和原则。

当我们根据产品定义开发软件系统时,随着用户访问量的增加,单台服务器硬件的计算和存储能力将无法满足产品需求。我们将尝试将软件系统拆分到不同的硬件服务器中。用于增加系统整体计算和存储能力的行为称为软件系统的分布式操作。微服务的概念源于此,将服务拆分成更小的服务,可以将服务放入更多的硬件中。或者将不经常使用的服务合并到一台硬件中。通过增加或减少系统硬件,可以调整系统的整体承载能力。由于服务将被拆分,第一步是了解如何构建服务。典型的 Web 服务是服务器端软件系统,可以处理多个 http 请求。例如,在 spring boot 中,每个 HTTP 请求都放在 controller 目录中。在 nodejs 的 express 框架中,它放在 routes 目录中。以前,服务被定义为由消息触发的任务组成的软件系统。

我们知道服务由多个任务组成,这些任务属于不同的产品功能。虽然它们都放在同一个服务中,但有些任务之间密切相关,而另一些任务似乎彼此无关。由于产品设计的原因,这些任务通常没有独立清晰的描述。一个服务由几十到几万个任务组成。即使对于项目开发人员来说,任务也太多了。在大型项目中,一个开发人员一天可以开发一个或多个任务。当有太多任务无法理解时,人们引入了另一个概念“产品功能”来帮助对任务进行分类。首先,我们应该摒弃产品功能的概念,因为产品功能的划分不属于软件系统范畴。原因是计算机只能识别任务,而不能识别产品功能。产品功能只是帮助人们在软件开发中记忆和交流的辅助工具,不能作为微服务拆分的标准。

微服务拆分的第一步是消除任务之间的序列和数据耦合。所有数据都将被剥离到内存数据库中,使服务无状态。首先将数据保存在任务或服务中是非常危险的。服务崩溃和硬件停机可能导致数据丢失或混淆。任务的私有数据和私有数据交换将导致任务的序列问题,即任务的执行顺序具有依赖关系。例如,采购商品的任务直接传输数据并触发支付任务,并等待支付任务的完成,这是任务之间的序列依赖。消除这种相互依赖的耦合的最简单直接的方法是将所有数据放入内存数据库。这种消除耦合的方法我称之为“AP”方法,即“服务的可用性分区或操作”。将所有数据放入内存数据库后,任务的执行过程变为

  1. 接受消息触发
  2. 读取内存数据库
  3. 实现数据处理逻辑
  4. 将处理后的数据写回内存数据库

完成上述准备工作后,就可以开始准备拆分服务了。因为每个任务的数据都是从内存数据库中读取的。在这里,你必须将每个任务放在一个单独的文件中。使用搜索工具“grep”或“搜索和替换”搜索关键字“SET”,可以找到每个任务的写入数据集。在并发编程中,我们已经了解到,如果两个线程同时写入数据,就会出现冲突错误。这个问题在分布式系统中也同样存在。如果两个具有写入数据交集的任务被分发到不同的服务器,就会出现写入数据覆盖的冲突。这两个任务之间写入交集的条件称为具有原子关系的任务。原子性是指并发编程中的互斥。在并发编程中,如果遇到原子性要求,我们需要创建一个互斥锁来保护数据的正确写入或读取。在分布式系统中,服务容器本身具有原子性。单线程服务器容器,如单线程 nginx、Tomcat 或 web 服务,本身就是原子的。也就是说,这些容器本身就是互斥锁或分布式锁。任务在从内存数据库读取和写入数据的过程中也具有天然的事务性。在任务计算过程中,数据不会存储在内存数据库中。只有当任务计算成功时,数据才会提交到内存数据库。在此期间,如果任务执行失败且数据未提交到内存数据库,则不会污染或破坏数据。

显然,具有原子关系的任务不能拆分到不同的服务器。没有原子关系的任务可以拆分到不同的服务器。例如,我们有任务“A、B、C”。写入数据为“A {裤子、裙子、外套},B {牛仔裤、外套},C {帽子、手套}”。显然,由于 A 和 B 都需要写入数据“外套”,任务 AB 具有原子关系,只能放在同一个服务器容器中。任务 C 与其他任务没有原子关系,因此不需要放在同一个容器中。那么我们可以将服务拆分为两部分。

这种方法的奇妙之处在于,你不需要真正将项目文档拆分成两个不同的部分。只要写入数据不同,就可以在路由器中分流请求。

我们称之为 RP 方法,它根据写入数据集的原子关系对任务进行分类和拆分。这里的服务似乎被撕裂开来,但仍然相互关联,在服务之间保持着一种奇妙的关系。尽管《人月神话》告诉我们没有银弹,但 AP&RP 方法确实允许我们使用简单的方法论来实现微服务的拆分。我认为这是基于以下几个方面。首先,AP&RP 方法不试图解决产品功能实现的问题。其次,AP&RP 方法首先将任务的序列性转换为数据。然后通过划分数据实现系统分布式。第三,AP&RP 方法指出服务容器是一个由许多潜在属性组成的复杂体,包括原子性、事务性等。在分布式设计中,应充分考虑这些潜在复合属性的影响。基于并发编程经验,从头开始创建互斥锁或分布式锁会使问题进一步复杂化。在这里,我们介绍了基本的分布式知识和微服务的拆分技术。如果你有任何问题要问,我们将在后续文章中进一步讨论。

如果你通过搜索引擎获得文章,可以点击以下链接获取本系列最新示例和文章。

© . All rights reserved.