什么是微服务?

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

抽象的

在本文中,摘自我即将出版的 .NET 中的微服务 一书,我将讨论帮助您在看到微服务时识别微服务的特征,以及帮助您以实现微服务优势的方式确定和实施服务的特征

微服务是一种具有一个且只有一个非常狭隘的功能的服务。该功能通过远程 API 公开给系统的其余部分。例如,考虑一个用于管理仓库的系统:在这样的系统中,微服务可能分别提供的一些功能是:

  • 收货
  • 计算新库存应该存放在哪里
  • 计算仓库内的放置路线,将库存放入正确的存储单元
  • 为仓库员工分配放置路线
  • 接单
  • 给定一组订单计算仓库中的拣货路线
  • 为仓库员工分配拣货路线

这些功能中的每一项——很可能还有更多——都是由单独的微服务实现的。每个微服务都在单独的进程中运行,并且可以独立于其他微服务单独部署。同样,每个微服务都有自己的专用数据库。每个微服务仍然与其他微服务协作和通信。

系统内的不同微服务完全有可能在不同平台上实现——一些微服务可能在 .NET 上,其他微服务在 Erlang 上,还有一些在 Node.js 上。只要他们可以沟通以便协作,这种多语言方法就可以很好地工作。 HTTP 是一种很好的通信候选者:所有提到的平台以及许多其他平台都可以很好地处理 HTTP。其他技术也适合微服务通信:例如一些队列、一些服务总线和一些二进制协议。在这些协议中,HTTP 可能得到最广泛的支持,相当容易理解,而且——正如万维网所展示的那样——功能强大,总而言之,它是一个很好的候选者。

为了说明这一点,请再次考虑仓库系统。该系统中的一个微服务是 Assign Pick Routes 微服务。图 1 显示了 Assign Pick Route 微服务接收来自另一个协作微服务的请求。该请求是针对给定员工的下一条拣货路线。 Assign Pick Route 微服务必须为员工找到合适的路线。计算最佳路线是在另一个微服务中完成的。 Assign Pick Route 微服务只是收到拣选路线的通知,只需要决定如何将它们分配给员工。当给定员工的拣选路线请求进入时,分配拣选路线微服务会在其数据库中查找合适的拣选路线,选择一个并将其返回给调用微服务。


什么是微服务架构?

微服务作为一种架构风格是面向服务架构的一种轻量级形式,其中服务非常专注于做一件事并将其做好。

使用微服务作为其主要架构风格的系统是一个分布式系统——很可能是大量的协作微服务。每个微服务都在自己的进程中独立运行。每个微服务只提供一小部分图片,整个系统可以正常工作,因为微服务紧密协作。为了协作,他们通过一种轻量级媒体进行通信,该媒体不依赖于特定平台,如 .NET、Java 或 Erlang。如前所述,在本书中,微服务之间的所有通信都是通过 HTTP 进行的,但其他选项包括队列、总线或像 Thrift 这样的二进制协议。

微服务架构风格在构建和维护复杂的服务器端软件系统方面迅速流行起来。可以理解的是:微服务提供了一个

与更传统的面向服务的方法和单体架构相比,有许多潜在的好处。微服务——如果做得好——是可塑的、可扩展的、有弹性的,并且可以缩短从实施开始到部署再到生产的准备时间。对于复杂的软件系统,这种组合通常被证明是回避的。

微服务特性

到目前为止,我已经确定微服务是一种非常集中的服务,但这仍然是一个模糊的定义。为了缩小微服务的定义范围,让我们来看看微服务的特征是什么。在我对该术语的解释中,微服务的特点是:

  1. 负责一项能力
  2. 可单独部署
  3. 由一个或多个进程组成
  4. 拥有自己的数据存储
  5. 一个小团队可以维护少量的微服务
  6. 可更换

这个特征列表既可以帮助您在看到一个微服务时认出它,也可以帮助您以一种能够发挥微服务优势的方式来确定和实施您的服务——一个可延展、可扩展和有弹性的系统。让我们依次看一下。

负责单一能力

微服务负责整个系统中的一项且仅一项功能。将该声明分解为两个部分:首先,微服务具有单一职责。其次,责任是一种能力。单一职责原则已经以多种方式表述。一种传统的方式是:


“改变课程的理由永远不应该超过一个。”

-- Robert C. Martin SRP:单一职责原则


虽然这种表达方式特别提到了“类”,但事实证明,该原则适用于面向对象语言中除类之外的其他级别。对于微服务,我们在服务级别应用单一责任原则。另一种较新的陈述单一责任原则的方式,也是来自 Uncle Bob,是:


“将因相同原因而变化的事物聚集在一起。将因不同原因而变化的事物分开。”

-- Robert C. Martin 单一职责


这种表述原则的方式适用于微服务:微服务应该恰好实现一种能力。这样,只有当该功能发生变化时,微服务才必须改变。此外,我们应该力求让微服务完整地实现能力,这样当能力发生变化时,只需要改变一个微服务。

微服务系统中的功能可能意味着几件事。首先,能力可以是业务能力。业务能力是系统所做的有助于实现系统目的的事情——比如跟踪用户购物车或计算价格。梳理系统具有哪些独立业务功能的一个很好的方法是使用领域驱动设计。其次,能力有时可以是其他几个微服务需要使用的技术能力——例如与某些第三方系统的集成。技术能力并不是将系统分解为微服务的主要驱动因素,它们只是被确定为多个微服务实现需要相同技术能力的业务能力的结果。

可单独部署

每个微服务都应该可以单独部署。也就是说:当您更改特定的微服务时,您应该能够将该微服务的更改部署到生产环境中,而无需部署或以任何其他方式接触系统的任何其他部分。事实上,系统中的其他微服务应该在部署更改后的微服务期间以及在部署并启动并运行新版本之后继续运行和工作。

考虑一个电子商务网站。每当对购物车微服务进行更改时,您应该能够只部署购物车微服务。同时,价格计算微服务、推荐微服务、产品目录微服务等应继续工作并为用户请求提供服务。

出于多种原因,能够单独部署每个微服务很重要。首先,在微服务系统中,有许多微服务,每个微服务都会与其他几个微服务协作。同时,开发工作是在所有或许多微服务上并行完成的。如果我们必须同步部署所有或其中的一组,管理部署将很快变得笨拙,通常会导致不频繁但风险很大的部署。这是我们非常想避免的事情。相反,我们希望能够对每个微服务部署小的更改,这通常会导致频繁且小的低风险部署。

为了能够在系统的其余部分继续运行的同时部署单个微服务,构建过程必须牢记这一点:每个微服务都必须构建到单独的工件或包中。同样,部署过程本身也必须设置为支持在其他微服务继续运行的同时单独部署微服务。例如,可以使用滚动部署过程,其中微服务一次部署到一台服务器以减少停机时间。

微服务交互的方式也受到我们想要单独部署它们这一事实的影响。在大多数情况下,对微服务接口的更改必须向后兼容,以便其他现有微服务可以像与旧版本一样继续与新版本协作。此外,微服务交互的方式必须具有弹性,因为每个微服务都必须预料到其他服务偶尔会失败并尽可能继续工作。一个微服务失败——例如由于部署期间的短时间停机——不能导致其他微服务失败,只能导致功能减少或处理时间稍长。

由一个或多个进程组成

微服务由一个或多个进程组成。这个特点有两个方面。首先,每个微服务都在与其他微服务不同的进程中运行。第二,每个微服务可以有多个进程。

微服务在单独的进程中运行是希望使每个微服务尽可能独立于其他微服务的结果。此外,为了

单独部署微服务,该微服务不能与任何其他微服务在同一进程中运行。再次考虑购物车微服务。如果它在与产品目录微服务相同的进程中运行,则购物车代码可能会对产品目录产生副作用。这将意味着购物车微服务和产品目录微服务之间存在紧密且不受欢迎的耦合。

现在考虑部署新版本的购物车微服务。我们要么也必须重新部署产品目录微服务,要么有某种动态代码加载能够在运行过程中切换购物车代码。前一个选项直接反对可单独部署的微服务。第二种选择很复杂,至少会使产品目录微服务因部署到购物车微服务而面临宕机的风险。

每个微服务可能包含多个进程。从表面上看,这可能令人惊讶。毕竟,我们正在努力使每个微服务尽可能地易于处理,那么为什么要引入拥有多个进程的复杂性呢?让我们考虑电子商务网站中的推荐微服务。它实现了在我们的电子商务网站上推动推荐的推荐算法。这些算法在属于微服务的进程中运行。它还存储提供推荐所需的数据。此数据可以存储在磁盘上的文件中,但更可能存储在数据库中。该数据库在也属于微服务的第二个进程中运行。微服务通常有两个或更多进程的需求来自微服务实现提供功能所需的一切,例如数据存储和可能的后台处理。

拥有自己的数据存储

微服务拥有存储所需数据的数据存储。这是希望微服务的范围成为完整功能的另一个结果。对于大多数业务功能,需要一些数据存储。例如,对于产品目录微服务,需要存储有关每个产品的信息。为了使产品目录微服务与其他微服务保持松散耦合,包含产品信息的数据存储完全由产品目录微服务拥有。产品信息的存储方式和时间由产品目录微服务决定。其他微服务——例如购物车微服务——只能通过产品目录微服务的接口访问产品信息,而不能直接从产品目录商店访问。

每个微服务都拥有自己的数据存储,这为根据每个微服务的需求为不同的微服务使用不同的数据库技术开辟了可能性。产品目录微服务可能会使用 SQL Server 来存储产品信息,而购物车微服务可能会在 Redis 中存储每个用户的购物车,而推荐微服务可能会使用弹性搜索索引来提供推荐。为微服务选择的数据库技术是实现的一部分,并且对其他微服务是隐藏的。将数据库技术与每个微服务的需求混合和匹配的好处是允许每个微服务使用最适合工作的数据库。这可以在开发时间、性能和可扩展性方面带来好处,但也会带来成本。数据库往往是一项复杂的技术,学习在生产中可靠地使用和运行数据库并不容易。在为微服务选择数据库技术时,您应该考虑这种权衡。但也要记住,由于微服务拥有自己的数据存储,以后将其换成另一个数据库是可行的。

由一个小团队维护

到目前为止,我还没有过多讨论微服务的大小,尽管微服务一词的“微”部分表明它们很小。然而,我认为讨论微服务应该具有的代码行数,或者它应该实现的需求、用例或功能点的数量是没有意义的。所有这些都取决于微服务提供的功能的复杂性。然而,有意义的是考虑维护微服务所涉及的工作量。一个可以指导微服务规模的经验法则是一个小团队——比如 5 人——应该能够维护少数或更多的微服务。维护微服务包括保持其健康和适合目的的所有方面:开发新功能,从已经变得太大的微服务中分离出新的微服务,在生产中运行它,监控它,测试它,修复错误和其他一切需要的东西。考虑到一个小团队应该能够为少数微服务执行所有这些操作,您应该可以了解典型微服务的大小。

可更换

微服务是可替换的,这意味着它可以在合理的时间范围内从头开始重写。换句话说,维护微服务的团队可以决定用一个全新的实现替换当前的实现,并在他们的正常工作节奏内这样做。这个特性是对微服务大小的另一个限制:如果微服务变得太大,替换它的成本就会很高,只有保持很小的规模,重写才是现实的。

为什么团队会决定重写微服务?一个原因可能是代码变得一团糟。另一个是微服务在生产中的表现不够好。虽然这些不是理想的情况,但它们可以自己出现。即使我们在构建我们的微服务时很勤奋,随着时间的推移需求的变化也会以它无法处理的方式推动当前的实现。随着时间的推移,代码可能会变得凌乱,因为最初的设计被过度弯曲了。性能要求可能会增加太多,以至于当前设计无法处理。如果微服务足够小,可以在合理的时间范围内重写,这些情况时有发生是可以的。团队只需使用从编写现有实现以及新需求中获得的所有知识进行重写。

摘自 .NET 中的微服务