mirror of
https://github.com/DocsHome/microservices.git
synced 2025-12-08 19:25:13 +00:00
Re-proofread content
This commit is contained in:
parent
79b62c1526
commit
2a38f2ae28
@ -5,7 +5,8 @@
|
||||
|
||||
我们先来看看为什么要考虑使用微服务。
|
||||
|
||||
<a name="building-monolithic-applications"></a>
|
||||
<a id="building-monolithic-applications"></a>
|
||||
|
||||
## 1.1、构建单体应用
|
||||
我们假设,您开始开发一个打车应用,打算与 Uber 和 Hailo 竞争。经过初步交流和需求收集,您将手动或者使用类似 Rails、Spring Boot、Play 或者 Maven 等平台来生成一个新项目。
|
||||
|
||||
@ -19,7 +20,8 @@
|
||||
|
||||
以这种风格编写的应用是很常见的。他们很容易开发,因为我们的 IDE 和其他工具就是专注于构建单体应用。这些应用程序也很容易测试。您可以通过简单地启动并使用如 Selenium 测试包来测试 UI 以轻松地实现端到端(end-to-end)测试。单体应用同样易于部署。你只需拷贝打包好的应用程序到服务器。您还可以通过运行多个副本和结合负载均衡器来扩展应用。在项目的早期阶段,它可以良好运作。
|
||||
|
||||
<a name="marching-toward-monolithic-hell"></a>
|
||||
<a id="marching-toward-monolithic-hell"></a>
|
||||
|
||||
## 1.2、走向单体地狱
|
||||
不幸的是,这种简单的方法有很大的局限性。成功的应用有一个趋势,随着时间推移而变得越来越臃肿。您的开发团队在每个冲刺阶段都要实现了更多的用户需求,这意味着需要添加了许多行代码。几年之后,小而简单的应用将会逐渐成长成一个[庞然大物似的单体][3]。为了给出一个极端示例,我最近和一位开发者做了交谈,他正在编写一个工具,该工具用于从他们的数百万行代码(lines of code,LOC)应用中分析出数千个 JAR 之间的依赖。我相信这是大量开发者在多年齐心协力下创造出了这样的野兽。
|
||||
|
||||
@ -39,7 +41,8 @@
|
||||
|
||||
那么您能做些什么呢?
|
||||
|
||||
<a name="tackling-the-complexity"></a>
|
||||
<a id="tackling-the-complexity"></a>
|
||||
|
||||
## 1.3、微服务 — 解决复杂问题
|
||||
许多组织,如 Amazon、eBay 和 [Netflix][8],已经采用现在所谓的[微服务架构模式][9]解决了这个问题,而不是构建一个臃肿的单体应用。它的思路是将应用程序分解成一套较小的互连服务。
|
||||
|
||||
@ -75,7 +78,8 @@
|
||||
|
||||
从表面上看,微服务架构模式类似于 SOA。微服务是由一组服务组成。然而,换另一种方式去思考微服务架构模式,它是没有商业化的 SOA,没有集成 [Web 服务规范][18](WS-\*)和企业服务总线(Enterprise Service Bus,ESB)。基于微服务的应用支持更简单、轻量级的协议,例如,REST,而不是 WS-\*。他们也尽量避免使用 ESB,而是实现微服务本身具有类似 ESB 的功能。微服务架构也拒绝了 SOA 的其他部分,例如,数据访问[规范模式][19]概念。
|
||||
|
||||
<a name="the-benefits-of-microservices"></a>
|
||||
<a id="the-benefits-of-microservices"></a>
|
||||
|
||||
## 1.4、微服务的优点
|
||||
微服务架构模式有许多非常好的地方。第一,它解决了复杂问题。它把可能会变得庞大的单体应用程序分解成一套服务。虽然功能数量不变,但是应用程序已经被分解成可管理的块或者服务。每个服务都有一个明确定义的边界的方式,如远程过程调用(RPC)驱动或者消息驱动的 API。微服务架构模式强制一定程度的模块化,实际上,使用单体代码来实现是极其困难的。因此,个体服务能被更快地开发,并更容易理解与维护。
|
||||
|
||||
@ -85,7 +89,8 @@
|
||||
|
||||
最后,微服务架构模式使得每个服务能够独立扩展。您可以仅部署满足每个服务的容量和可用性约束的实例数目。此外,您可以使用与服务资源要求最匹配的硬件。例如,您可以在 EC2 Compute Optimized 实例上部署一个 CPU 密集型图像处理服务,并且在 EC2 Memory-optimized 实例上部署一个内存数据库服务。
|
||||
|
||||
<a name="the-drawbacks-of-microservices"></a>
|
||||
<a id="the-drawbacks-of-microservices"></a>
|
||||
|
||||
## 1.5、微服务的缺点
|
||||
就像 Fred Brooks 近 30 年前写的[《人月神话》][20]说的,没有银弹。与其他技术一样,微服务架构模式也存在着缺点。其中一个缺点就是名称本身。微服务这个术语的重点过多偏向于服务的规模。事实上,有些开发者主张构建极细粒度的 10 至 100 LOC(代码行) 服务虽然对于小型服务可能比较好,但重要的是要记住,小型服务只是一种手段,而不是主要目标。微服务的目标在于充分分解应用程序以方便应用敏捷开发和部署。
|
||||
|
||||
@ -105,12 +110,14 @@
|
||||
|
||||
自动化微服务部署的另一个方式是开发自己的 PaaS。一个普遍的起点是使用集群方案,如 [Kubernetes][24],与 Docker 等容器技术相结合。在本书最后我们将看到[基于软件的应用交付][25]方式如 NGINX 是如何在微服务级别处理缓存、访问控制、API 计量和监控,这些可以帮助解决此问题。
|
||||
|
||||
<a name="summary"></a>
|
||||
<a id="summary"></a>
|
||||
## 1.6、总结
|
||||
构建复杂的微服务应用程序本质上是困难的。单体架构模式只适用于简单、轻量级的应用程序,如果您使用它来构建复杂应用,您最终会陷入一个痛苦的境地。微服务架构模式是复杂、持续发展应用的一个更好的选择。尽管它存在着缺点与实现挑战。
|
||||
|
||||
在后面的章节中,我将介绍微服务架构的方方面面并探讨诸如服务发现、服务部署方案以及将单体应用重构为服务的策略。
|
||||
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:NGINX Plus 作为反向代理服务器
|
||||
|
||||
By Floyd Smith
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
# 2、使用 API 网关
|
||||
本书的七个章节是关于设计、构建和部署微服务。第一章介绍了微服务架构模式。它阐述使用微服务的优点与缺点,以及尽管如此,微服务通常是复杂应用的理想选择。该系列的第二篇文章将探讨使用 API 网关构建微服务。
|
||||
本书的七个章节是关于设计、构建和部署微服务。[第一章](1-introduction-to-microservices.md)介绍了微服务架构模式。它阐述使用微服务的优点与缺点,以及尽管如此,微服务通常是复杂应用的理想选择。该系列的第二章将探讨使用 API 网关构建微服务。
|
||||
|
||||
当您选择将应用程序构建成为一组微服务时,您需要决定应用程序客户端将如何与微服务进行交互。单体应用程序只有一组端点(endpoint),通常使用复制(replicated)结合负载均衡来分配流量。
|
||||
|
||||
然而,在微服务架构中,每个微服务都暴露一组通常比较细颗粒的端点。在本文中,我们将研究如何改进客户端通信,并提出一个使用 API 网关的方案。
|
||||
|
||||
<a id="introduction"></a>
|
||||
|
||||
## 2.1、简介
|
||||
我们假设您正在为一个购物应用开发一个本地原生移动客户端。您可能需要实现一个产品详细信息页面,用于展示给定商品的信息。
|
||||
我们假设您正在为一个购物应用开发一个原生移动客户端。您可能需要实现一个产品详细信息页面,用于展示给定商品的信息。
|
||||
|
||||
例如,图 2-1 展示了在 Amazon 的 Android 移动应用中的滚动产品信息时所看的内容。
|
||||
|
||||

|
||||
|
||||
即使这是一个智能手机应用,产品详细信息页面展示了很多信息。例如,不仅有基本的产品信息,如名称、描述和价格,页面还展示了:
|
||||
这是一个智能手机应用,产品详细信息页面展示了很多信息。不仅有基本的产品信息,如名称、描述和价格,页面还展示了:
|
||||
|
||||
1. 购物车中的物品数量
|
||||
2. 订单历史
|
||||
3. 客户评论
|
||||
3. 客户评价
|
||||
4. 低库存警告
|
||||
5. 配送选项
|
||||
6. 各种推荐,包括了买了此产品的客户经常买的其他产品
|
||||
6. 各种推荐,包括了购买此产品的客户购买的其他产品
|
||||
7. 选择性购买选项
|
||||
|
||||
在使用单体应用架构的情况下,移动客户端通过对应用程序进行单个 REST 调用来检索此数据,例如:
|
||||
@ -30,16 +32,18 @@ GET api.company.com/productdetails/productId
|
||||
|
||||
负载均衡器将请求路由到几个相同应用程序实例中的其中一个。之后,应用程序查询各个数据库表并返回响应给客户端。相比之下,当使用微服务架构时,产品详细页面上展示的数据来自多个微服务。以下是一些可能拥有特定商品页面展示的数据的微服务:
|
||||
|
||||
- **订单服务** - 订单历史
|
||||
- **目录(catalog)服务** - 基本的产品信息,如产品名称、图片和价格
|
||||
- **评价服务** - 客户评价
|
||||
- **库存服务** - 低库存警告
|
||||
- **配送服务** - 配送选项、期限和费用,由配送方的 API 单独提供
|
||||
- **推荐服务** - 推荐类目
|
||||
- **订单服务** — 订单历史
|
||||
- **目录(catalog)服务** — 基本的产品信息,如产品名称、图片和价格
|
||||
- **评价服务** — 客户评价
|
||||
- **库存服务** — 低库存警告
|
||||
- **配送服务** — 配送选项、期限和费用,由配送方的 API 单独提供
|
||||
- **推荐服务** — 推荐类目
|
||||
|
||||

|
||||
|
||||
我们需要决定移动客户端如何访问这些服务。让我们来看看有哪些方式。
|
||||
我们需要决定移动客户端如何访问这些服务。让我们来看看有哪些方法。
|
||||
|
||||
<a id="direct-client-to-microservice-communication"></a>
|
||||
|
||||
## 2.2、客户端与微服务直接通信
|
||||
理论上,客户端可以直接向每个微服务发送请求。每个微服务都有一个公开的端点:
|
||||
@ -47,18 +51,20 @@ GET api.company.com/productdetails/productId
|
||||
```
|
||||
https://serviceName.api.company.name
|
||||
```
|
||||
该 URL 将映射到用于跨可用实例分发请求的微服务负载均衡器,要检索特定的产品页面信息,移动客户端将向上述的每个微服务发送请求。
|
||||
该 URL 将映射到用于跨可用实例分发请求的微服务负载均衡器。为了检索特定的产品页面信息,移动客户端将向上述的每个微服务发送请求。
|
||||
|
||||
不幸的是,这种方式存在着挑战与限制。第一个问题是客户端的需求与每个微服务暴露的细粒度的 API 不匹配。此示例中,客户端需要进行七次单独请求。如果在更加复杂的应用中,它可能需要做更多的工作。例如,Amazon 展示了在产品页面渲染中如何牵涉到数百个微服务。虽然客户端可以通过 LAN 发送许多请求,但在公共互联网下效率低下,在移动网络必然是不切实际。
|
||||
|
||||
客户端直接调用微服务的另一个问题是有些可能使用了不是 web 友好的协议。一个服务可能使用 Thrift 二进制 RPC,而另一个则可能使用 AMQP 消息协议。这两个协议无论是对于浏览器还是防火墙都是不友好的,最好是在内部使用。应用程序应该在防火墙之外使用 HTTP 或者 WebSocket 之类的协议。
|
||||
客户端直接调用微服务存在的另一个问题是有些可能使用了非 web 友好协议。一个服务可能使用了 Thrift 二进制 RPC,而另一个则可能使用 AMQP 消息协议。这两个协议无论是对于浏览器还是防火墙都是不友好的,最好是在内部使用。应用程序在防火墙之外应该使用 HTTP 或者 WebSocket 之类的协议。
|
||||
|
||||
这种方法的另一个缺点是它难以重构微服务。随着时间推移,我们可能会想改变系统划分服务。例如,我们可能会合并两个服务或者将服务拆分为两个或者多个。然而,如果客户端直接与服务进行通信,实施这类的重构将变得非常困难。
|
||||
|
||||
由于存在这些问题,很少有客户端直接与微服务进行通信。
|
||||
由于存在这些问题,很少有客户端与微服务直接进行通信。
|
||||
|
||||
<a id="using-an-api-gateway"></a>
|
||||
|
||||
## 2.3、使用 API 网关
|
||||
通常更好的方法是使用 API 网关。一个 API 网关是一个服务器,是系统的单入口点。它类似于面向对象设计模式中的门面(Facade)模式。API 网关封装了内部系统架构,并针对每个客户端提供一个定制的 API。它还可用于认证、监控、负载均衡、缓存和静态响应处理。
|
||||
通常更好的方法是使用 API 网关。API 网关是一个服务器,是系统的单入口点。它类似于面向对象设计模式中的门面(Facade)模式。API 网关封装了内部系统架构,并针对每个客户端提供一个定制 API。它还可用于认证、监控、负载均衡、缓存和静态响应处理。
|
||||
|
||||
图 2-3 展示了 API 通常如何整合架构
|
||||
|
||||
@ -68,65 +74,88 @@ API 网关负责请求路由、组合和协议转换。所有的客户端请求
|
||||
|
||||
API 还可以为每个客户端提供一个定制的 API。它通常会为移动客户端暴露一个粗粒度的 API。例如,考虑一下产品详细信息场景。API 网关可以提供一个端点 `/productdetails?productid=xxx`,如图 2-3 所示,一个使用了 API 网关的微服务。允许移动客户端通过一个单独的请求来检索所有产品详细信息。API 网关通过调用各种服务(产品信息、推荐、评价等)并组合结果。
|
||||
|
||||
API 网关的一个很好的案例是 [Netflix API 网关](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)。Netflix 流媒体服务可用于数百种不同类型的设备,包括电视机、机顶盒、智能手机、游戏机和平板电脑等。起初,Netflix 尝试为他们的流媒体服务提供一个[通用](http://www.programmableweb.com/news/why-rest-keeps-me-night/2012/05/15)的 API。但是,他们发现由于设备种类繁多,并且他们各自有着不同需求,所以并不是能很好地运作。如今,他们使用了 API 网关,通过运行特定设备适配代码来为每个设备提供一个定制 API。
|
||||
API 网关的一个很好的案例是 [Netflix API 网关](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)。Netflix 流媒体服务可用于数百种不同类型的设备,包括电视机、机顶盒、智能手机、游戏机和平板电脑等。起初,Netflix 尝试为他们的流媒体服务提供一个[通用](http://www.programmableweb.com/news/why-rest-keeps-me-night/2012/05/15)的 API。后来,他们发现由于设备种类繁多,并且他们各自有着不同需求,所以并不是能很好地运作。如今,他们使用了 API 网关,通过运行特定设备适配代码来为每个设备提供一个定制 API。
|
||||
|
||||
## 2.4、API 网关的优点和缺点
|
||||
<a id="benefits-and-drawbacks-of-an-api-gateway"></a>
|
||||
|
||||
## 2.4、API 网关的优点与缺点
|
||||
正如您所料,使用 API 网关同样存在好处与坏处。使用 API 网关的主要好处是它封装了应用程序的内部结构。客户端只需要与网关通信,而不必调用特定的服务。API 网关为每种类型的客户端提供了特定的 API,减少了客户端与应用程序之间的往返次数。它还简化了客户端代码。
|
||||
|
||||
API 网关也存在一些缺点,它是另一个高度可用的组件,需要开发、部署和管理。还有另一个风险是 API 网关可能会成为开发瓶颈。开发人员必须更新 API 网关以暴露每个微服务的端点。
|
||||
|
||||
重要的是更新 API 网关的过程应尽可能地轻一些。否则,开发人员将被迫排队等待网关更新。尽管 API 网关存在这些缺点,但对于大多数的真实应用来说,使用 API 是合理的。
|
||||
重要的是更新 API 网关的过程应尽可能地放缓一些。否则,开发人员将被迫排队等待网关更新。尽管 API 网关存在这些缺点,但对于大多数的真实应用来说,使用 API 是合理的。
|
||||
|
||||
<a id="implementing-an-api-gateway"></a>
|
||||
|
||||
## 2.5、实施 API 网关
|
||||
我们已经了解了使用 API 网关的动机与权衡。接下来让我们看看您需要考虑的各种设计问题。
|
||||
|
||||
<a id="performance-and-scalability"></a>
|
||||
|
||||
### 2.5.1、性能与可扩展性
|
||||
只有少数公司能达到 Netflix 的运营规模,每天需要处理数十亿的请求。然而,对于大多数应用来说,API 网关的性能和可扩展性是相当重要的。因此,在一个支持异步、非阻塞 I/O 平台上构建 API 网关是有必要的。可以使用不同的技术来实现一个可扩展的 API 网关。在 JVM 上,您可以使用基于 NIO 的框架,如Netty、Vertx、Spring Reactor 或者 JBoss Undertow。一个流行的非 JVM 选择是使用 Node.js,它是一个建立在 Chrome 的 JavaScript 引擎的平台。另一个选择是使用 NGINX Plus。
|
||||
只有少数公司能达到 Netflix 的运营规模,每天需要处理数十亿的请求。然而,对于大多数应用来说,API 网关的性能和可扩展性是相当重要的。因此,在一个支持异步、非阻塞 I/O 平台上构建 API 网关是有必要的。可以使用不同的技术来实现一个可扩展的 API 网关。在 JVM 上,您可以使用基于 NIO 的框架,如 Netty、Vertx、Spring Reactor 或者 JBoss Undertow。一个流行的非 JVM 选择是使用 Node.js,它是一个建立在 Chrome JavaScript 引擎的平台。另一个选择是使用 NGINX Plus。
|
||||
|
||||
[NGINX Plus](https://www.nginx.com/solutions/api-gateway/) 提供了一个成熟、可扩展和高性能的 Web 服务器和反向代理,它易于部署、配置和编程。NGINX Plus 可以管理身份验证、访问控制、负载均衡请求、缓存响应,并且提供了应用程序健康检查和监控功能。
|
||||
|
||||
<a id="using-a-reactive-programming-model"></a>
|
||||
|
||||
### 2.5.2、使用响应式编程模型
|
||||
API 网关通过简单地把他们(请求)路由到适当的后端服务来处理一些请求。它通过调用多个后端服务并聚合结果来处理其他请求。对于某些请求,如产品详细信息请求,对后端服务请求而言是彼此独立的。为了缩短响应时间到最小,API 网关应该并发执行独立请求。
|
||||
|
||||
然而,有时候,请求是相互依赖的。首先,API 网关可能需要在将请求路由到后端服务之前,通过调用验证服务来验证请求。同样,为了从客户的愿望清单中获取关于产品的信息,API 网关首先必须检索包含该信息的客户资料,然后检索每个产品的信息。API 组合的另一个有趣的案例是 [Netflix 视频网格](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)。
|
||||
|
||||
使用传统的异步回调方式来编写 API 组合代码会很快使你陷入回调地狱。代码将会变得杂乱,难以理解,并且容易出错。一个更好的方式是使用响应式方法以声明式编写 API 网关代码。响应式抽象的例子包括 Scala 的 [Future](http://docs.scala-lang.org/overviews/core/futures.html)、Java 8 中的 [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) 和 JavaScript 中的 [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。还有 [Reactive Extensions](http://reactivex.io/)(也称为 Rx 或 ReactiveX),最初由 Microsoft 为 .NET 平台开发。Netflix 为 JVM 创建了 RxJava,专门应用于其 API 网关。还有用于 JavaScript 的 RxJS,它可以在浏览器和 Node.js 中运行。使用响应式方式可让您能够编写出简单而高效的 API 网关代码。
|
||||
使用传统的异步回调方式来编写 API 组合代码会很快使你陷入回调地狱。代码将会变得杂乱、难以理解并且容易出错。一个更好的方式是使用响应式方法以声明式编写 API 网关代码。响应式抽象的例子包括 Scala 的 [Future](http://docs.scala-lang.org/overviews/core/futures.html)、Java 8 中的 [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) 和 JavaScript 中的 [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。还有 [Reactive Extensions](http://reactivex.io/)(也称为 Rx 或 ReactiveX),最初由 Microsoft 为 .NET 平台开发。Netflix 为 JVM 创建了 RxJava,专门应用于其 API 网关。还有用于 JavaScript 的 RxJS,它可以在浏览器和 Node.js 中运行。使用响应式方式可让您能够编写出简单而高效的 API 网关代码。
|
||||
|
||||
<a id="service-invocation"></a>
|
||||
|
||||
### 2.5.3、服务调用
|
||||
一个基于微服务的应用程序是一个分布式系统,必须使用一个进程间(inter-process)通信机制。有两种进程间通信方案。一是使用基于消息的异步机制。某些实现采用了消息代理,如 JMS 和 AMQP。其他采用无代理的方式直接与服务通信,如 Zeromq。
|
||||
|
||||
另一种类型的进程间通信采用了同步机制,如 HTTP 和 Thrift。系统通常会同时使用异步和同步方式。甚至可以为每种方式应用多个实现。因此,API 网关需要支持各种通信机制。
|
||||
|
||||
<a id="service-discovery"></a>
|
||||
|
||||
### 2.5.4、服务发现
|
||||
API 网关需要知道与其通信的每个微服务的位置(IP 地址和端口)。传统应用程序中,您可以将这些位置硬编码,但在现代基于云的微服务应用程序中,找到所需的位置不是一个简单的问题。
|
||||
API 网关需要知道与其通信的每个微服务的位置(IP 地址和端口)。在传统应用程序中,您可以将这些位置硬编码,但在现代基于云的微服务应用程序中,找到所需的位置不是一件简单的事情。
|
||||
|
||||
基础设施服务(比如消息代理)通常都有一个可以通过系统环境变量来指定的静态位置。但是,要确定应用程序服务的位置并不是那么容易。
|
||||
|
||||
应用服务可以动态分配位置。此外,由于自动扩缩与升级,一个服务的整组实例可以动态变更。因此,API 网关与系统中的任何其他服务客户端一样,需要使用系统的服务发现机制:[服务端发现](http://microservices.io/patterns/server-side-discovery.html)或[客户端发现](http://microservices.io/patterns/client-side-discovery.html)。 第4章中更详细地描述了服务发现。现在需要注意的是,如果系统使用客户端发现,API 网关必须能够查询[服务注册表](http://microservices.io/patterns/service-registry.html),该注册表是所有微服务实例及其位置的数据库。
|
||||
应用服务可以动态分配位置。此外,由于自动扩缩和升级,一个服务的整组实例可以动态变更。因此,API 网关与系统中的任何其他服务客户端一样,需要使用系统的服务发现机制:[服务端发现](http://microservices.io/patterns/server-side-discovery.html)或[客户端发现](http://microservices.io/patterns/client-side-discovery.html)。[第四章](4-service-discovery.md)中更详细地描述了服务发现。现在需要注意的是,如果系统使用客户端发现,API 网关必须能够查询[服务注册中心](http://microservices.io/patterns/service-registry.html),该注册中心是所有微服务实例及其位置的数据库。
|
||||
|
||||
<a id="handling-partial-failures"></a>
|
||||
|
||||
### 2.5.5、处理局部故障
|
||||
实施 API 网关时必须解决的另一个问题是局部故障问题。当一个服务调用另一个响应缓慢或者不可用的服务时,所有分布式系统都会出现此问题。API 网关不应该无限期地等待下游服务。但是,如何处理故障问题取决于特定的方案和哪些服务发生故障。例如,如果推荐服务在获取产品详细信息时没有响应,API 网关应将其余的产品详细信息返回给客户端,因为它们对用户仍然有用。建议可以是空的,也可以用其他代替,例如硬编码的十强名单。然而,如果产品信息服务没有响应,那么 API 网关应该向客户端返回错误。
|
||||
实施 API 网关时必须解决的另一个问题是局部故障问题。当一个服务调用另一个响应缓慢或者不可用的服务时,所有分布式系统都会出现此问题。API 网关不应该无期限地等待下游服务。但是,如何处理故障问题取决于特定的方案和哪些服务发生故障。例如,如果推荐服务在获取产品详细信息时没有响应,API 网关应将其余的产品详细信息返回给客户端,因为它们对用户仍然有用。建议可以是空的,也可以用其他代替,例如硬编码的十强名单。然而,如果产品信息服务没有响应,那么 API 网关应该向客户端返回错误。
|
||||
|
||||
如果可以,API 网关还可以返回缓存数据。例如,由于产品价格变化不大,如果价格服务不可用,API 网关可以返回被缓存的价格数据。数据可以由 API 网关缓存或存储在外部缓存中,如 Redis 或者 Memcached。API 网关通过返回默认数据或缓存数据,确保了系统发生故障时最小程度上影响到用户体验。
|
||||
如果可以,API 网关还可以返回缓存数据。例如,由于产品价格变化不大,如果价格服务不可用,API 网关可以返回被缓存的价格数据。数据可以由 API 网关缓存或存储在外部缓存中,如 Redis 或者 Memcached。API 网关通过返回默认数据或缓存数据,确保系统发生故障时最小程度上影响到用户体验。
|
||||
|
||||
[Netflix Hystrix](https://github.com/Netflix/Hystrix) 是用于编写调用远程服务代码的一个非常有用的库。Hystrix 可以使超出指定阈值的调用超时。它实现了断路器模式,防止客户端不必要地等待无响应的服务。如果服务的错误率超过指定阈值,Hystrix 将会跳闸,所有请求将在指定的时间内立即失败。Hystrix 允许您在请求失败时定义回退操作,例如从缓存读取或返回默认值。如果您正在使用 JVM,那么您一定要考虑使用 Hystrix。如果您是在非 JVM 环境中运行,则应使用同等作用的库。
|
||||
|
||||
<a id="summary"></a>
|
||||
|
||||
## 2.6、总结
|
||||
对于大多数基于微服务的应用程序来说,实现一个 API 网关是很有意义的,API 网关充当着系统的单入口点,并且负责请求路由,组合和协议转换。它为每个应用程序客户端提供了一个自定义 API。API 网关还可以通过返回缓存或默认数据来掩盖后端服务故障。在下一章中,我们将介绍服务间的通信。
|
||||
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:NGINX Plus 作为 API 网关
|
||||
|
||||
by Floyd Smith
|
||||
|
||||
本章讨论了 API 网关如何作为系统的单入口点。它可以处理诸如负载均衡、缓存、监控和协议转换等其他功能 —— 当 NGINX 充当一个反向代理服务器时,可以作为系统的单入口点,并且支持所有提到的一个 API 网关具有的附加功能。因此使用 NGINX 作为 API 网关的主机可以很好地工作。
|
||||
本章讨论了 API 网关如何作为系统的单入口点。它可以处理诸如负载均衡、缓存、监控和协议转换等其他功能 — 当 NGINX 充当反向代理服务器时,其可以作为系统的单入口点,并且支持所有提到的一个 API 网关具有的附加功能。因此使用 NGINX 作为 API 网关的主机可以很好地工作。
|
||||
|
||||
将 NGINX 作为 API 网关并不是本书最开始的想法。[NGINX Plus](https://www.nginx.com/products/) 是用于管理和保护基于 HTTP 的 API 流量的领先平台。您可以实现自己的 API 网关或使用现有的 API 管理平台,其中许多使用了 NGINX。
|
||||
|
||||
使用 NGINX Plus 作为 [API 网关](https://www.nginx.com/solutions/api-gateway/)的理由包括:
|
||||
|
||||
- **访问管理** - 上至典型的 Web 应用级别,下至每个个体微服务级别,您都可以使用各种访问控制列表(ACL)方法,并且可以轻松实现 SSL/TLS。
|
||||
- **可管理性与弹性** - 您可以使用 NGINX 的动态重新配置 API、Lua 模块、Perl 来更新基于 NGINX Plus 的 API 服务器,也可以通过 Chef、Puppet、ZooKeeper 或 DNS 来改变。
|
||||
- **与第三方工具集成** - NGINX Plus 已经可以与某些先进的工具集成在一起
|
||||
,如 [3scale](https://www.nginx.com/blog/manage-api-gateway-less-3scale-nginx-plus/),[Kong](https://www.nginx.com/blog/nginx-powers-kong-api-management-solution/) 和 [MuleSoft](https://www.nginx.com/blog/mulesoft-implements-nginx-plus/) 集成平台(仅列举在 NGINX 网站上提及的工具)。
|
||||
- **访问管理**
|
||||
|
||||
NGINX Plus 被广泛用作 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)中的 API 网关。利用在这里收集的文章以及 MRA (微服务参考架构)来了解如何在您自己的应用程序中实现这一点。
|
||||
上至典型的 Web 应用级别,下至每个个体微服务级别,您都可以使用各种访问控制列表(ACL)方法,并且可以轻松实现 SSL/TLS。
|
||||
- **可管理性与弹性**
|
||||
|
||||
您可以使用 NGINX 的动态重新配置 API、Lua 模块、Perl 来更新基于 NGINX Plus 的 API 服务器,也可以通过 Chef、Puppet、ZooKeeper 或 DNS 来改变。
|
||||
- **与第三方工具集成**
|
||||
|
||||
NGINX Plus 已经可以与某些先进的工具集成在一起,如 [3scale](https://www.nginx.com/blog/manage-api-gateway-less-3scale-nginx-plus/),[Kong](https://www.nginx.com/blog/nginx-powers-kong-api-management-solution/) 和 [MuleSoft](https://www.nginx.com/blog/mulesoft-implements-nginx-plus/) 集成平台(仅列举在 NGINX 网站上提及的工具)。
|
||||
|
||||
NGINX Plus 被广泛用作 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)中的 API 网关。利用在这里收集的文章以及 MRA(微服务参考架构)来了解如何在您自己的应用程序中实现这一点。
|
||||
@ -1,5 +1,7 @@
|
||||
# 3、进程间通信
|
||||
本书的第三章主要是关于使用微服务架构构建应用程序。第一章介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),将其与单体架构模式进行对比,并讨论了使用微服务的优点和缺点。第二章描述了应用程序客户端通过扮演中间人角色的 [API 网关](http://microservices.io/patterns/apigateway.html)与微服务器进行通信。在本章中,我们来了解一下系统中的服务是如何相互通信的。第四章将详细探讨服务发现方面的内容。
|
||||
本书是关于如何使用微服务架构构建应用程序,这是本书的第三章。[第一章](1-introduction-to-microservices.md)介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),将其与单体架构模式进行对比,并讨论了使用微服务的优点与缺点。[第二章](2-using-an-api-gateway.md)描述了应用程序客户端通过扮演中间人角色的 [API 网关](http://microservices.io/patterns/apigateway.html)与微服务器进行通信。在本章中,我们来了解一下系统中的服务是如何相互通信的。[第四章](4-service-discovery.md)将详细探讨服务发现方面的内容。
|
||||
|
||||
<a id="introduction"></a>
|
||||
|
||||
## 3.1、简介
|
||||
在单体应用程序中,组件可通过语言级方法或者函数相互调用。相比之下,基于微服务的应用程序是一个运行在多台机器上的分布式系统。通常,每个服务实例是一个进程。
|
||||
@ -10,22 +12,24 @@
|
||||
|
||||

|
||||
|
||||
## 3.2、交互方式
|
||||
当为服务选择一种 IPC 机制时,首先需要考虑服务如何交互。有很多种客户端-服务交互方式。它们可以分为两个类。第一类是一对一交互与一对多交互:
|
||||
<a id="interaction-styles"></a>
|
||||
|
||||
- **一对一** - 每个客户端请求都由一个服务实例处理。
|
||||
- **一对多** - 每个请求由多个服务实例处理。
|
||||
## 3.2、交互方式
|
||||
当为服务选择一种 IPC 机制时,首先需要考虑服务如何交互。有很多种客户端 — 服务交互方式。它们可以分为两个类。第一类是一对一交互与一对多交互:
|
||||
|
||||
- **一对一** — 每个客户端请求都由一个服务实例处理。
|
||||
- **一对多** — 每个请求由多个服务实例处理。
|
||||
|
||||
第二类是同步交互与异步交互:
|
||||
|
||||
- **同步** - 客户端要求服务及时响应,在等待过程中可能会发生阻塞。
|
||||
- **异步** - 客户端在等待响应时不会发生阻塞,但响应(如果有)不一定立即返回。
|
||||
- **同步** — 客户端要求服务及时响应,在等待过程中可能会发生阻塞。
|
||||
- **异步** — 客户端在等待响应时不会发生阻塞,但响应(如果有)不一定立即返回。
|
||||
|
||||
下表展示了各种交互方式。
|
||||
|
||||
- | 一对一 | 一对多
|
||||
\- | 一对一 | 一对多
|
||||
---|---|---
|
||||
同步 | 请求/响应 | -
|
||||
同步 | 请求/响应 | \-
|
||||
异步 | 通知 | 发布/订阅
|
||||
异步 | 请求/异步响应 | 发布/异步响应
|
||||
|
||||
@ -33,14 +37,24 @@
|
||||
|
||||
一对一交互分为以下列举的类型,包括同步(请求/响应)和异步(通知与请求/异步响应):
|
||||
|
||||
- **请求/响应** - 客户端向服务发出请求并等待响应。客户端要求响应及时到达。在基于线程的应用程序中,发出请求的线程可能在等待时发生阻塞。
|
||||
- **通知(a.k.a. 单向请求)** - 客户端向服务发送请求,但不要求响应。
|
||||
- **请求/异步响应** - 客户端向服务发送请求,服务异步响应。客户端在等待时不发生阻止,适用于假设响应可能不会立即到达的场景。
|
||||
- **请求/响应**
|
||||
|
||||
客户端向服务发出请求并等待响应。客户端要求响应及时到达。在基于线程的应用程序中,发出请求的线程可能在等待时发生阻塞。
|
||||
- **通知(又称为单向请求)**
|
||||
|
||||
客户端向服务发送请求,但不要求响应。
|
||||
- **请求/异步响应**
|
||||
|
||||
客户端向服务发送请求,服务异步响应。客户端在等待时不发生阻止,适用于假设响应可能不会立即到达的场景。
|
||||
|
||||
一对多交互可分为以下列举的类型,它们都是异步的:
|
||||
|
||||
- **发布/订阅** - 客户端发布通知消息,由零个或多个感兴趣的服务消费。
|
||||
- **发布/异步响应** - 客户端发布请求消息,然后等待一定时间来接收消费者的响应。
|
||||
- **发布/订阅**
|
||||
|
||||
客户端发布通知消息,由零个或多个感兴趣的服务消费。
|
||||
- **发布/异步响应**
|
||||
|
||||
客户端发布请求消息,然后等待一定时间来接收消费者的响应。
|
||||
|
||||
通常,每个服务都组合着使用这些交互方式。对于一些服务,单一的 IPC 机制就足够了,但其他服务可能需要组合多个 IPC 机制。
|
||||
|
||||
@ -52,41 +66,59 @@
|
||||
|
||||
现在我们来看一下交互方式,我们先来看看如何定义 API。
|
||||
|
||||
## 3.3、定义 API
|
||||
服务 API 是服务与客户端之间的契约。无论您选择何种 IPC 机制,使用接口定义语言(interface definition language,IDL)严格定义服务 API 都是非常有必要的。有论据证明使用 [API 优先(API‑first)法](https://www.programmableweb.com/news/how-to-design-great-apis-api-first-design-and-raml/how-to/2015/07/10)定义服务更加合适。在对您需要实现的服务的 API 定义进行迭代之后,您可以通过编写接口定义并与客户端开发人员进行审阅来开始开发服务。这样设计可以增加您构建出符合客户端需求的服务的机率。
|
||||
<a id="defining-apis"></a>
|
||||
|
||||
正如您将会在后面看到,定义 API 的方式取决于您使用何种 IPC 机制。如果您正在使用消息传递,则 API 由消息通道和消息类型组成。如果您使用的是 HTTP,则 API 由 URL、请求和响应格式组成。稍后我们将详细地介绍关于 IDL 方面的内容。
|
||||
## 3.3、定义 API
|
||||
服务 API 是服务与客户端之间的契约。无论您选择何种 IPC 机制,使用接口定义语言(interface definition language,IDL)严格定义服务 API 都是非常有必要的。有论据证明使用 [API 优先(API‑first)法](https://www.programmableweb.com/news/how-to-design-great-apis-api-first-design-and-raml/how-to/2015/07/10)定义服务更加合理。在对您需要实现的服务的 API 定义进行迭代之后,您可以通过编写接口定义并与客户端开发人员进行审阅来开始开发服务。这样设计可以增加您构建出符合客户端需求的服务的机率。
|
||||
|
||||
正如您将会在后面看到,定义 API 的方式取决于您使用何种 IPC 机制。如果您正在使用消息传递,那么 API 由消息通道和消息类型组成。如果您使用的是 HTTP,那么 API 由 URL、请求和响应格式组成。稍后我们将详细地介绍关于 IDL 方面的内容。
|
||||
|
||||
<a id="evolving-apis"></a>
|
||||
|
||||
## 3.4、演化 API
|
||||
服务 API 总是随着时间而变化。在单体应用程序中,更改 API 和更新所有调用者通常是一件直截了当的事。但在基于微服务的应用程序中,即使您的 API 的所有消费者都是同一应用程序中的其他服务,要想完成这些工作也是非常困难的。通常,您无法强制所有客户端与服务升级的节奏一致。此外,您可能需要[逐步部署新版本服务](http://techblog.netflix.com/2013/08/deploying-netflix-api.html),以便新旧版本的服务同时运行。制定这些问题的处理策略还是很重要的。
|
||||
服务 API 总是随着时间而变化。在单体应用程序中,更改 API 和更新所有调用者通常是一件直截了当的事。但在基于微服务的应用程序中,即使您的 API 的所有消费者都是同一应用程序中的其他服务,要想完成这些工作也是非常困难的。通常,您无法强制所有客户端与服务升级的节奏一致。此外,您可能需要[逐步部署服务的新版本](http://techblog.netflix.com/2013/08/deploying-netflix-api.html),以便新旧版本的服务同时运行。制定这些问题的处理策略还是很重要的。
|
||||
|
||||
处理 API 变更的方式取决于变更的程度。某些更改是次要或需要向后兼容以前的版本。例如,您可能会向请求或响应添加属性。这时设计客户与服务时遵守[鲁棒性原则](https://en.wikipedia.org/wiki/Robustness_principle)就显得很有意义了。使用较旧 API 的客户端应继续使用新版本的服务。该服务为缺少的请求属性提供默认值,并且客户端忽略任何多余的响应属性。使用 IPC 机制和消息格式非常重要,可以让您轻松地演化 API。
|
||||
处理 API 变更的方式取决于变更的程度。某些更改是次要或需要向后兼容以前的版本。例如,您可能会向请求或响应添加属性。此时设计客户与服务遵守[鲁棒性原则](https://en.wikipedia.org/wiki/Robustness_principle)就显得很有意义了。使用较旧 API 的客户端应继续使用新版本的服务。该服务为缺少的请求属性提供默认值,并且客户端忽略任何多余的响应属性。使用 IPC 机制和消息格式非常重要,可以让您轻松地演化 API。
|
||||
|
||||
但有时候,您必须对 API 作出大量不兼容的更改。由于您无法强制客户端立即升级,服务也必须支持较旧版本的 API 一段时间。如果您使用了基于 HTTP 的机制(如 REST),则一种方法是将版本号嵌入 URL 中。每个服务实例可能同时处理多个版本。或者,您可以部署多个不同的实例,每个实例用于处理特定版本。
|
||||
|
||||
## 3.5、处理局部故障
|
||||
正如第二章中关于 API 网关所述,在分布式系统中存在局部故障风险。由于客户端进程与服务进程是分开的,服务可能无法及时响应客户端的请求。由于故障或者维护,服务可能需要关闭。也有可能因服务过载,造成响应速度变得极慢。
|
||||
<a id="handling-partial-failure"></a>
|
||||
|
||||
例如,请回想第二章中的产品详细信息场景。我们假设 Recommendation Service 没有响应。客户端天真般的实现可能会无限期地阻塞以等待响应。这不仅会导致用户体验糟糕,而且在许多应用程序中,它将消耗如线程之类等宝贵资源。以致最终,在运行时将线程用完,造成无法响应,如图 3-3 所示。
|
||||
## 3.5、处理局部故障
|
||||
正如[第二章](2-using-an-api-gateway.md)中关于 API 网关所述,在分布式系统中存在局部故障风险。由于客户端进程与服务进程是分开的,服务可能无法及时响应客户端的请求。由于故障或者维护,服务可能需要关闭。也有可能因服务过载,造成响应速度变得极慢。
|
||||
|
||||
例如,请回想[第二章](2-using-an-api-gateway.md)中的产品详细信息场景。我们假设 Recommendation Service 没有响应。客户端天真般的实现可能会无限期地阻塞以等待响应。这不仅会导致用户体验糟糕,而且在许多应用程序中,它将消耗如线程之类等宝贵资源。以致最终,在运行时将线程用完,造成无法响应,如图 3-3 所示。
|
||||
|
||||

|
||||
|
||||
为了防止这个问题出现,您必须设计您的服务以处理局部故障。以下是一个由 [Netflix 给出的好方法](http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html)。处理局部故障的策略包括:
|
||||
为了防止此类问题出现,您必须设计您的服务以处理局部故障。以下是一个由 [Netflix 给出的好办法](http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html)。处理局部故障的策略包括:
|
||||
|
||||
- **网络超时** - 在等待响应时,不要无限期地阻塞,始终使用超时方案。使用超时方案确保资源不被无限地消耗。
|
||||
- **网络超时**
|
||||
|
||||
- **限制未完成的请求数量** - 对客户端拥有特定服务的未完成请求的数量设置上限。如果达到了上限,发出的额外请求可能是毫无意义的,因此这些尝试需要立即失败。
|
||||
在等待响应时,不要无限期地阻塞,始终使用超时方案。使用超时方案确保资源不被无限地消耗。
|
||||
|
||||
- **[断路器模式](http://martinfowler.com/bliki/CircuitBreaker.html)** - 追踪成功和失败请求的数量。如果错误率超过配置阈值,则断开断路器,以便后续的尝试能立即失败。如果出现大量请求失败,则表明服务不可用,发送请求将是无意义的。发生超时后,客户端应重新尝试,如果成功,则关闭断路器。
|
||||
- **限制未完成的请求数量**
|
||||
|
||||
- **提供回退** - 请求失败时执行回退逻辑。例如,返回缓存数据或者默认值,如一组空白的推荐数据。
|
||||
对客户端拥有特定服务的未完成请求的数量设置上限。如果达到了上限,发出的额外请求可能是毫无意义的,因此这些尝试需要立即失败。
|
||||
|
||||
- **[断路器模式](http://martinfowler.com/bliki/CircuitBreaker.html)**
|
||||
|
||||
追踪成功和失败请求的数量。如果错误率超过配置阈值,则断开断路器,以便后续的尝试能立即失败。如果出现大量请求失败,则表明服务不可用,发送请求将是无意义的。发生超时后,客户端应重新尝试,如果成功,则关闭断路器。
|
||||
|
||||
- **提供回退**
|
||||
|
||||
请求失败时执行回退逻辑。例如,返回缓存数据或者默认值,如一组空白的推荐数据。
|
||||
|
||||
[Netflix Hystrix](https://github.com/Netflix/Hystrix) 是一个实现上述和其他模式的开源库。如果您正在使用 JVM,那么您一定要考虑使用 Hystrix。如果您在非 JVM 环境中运行,则应使用相等作用的库。
|
||||
|
||||
<a id="ipc-technologies"></a>
|
||||
|
||||
## 3.6、IPC 技术
|
||||
有多种 IPC 技术可供选择。服务可以使用基于同步请求/响应的通信机制,比如基于 HTTP 的 REST 或 Thrift。或者,可以使用异步、基于消息的通信机制,如 AMQP 或 STOMP。
|
||||
|
||||
还有各种不同的消息格式。服务可以使用人类可读的、基于文本的格式,如 JSON 或 XML。或者,可以使用如 Avro 或 Protocol Buffers 等二进制格式(更加高效)。稍后我们将讨论同步 IPC 机制,但在此之前让我们先来讨论一下异步 IPC 机制。
|
||||
还有各种不同的消息格式。服务可以使用人类可读、基于文本的格式,如 JSON 或 XML。或者,可以使用如 Avro 或 Protocol Buffers 等二进制格式(更加高效)。稍后我们将讨论同步 IPC 机制,但在此之前让我们先来讨论一下异步 IPC 机制。
|
||||
|
||||
<a id="asynchronous-message-based-communication"></a>
|
||||
|
||||
## 3.7、异步、基于消息的通信
|
||||
当使用消息传递时,进程通过异步交换消息进行通信。客户端通过发送消息向服务发出请求。如果服务需要回复,则通过向客户端发送一条单独的消息来实现。由于通信是异步的,因此客户端不会阻塞等待回复。相反,客户端被假定不会立即收到回复。
|
||||
@ -98,9 +130,9 @@
|
||||
|
||||
图 3-4 展示了打车应用程序如何使用发布订阅通道。
|
||||
|
||||

|
||||

|
||||
|
||||
Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来通知已订阅的服务,如 Dispatcher。 Dispatcher 找到可用的司机并通过向发布订阅通道写入 Driver Proposed 消息来通知其他服务。
|
||||
Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来通知已订阅的服务,如 Dispatcher。Dispatcher 找到可用的司机并通过向发布订阅通道写入 Driver Proposed 消息来通知其他服务。
|
||||
|
||||
有许多消息系统可供选择。您应该选择一个支持多种编程语言的。
|
||||
|
||||
@ -110,18 +142,32 @@ Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来
|
||||
|
||||
使用消息传递有很多优点:
|
||||
|
||||
- **将客户端与服务分离** - 客户端通过向相应的通道发送一条消息来简单地发出一个请求。服务实例对客户端而言是透明的。客户端不需要使用发现机制来确定服务实例的位置。
|
||||
- **消息缓冲** - 使用如 HTTP 的同步请求/响应协议,客户端和服务在交换期间必须可用。相比之下,消息代理会将消息写入通道入队,直到消费者处理它们。这意味着,例如,即使订单执行系统出现缓慢或不可用的情况,在线商店还是可以接受客户的订单。订单消息只需要简单地排队。
|
||||
- **灵活的客户端-服务交互** - 消息传递支持前面提到的所有交互方式。
|
||||
- **毫无隐瞒的进程间通信** - 基于 RPC 的机制试图使调用远程服务看起来与调用本地服务相同。然而,由于物理因素和局部故障的可能性,他们实际上是完全不同的。消息传递使这些差异变得非常明显,所以开发人员不会被这些虚假的安全感所欺骗。
|
||||
- **将客户端与服务分离**
|
||||
|
||||
客户端通过向相应的通道发送一条消息来简单地发出一个请求。服务实例对客户端而言是透明的。客户端不需要使用发现机制来确定服务实例的位置。
|
||||
- **消息缓冲**
|
||||
|
||||
使用如 HTTP 的同步请求/响应协议,客户端和服务在交换期间必须可用。相比之下,消息代理会将消息写入通道入队,直到消费者处理它们。这意味着,例如,即使订单执行系统出现缓慢或不可用的情况,在线商店还是可以接受客户的订单。订单消息只需要简单地排队。
|
||||
- **灵活的客户端 — 服务交互**
|
||||
|
||||
消息传递支持前面提到的所有交互方式。
|
||||
- **毫无隐瞒的进程间通信**
|
||||
|
||||
基于 RPC 的机制试图使调用远程服务看起来与调用本地服务相同。然而,由于物理因素和局部故障的可能性,他们实际上是完全不同的。消息传递使这些差异变得非常明显,所以开发人员不会被这些虚假的安全感所欺骗。
|
||||
|
||||
然而,消息传递也存在一些缺点:
|
||||
|
||||
- **额外的复杂操作** - 消息传递系统是一个需要安装、配置和操作的系统组件。消息代理程序必须高度可用,否则系统的可靠性将受到影响。
|
||||
- **实施基于请求/响应的交互的复杂性** - 请求/响应式交互需要做些工作来实现。每个请求消息必须包含应答通道标识符和相关标识符。该服务将包含相关 ID 的响应消息写入应答信道。客户端使用相关 ID 将响应与请求相匹配。通常使用直接支持请求/响应的 IPC 机制更加容易。
|
||||
- **额外的复杂操作**
|
||||
|
||||
消息传递系统是一个需要安装、配置和操作的系统组件。消息代理程序必须高度可用,否则系统的可靠性将受到影响。
|
||||
- **实施基于请求/响应式交互的复杂性**
|
||||
|
||||
请求/响应式交互需要做些工作来实现。每个请求消息必须包含应答通道标识符和相关标识符。该服务将包含相关 ID 的响应消息写入应答信道。客户端使用相关 ID 将响应与请求相匹配。通常使用直接支持请求/响应的 IPC 机制更加容易。
|
||||
|
||||
现在我们已经了解了使用基于消息的 IPC,让我们来看看请求/响应的 IPC。
|
||||
|
||||
<a id="synchronous-request-response-ipc"></a>
|
||||
|
||||
## 3.8、同步的请求/响应 IPC
|
||||
当使用基于同步、基于请求/响应的 IPC 机制时,客户端向服务器发送请求。该服务处理该请求并返回响应。
|
||||
|
||||
@ -129,14 +175,16 @@ Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来
|
||||
|
||||
有许多协议可供选择。有两种流行协议分别是 REST 和 Thrift。我们先来看一下 REST。
|
||||
|
||||
<a id="rest"></a>
|
||||
|
||||
### 3.8.1、REST
|
||||
如今,开发 [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) 风格的 API 是很流行的。REST 是一种使用了 HTTP (几乎总是)的 IPC 机制。
|
||||
|
||||
资源是 REST 中的一个关键概念,它通常表示业务对象,如客户、产品或这些业务对象的集合。REST 使用 HTTP 动词(谓词)来操纵资源,这些资源通过 URL 引用。例如,GET 请求返回一个资源的表示形式,可能是 XML 文档或 JSON 对象形式。POST 请求创建一个新资源,PUT 请求更新一个资源。
|
||||
资源是 REST 中的一个关键概念,它通常表示业务对象,如客户、产品或这些业务对象的集合。REST 使用 HTTP 动词(谓词)来操纵资源,这些资源通过 URL 引用。例如,GET 请求返回一个资源的表述形式,可能是 XML 文档或 JSON 对象形式。POST 请求创建一个新资源,PUT 请求更新一个资源。
|
||||
|
||||
引用 REST 创建者 Roy Fielding:
|
||||
|
||||
> “REST 提供了一套架构约束,当作为整体应用时,其强调组件交互的可扩展性、接口的通用性、组件的独立部署以及中间组件,以减少交互延迟、实施安全性和封装传统系统。” - Roy Fielding,[《架构风格与基于网络的软件架构设计》](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)
|
||||
> “REST 提供了一套架构约束,当应用为整体时,其强调组件交互的可扩展性、接口的通用性、组件的独立部署以及中间组件,以减少交互延迟、实施安全性和封装传统系统。” — **Roy Fielding,[《架构风格与基于网络的软件架构设计》](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)**
|
||||
|
||||
图 3-5 展示了打车应用程序可能使用 REST 的方式之一。
|
||||
|
||||
@ -144,14 +192,22 @@ Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来
|
||||
|
||||
乘客的智能手机通过向 Trip Management 服务的 `/trips` 资源发出一个 POST 请求来请求旅程。该服务通过向 Passenger Management 服务发送一个获取乘客信息的 GET 请求来处理该请求。在验证乘客被授权创建旅程后,Trip Management 服务将创建旅程,并向智能手机返回 201 响应。
|
||||
|
||||
许多开发人员声称其基于 HTTP 的 API 就是 RESTful。然而,正如 Fielding 在这篇博文中所描述的那样,并不是所有的都是这样。
|
||||
许多开发人员声称其基于 HTTP 的 API 就是 RESTful。然而,正如 Fielding 在这篇博文中所描述的那样,并不是都是这样。
|
||||
|
||||
Leonard Richardson 定义了一个非常有用的 [ REST 成熟度模型](https://martinfowler.com/articles/richardsonMaturityModel.html),包括以下层次:
|
||||
|
||||
- **级别 0** - 级别 0 的 API 的客户端通过向其唯一的 URL 端点发送 HTTP POST 请求来调用该服务。每个请求被指定要执行的操作、操作的目标(如业务对象)以及参数。
|
||||
- **级别 1** - 级别 1 的 API 支持资源概念。要对资源执行操作,客户端会创建一个 POST 请求,指定要执行的操作和参数。
|
||||
- **级别 2** - 级别 2 的 API 使用 HTTP 动词(谓词)执行操作:使用 GET 检索、使用 POST 创建和使用 PUT 进行更新。请求查询参数和请求体(如果有)指定操作的参数。这使服务能够利用 Web 基础特性,如缓存 GET 请求。
|
||||
- **级别 3级** - 级别 3 的 API 基于非常规命名原则设计,HATEOAS(超文本作为应用程序状态引擎)。基本思想是 GET 请求返回的资源的表示,包含用于执行该资源上允许的操作的链接。例如,客户端可以使用发送 GET 请求检索订单返回的订单响应中的链接来取消订单。HATEOAS 的一个[好处](http://www.infoq.com/news/2009/04/hateoas-restful-api-advantages)是不再需要将 URL 硬编码在客户端代码中。另一个好处是,由于资源的表示包含可允许操作的链接,所以客户端不必猜测可以对当前状态的资源执行什么操作。
|
||||
- **级别 0**
|
||||
|
||||
级别 0 的 API 的客户端通过向其唯一的 URL 端点发送 HTTP POST 请求来调用该服务。每个请求被指定要执行的操作、操作的目标(如业务对象)以及参数。
|
||||
- **级别 1**
|
||||
|
||||
级别 1 的 API 支持资源概念。要对资源执行操作,客户端会创建一个 POST 请求,指定要执行的操作和参数。
|
||||
- **级别 2**
|
||||
|
||||
级别 2 的 API 使用 HTTP 动词(谓词)执行操作:使用 GET 检索、使用 POST 创建和使用 PUT 进行更新。请求查询参数和请求体(如果有)指定操作的参数。这使服务能够利用 Web 基础特性,如缓存 GET 请求。
|
||||
- **级别 3**
|
||||
|
||||
级别 3 的 API 基于非常规命名原则设计,HATEOAS(Hypermedia as the engine of application state,超媒体即应用程序状态引擎)。基本思想是 GET 请求返回的资源的表述,包含用于执行该资源上允许的操作的链接。例如,客户端可以使用发送 GET 请求检索订单返回的订单响应中的链接来取消订单。HATEOAS 的一个[好处](http://www.infoq.com/news/2009/04/hateoas-restful-api-advantages)是不再需要将 URL 硬编码在客户端代码中。另一个好处是,由于资源的表示包含可允许操作的链接,所以客户端不必猜测可以对当前状态的资源执行什么操作。
|
||||
|
||||
使用基于 HTTP 的协议有很多好处:
|
||||
- HTTP 简单易懂。
|
||||
@ -163,31 +219,39 @@ Leonard Richardson 定义了一个非常有用的 [ REST 成熟度模型](https:
|
||||
使用 HTTP 也存在一些缺点:
|
||||
- HTTP 仅直接支持请求/响应的交互方式。您可以使用 HTTP 进行通知,但服务器必须始终发送 HTTP 响应。
|
||||
- 因为客户端和服务直接通信(没有一个中间者来缓冲消息),所以它们必须在交换期间都运行。
|
||||
- 客户端必须知道每个服务实例的位置(即 URL)。如第二章关于 API 网关所述,这是现代应用程序中的一个不简单的问题。客户端必须使用服务发现机制来定位服务实例。
|
||||
- 客户端必须知道每个服务实例的位置(即 URL)。如[第二章](2-using-an-api-gateway.md)关于 API 网关所述,这是现代应用程序中的一个复杂问题。客户端必须使用服务发现机制来定位服务实例。
|
||||
|
||||
开发人员社区最近重新发现了 RESTful API 接口定义语言的价值。有几个可以选择,包括 [RAML](https://raml.org/) 和 [Swagger](http://swagger.io/)。一些 IDL(如 Swagger)允许您定义请求和响应消息的格式。其他如 RAML,需要您使用一个单独的规范,如 [JSON 模式](http://json-schema.org/)。除了用于描述 API 之外,IDL 通常还具有可从接口定义生成客户端 stub 和服务器 skeleton 的工具。
|
||||
|
||||
### 3.8.2、Thrift
|
||||
[Apache Thrift](https://thrift.apache.org/) 是 REST 的一个有趣的替代方案。它是一个用于编写跨语言 [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) 客户端和服务器 skeleton。Thrift 提供了一个 C 风格的 IDL 来定义您的 API。您可以使用 Thrift 编译器生成客户端存根和服务器端骨架。编译器可以生成各种语言的代码,包括 C++、Java、Python、PHP、Ruby、Erlang 和 Node.js。
|
||||
<a id="thrift"></a>
|
||||
|
||||
Thrift 接口由一个或多个服务组成。一个服务定义类似于一个 Java 接口。它是强类型方法的集合。Thrift 方法可以返回一个(可能为 void)值,或者如果它们被定义为单向,则不会返回值。返回值方法实现了请求/响应的交互方式,客户端等待响应,并可能会抛出异常。单向方式对应通知互动方式,服务器不发送响应。
|
||||
### 3.8.2、Thrift
|
||||
[Apache Thrift](https://thrift.apache.org/) 是 REST 的一个有趣的替代方案。它是一个用于编写跨语言 [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) 客户端和服务器 skeleton。Thrift 提供了一个 C 风格的 IDL 来定义您的 API。您可以使用 Thrift 编译器生成客户端 stub 和服务器端 skeleton。编译器可以生成各种语言的代码,包括 C++、Java、Python、PHP、Ruby、Erlang 和 Node.js。
|
||||
|
||||
Thrift 接口由一个或多个服务组成。服务定义类似于一个 Java 接口。它是强类型方法的集合。Thrift 方法可以返回一个(可能为 void)值,或者如果它们被定义为单向,则不会返回值。返回值方法实现了请求/响应的交互方式,客户端等待响应,并可能会抛出异常。单向方式对应通知互动方式,服务器不发送响应。
|
||||
|
||||
Thrift 支持多种消息格式:JSON,二进制和压缩二进制。二进制比 JSON 更有效率,因为其解码速度更快。而且,顾名思义,压缩二进制是一种节省空间的格式。当然,JSON 是人性化和浏览器友好的。Thrift 还为您提供了包括原始 TCP 和 HTTP 在内的传输协议选择。原始 TCP 可能比 HTTP 更有效率。然而,HTTP 是防火墙友好的、浏览器友好的和人性化的。
|
||||
|
||||
<a id="message-formats"></a>
|
||||
|
||||
## 3.9、消息格式
|
||||
我们已经了解了 HTTP 和 Thrift,现在让我们来看看消息格式的问题。如果您使用的是消息系统或 REST,则可以选择您的消息格式。其他 IPC 机制如 Thrift 可能只支持少量的消息格式,甚至只支持一种。在任一种情况下,使用跨语言消息格式就显得非常重要了。即使您现在是以单一语言编写您的微服务,您将来也可能会使用到其他语言。
|
||||
我们已经了解了 HTTP 和 Thrift,现在让我们来看看消息格式的问题。如果您使用的是消息系统或 REST,则可以选择自己的消息格式。其他 IPC 机制如 Thrift 可能只支持少量的消息格式,甚至只支持一种。在任一种情况下,使用跨语言消息格式就显得非常重要了。即使您现在是以单一语言编写您的微服务,您将来也可能会使用到其他语言。
|
||||
|
||||
有两种主要的消息格式:文本和二进制。基于文本格式的例子有 JSON 和 XML。这些格式的优点在于,它们不仅是人类可读的,而且是自描述的。在 JSON 中,对象的属性由键值对集合表示。类似地,在 XML 中,属性由命名元素和值表示。这使得消息消费者能够挑选其感兴趣的值并忽略其余的值。因此,可以轻松地向后兼容作出微小更改的消息格式。
|
||||
|
||||
XML 文档的结构由 [XML 模式](https://www.w3.org/XML/Schema)(schema)指定。随着时间的推移,开发人员社区已经意识到 JSON 也需要一个类似的机制。一个选择是使用 [JSON Schema](http://json-schema.org/),独立或作为 IDL 的一部分,如 Swagger。
|
||||
XML 文档的结构由 [XML 模式](https://www.w3.org/XML/Schema)(schema)指定。随着时间的推移,开发人员社区已经意识到 JSON 也需要一个类似的机制。一个选择是使用 [JSON Schema](http://json-schema.org/),无论独立或作为 IDL 的一部分,如 Swagger。
|
||||
|
||||
使用基于文本的消息格式的缺点是消息往往是冗长的,特别是 XML。因为消息是自描述的,每个消息除了它们的值之外还包含属性的名称。另一个缺点是解析文本的开销。因此,您可能需要考虑使用二进制格式。
|
||||
|
||||
有几种二进制格式可供选择。如果您使用的是 Thrift RPC,您可以使用二进制 Thrift。如果您选择的消息格式,包括了流行的 [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) 和 [Apache Avro](https://avro.apache.org/)。这两种格式都提供了一种用于定义消息结构的类型 IDL。然而,一个区别是 Protocol Buffers 使用标记字段,而 Avro 消费者需要知道模式才能解释消息。因此,Protocol Buffers 的 API 演化比 Avro 更容易使用。这里有篇[博文](http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html)对 Thrift、Protocol Buffers 和 Avro 作出了极好的比较。
|
||||
|
||||
<a id="summary"></a>
|
||||
|
||||
## 3.10、总结
|
||||
微服务必须使用进程间通信机制进行通信。在设计服务如何进行通信时,您需要考虑各种问题:服务如何交互、如何为每个服务指定 API、如何演变 API 以及如何处理局部故障。微服务可以使用两种 IPC 机制:异步消息传递和同步请求/响应。为了进行通信,一个服务必须能够找到另一个服务。在第四章中,我们将介绍微服务架构中服务发现问题。
|
||||
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:NGINX 与应用程序架构
|
||||
|
||||
by Floyd Smith
|
||||
@ -200,9 +264,6 @@ NGINX 使您能够实现各种伸缩和镜像操作,使您的应用程序更
|
||||
|
||||
NGINX 本质上非常适合四层架构,从客户端层的媒体流,到交付层的负载均衡与缓存、聚合层的高性能和安全的基于 API 的通信的工具,以及服务层中支持灵活管理的短暂服务实例。
|
||||
|
||||
同样的灵活性使得可以实现强大的伸缩和镜像模式,以处理流量变化,防止安全攻击,此外还提供可用的故障配置切换,从而实现高可用。
|
||||
同样的灵活性使得 NGINX 可以实现强大的伸缩和镜像模式,以处理流量变化,防止安全攻击,此外还提供可用的故障配置切换,从而实现高可用。
|
||||
|
||||
在更为复杂的架构中,包括服务实例实例化和需求不断的服务发现,解耦的进程间通信往往更受青睐。异步和一对多通信方式可能比高耦合的通信方式更加灵活,它们最终提供更高的性能和可靠性。
|
||||
|
||||
## 本系列全部译文
|
||||
- [https://github.com/oopsguy/microservices-from-design-to-deployment-chinese](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese)
|
||||
在更为复杂的架构中,包括服务实例实例化和需求不断的服务发现,解耦的进程间通信往往更受青睐。异步和一对多通信方式可能比高耦合的通信方式更加灵活,它们最终提供更高的性能和可靠性。
|
||||
@ -1,5 +1,7 @@
|
||||
# 4、服务发现
|
||||
本书主要介绍如何使用微服务来构建应用程序,现在是第四章。第一章已经介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),并讨论了使用微服务的优点与缺点。第二章和第三章介绍了微服务间的通信,并对不同的通信机制作出对比。在本章中,我们将探讨服务发现(service discovery)相关的内容。
|
||||
本书主要介绍如何使用微服务来构建应用程序,现在是第四章。[第一章](1-introduction-to-microservices.md)已经介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),并讨论了使用微服务的优点与缺点。[第二章](2-using-an-api-gateway.md)和[第三章](5-event-driven-data-management-for-microservices.md)介绍了微服务间的通信,并对不同的通信机制作出对比。在本章中,我们将探讨服务发现(service discovery)相关的内容。
|
||||
|
||||
<a id="why-use-service-discovery"></a>
|
||||
|
||||
## 4.1、为何使用服务发现
|
||||
我们假设您正在编写某些代码,这些代码调用了有 REST API 或 Thrift API 的服务。为了发送一个请求,您的代码需要知道服务实例的网络位置(IP 地址与端口)。在运行于物理硬件上的传统应用中,服务实例的网络位置是相对静态的。例如,您的代码可以从偶尔更新的配置文件中读取网络位置。
|
||||
@ -12,6 +14,8 @@
|
||||
|
||||
有两种主要的服务发现模式:客户端发现(client-side discovery)与服务端发现(server-side discovery)。让我们先来看看客户端发现。
|
||||
|
||||
<a id="the-client-side-discovery-pattern"></a>
|
||||
|
||||
## 4.2、客户端发现模式
|
||||
当使用[客户端发现模式](http://microservices.io/patterns/client-side-discovery.html)时,客户端负责确定可用服务实例的网络位置和请求负载均衡。客户端查询服务注册中心(service registry),它是可用服务实例的数据库。之后,客户端利用负载均衡算法选择一个可用的服务实例并发出请求。
|
||||
|
||||
@ -23,10 +27,12 @@
|
||||
|
||||
[Netflix OSS](https://netflix.github.io/) 提供了一个很好的客户端发现模式示例。[Netflix Eureka](https://github.com/Netflix/eureka) 是一个服务注册中心,它提供了一个用于管理服务实例注册和查询可用实例的 REST API。[Netflix Ribbon](https://github.com/Netflix/ribbon) 是一个 IPC 客户端,可与 Eureka 一起使用,用于在可用服务实例之间使请求负载均衡。本章稍后将讨论 Eureka。
|
||||
|
||||
客户端发现模式有各种优点与缺点。该模式相对比较简单,除了服务注册中心,没有其他移动部件。此外,由于客户端能发现可用的服务实例,因此可以实现智能的,特定于应用程序的负载均衡决策,比如使用一致性哈希。该模式的一个重要缺点是它将客户端与服务注册中心耦合在一起。您必须为服务客户端使用的每种编程语言和框架实现客户端服务发现逻辑。
|
||||
客户端发现模式存在各种优点与缺点。该模式相对比较简单,除了服务注册中心,没有其他移动部件。此外,由于客户端能发现可用的服务实例,因此可以实现智能的,特定于应用程序的负载均衡决策,比如使用一致性哈希。该模式的一个重要缺点是它将客户端与服务注册中心耦合在一起。您必须为服务客户端使用的每种编程语言和框架实现客户端服务发现逻辑。
|
||||
|
||||
现在我们已经了解了客户端发现,接下来让我们看看服务器端发现。
|
||||
|
||||
<a id="the-server-side-discovery-pattern"></a>
|
||||
|
||||
## 4.3、服务端发现模式
|
||||
服务发现的另一种方式是服务端发现模式。图 4-3 展示了该模式的结构:
|
||||
|
||||
@ -42,6 +48,8 @@ HTTP 服务器和负载均衡器(如 [NGINX Plus](https://www.nginx.com/produc
|
||||
|
||||
服务端发现模式有几个优点与缺点。该模式的一个很大的优点是发现的细节从客户端抽象出来。客户端只需向负载均衡器发出请求。这消除了为服务客户端使用的每种编程语言和框架都实现发现逻辑的必要性。另外,如上所述,一些部署环境免费提供此功能。然而,这种模式存在一些缺点。除非负载均衡器由部署环境提供,否则您需要引入这个高可用系统组件,并进行设置和管理。
|
||||
|
||||
<a id="the-service-registry"></a>
|
||||
|
||||
## 4.4、服务注册中心
|
||||
[服务注册中心](http://microservices.io/patterns/service-registry.html)(service registry)是服务发现的一个关键部分。它是一个包含了服务实例网络位置的数据库。服务注册中心必须是高可用和最新的。虽然客户端可以缓存从服务注册中心获得的网络位置,但该信息最终会过期,客户端将无法发现服务实例。因此,服务注册中心由使用了复制协议(replication protocol)来维护一致性的服务器集群组成。
|
||||
|
||||
@ -49,21 +57,31 @@ HTTP 服务器和负载均衡器(如 [NGINX Plus](https://www.nginx.com/produc
|
||||
|
||||
Netflix 通过在每个 Amazon EC2 可用性区域(Availability Zone)中运行一个或多个 Eureka 服务器来[实现高可用](https://github.com/Netflix/eureka/wiki/Configuring-Eureka-in-AWS-Cloud)。每个 Eureka 服务器都运行在具有一个 [Elastic IP 地址](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)的 EC2 实例上。DNS TEXT 记录用于存储 Eureka 集群配置,这是一个从可用性区域到 Eureka 服务器的网络位置列表的映射。当 Eureka 服务器启动时,它将会查询 DNS 以检索 Eureka 群集配置,查找其对等体,并为其分配一个未使用的 Elastic IP 地址。
|
||||
|
||||
经过 Eureka 客户端 - 服务与服务客户端 - 查询 DNS 以发现 Eureka 服务器的网络位置。客户端优先使用相同可用性区域中的 Eureka 服务器,如果没有可用的,则使用另一个可用性区域的 Eureka 服务器。
|
||||
经过 Eureka 客户端 — 服务与服务客户端 — 查询 DNS 以发现 Eureka 服务器的网络位置。客户端优先使用相同可用性区域中的 Eureka 服务器,如果没有可用的,则使用另一个可用性区域的 Eureka 服务器。
|
||||
|
||||
以下列举了其他服务注册中心:
|
||||
|
||||
- **[etcd](https://github.com/coreos/etcd)** - 一个用于共享配置和服务发现的高可用、分布式和一致的键值存储。使用 etcd 的两个著名项目分别为 Kubernetes 和 [Cloud Foundry](http://pivotal.io/platform)。
|
||||
- **[Consul](https://www.consul.io/)** - 一个发现与配置服务工具。它提供了一个 API,可用于客户端注册与发现服务。Consul 可对服务进行健康检查,以确定服务的可用性。
|
||||
- **[Apache ZooKeeper](http://zookeeper.apache.org/)** - 一个被广泛应用于分布式应用程序的高性能协调服务。Apache ZooKeeper 最初是一个 Hadoop 子项目,但现在已经成为一个独立的顶级项目。
|
||||
- **[etcd](https://github.com/coreos/etcd)**
|
||||
|
||||
一个用于共享配置和服务发现的高可用、分布式和一致的键值存储。使用 etcd 的两个著名项目分别为 Kubernetes 和 [Cloud Foundry](http://pivotal.io/platform)。
|
||||
- **[Consul](https://www.consul.io/)**
|
||||
|
||||
一个发现与配置服务工具。它提供了一个 API,可用于客户端注册与发现服务。Consul 可对服务进行健康检查,以确定服务的可用性。
|
||||
- **[Apache ZooKeeper](http://zookeeper.apache.org/)**
|
||||
|
||||
一个被广泛应用于分布式应用程序的高性能协调服务。Apache ZooKeeper 最初是一个 Hadoop 子项目,但现在已经成为一个独立的顶级项目。
|
||||
|
||||
另外,如之前所述,部分系统,如 Kubernetes、Marathon 和 AWS,没有明确的服务注册中心。相反,服务注册中心只是基础设施的一个内置部分。
|
||||
|
||||
现在我们已经了解服务注册中心的概念,接下来让我们看看服务实例是如何被注册到服务注册中心。
|
||||
|
||||
<a id="service-registration-options"></a>
|
||||
|
||||
## 4.5、服务注册方式
|
||||
如之前所述,服务实例必须在服务注册中心中注册与注销。有几种不同的方式来处理注册和注销。一是服务实例自我注册,即[自注册模式](http://microservices.io/patterns/self-registration.html)。另一个是使用其他系统组件来管理服务实例的注册,即[第三方注册模式](http://microservices.io/patterns/3rd-party-registration.html)。我们先来了解自注册模式。
|
||||
|
||||
<a id="the-self-registration-pattern"></a>
|
||||
|
||||
## 4.6、自注册模式
|
||||
当使用自注册模式时,服务实例负责在服务注册中心注册和注销自己。此外,如果有必要,服务实例将通过发送心跳请求来防止其注册信息过期。
|
||||
|
||||
@ -71,12 +89,14 @@ Netflix 通过在每个 Amazon EC2 可用性区域(Availability Zone)中运
|
||||
|
||||

|
||||
|
||||
该方式的一个很好的范例就是 [Netflix OSS Eureka 客户端](https://github.com/Netflix/eureka)。Eureka 客户端负责处理服务实例注册与注销的所有方面。实现了包括服务发现在内的多种模式的 [Spring Cloud 项目](http://projects.spring.io/spring-cloud/)可以轻松地使用 Eureka 自动注册服务实例。您只需在Java Configuration类应用 `@EnableEurekaClient` 注解即可。
|
||||
该方式的一个很好的范例就是 [Netflix OSS Eureka 客户端](https://github.com/Netflix/eureka)。Eureka 客户端负责处理服务实例注册与注销的所有方面。实现了包括服务发现在内的多种模式的 [Spring Cloud 项目](http://projects.spring.io/spring-cloud/)可以轻松地使用 Eureka 自动注册服务实例。您只需在 Java Configuration 类上应用 `@EnableEurekaClient` 注解即可。
|
||||
|
||||
自注册模式有好有坏。一个好处是它相对简单,不需要任何其他系统组件。然而,主要缺点是它将服务实例与服务注册中心耦合。您必须为服务使用的每种编程语言和框架都实现注册代码。
|
||||
|
||||
将服务与服务注册中心分离的替代方法是第三方注册模式。
|
||||
|
||||
<a id="the-third-party-registration-pattern"></a>
|
||||
|
||||
## 4.7、第三方注册模式
|
||||
当使用第三方注册模式时,服务实例不再负责向服务注册中心注册自己。相反,该工作将由被称为服务注册器(service registrar)的另一系统组件负责。服务注册器通过轮询部署环境或订阅事件来跟踪运行实例集的变更情况。当它检测到一个新的可用服务实例时,它会将该实例注册到服务注册中心。此外,服务注册器可以注销终止的服务实例。
|
||||
|
||||
@ -94,18 +114,22 @@ Netflix 通过在每个 Amazon EC2 可用性区域(Availability Zone)中运
|
||||
|
||||
该模式的一个缺点是,除非部署环境内置,否则您同样需要引入这样的一个高可用的系统组件,并进行设置和管理。
|
||||
|
||||
<a id="summary"></a>
|
||||
|
||||
## 4.8、总结
|
||||
在微服务应用程序中,运行的服务实例集会动态变更。实例具有动态分配的网络位置。因此,为了让客户端向服务发出请求,它必须使用服务发现机制。
|
||||
|
||||
[服务发现](http://microservices.io/patterns/service-registry.html)的一个关键部分是服务注册中心。服务注册中心是一个可用服务实例的数据库。服务注册中心提供了管理 API 和查询 API 的功能。服务实例通过使用管理 API 从服务注册中心注册或者注销。系统组件使用查询 API 来发现可用的服务实例。
|
||||
|
||||
有两种主要的服务发现模式:客户端发现与服务端发现。在使用了客户端服务发现的系统中,客户端查询服务注册中心,选择一个可用实例并发出请求。在使用了服务端发现的系统中,客户端通过路由器进行请求,路由器将查询服务注册中心,并将请求转发到可用实例。
|
||||
有两种主要的服务发现模式:客户端发现与服务端发现。在使用了客户端服务发现的系统中,客户端查询服务注册中心,选择一个可用实例并发出请求。在使用了服务端发现的系统中,客户端通过路由进行请求,路由将查询服务注册中心,并将请求转发到可用实例。
|
||||
|
||||
服务实例在服务注册中心中注册与注销有两种主要方式。一个是服务实例向服务注中心自我注册,即[自注册模式](http://microservices.io/patterns/self-registration.html)。另一个是使用他系统组件代表服务完成注册与注销,即[第三方注册模式](http://microservices.io/patterns/3rd-party-registration.html)。
|
||||
|
||||
在某些部署环境中,您需要使用如 [Netflix Eureka](https://github.com/Netflix/eureka) 或 [Apache ZooKeeper](http://zookeeper.apache.org/) 等服务注册中心来设置您自己的服务发现基础设施。在其他部署环境中,服务发现是内置的,例如,[Kubernetes](https://kubernetes.io/) 和 [Marathon](https://mesosphere.github.io/marathon/docs/service-discovery-load-balancing.html),可以处理服务实例的注册与注销。他们还在每一个扮演服务端发现路由器角色的集群主机上运行一个代理。
|
||||
|
||||
一个 HTTP 反向代理和负载均衡器(如 NGINX)也可以用作服务端发现负载均衡器。服务注册中心可以将路由信息推送给 NGINX,并调用一个正常的配置更新;例如,您可以使用 [Consul Template](https://www.hashicorp.com/blog/introducing-consul-template/)。NGINX Plus 支持[额外的动态重新配置机制](https://www.nginx.com/products/on-the-fly-reconfiguration/) - 它可以使用 DNS 从注册中心中提取有关服务实例的信息,并为远程重新配置提供一个 API。
|
||||
一个 HTTP 反向代理和负载均衡器(如 NGINX)也可以用作服务端发现负载均衡器。服务注册中心可以将路由信息推送给 NGINX,并调用一个正常的配置更新;例如,您可以使用 [Consul Template](https://www.hashicorp.com/blog/introducing-consul-template/)。NGINX Plus 支持[额外的动态重新配置机制](https://www.nginx.com/products/on-the-fly-reconfiguration/) — 它可以使用 DNS 从注册中心中提取有关服务实例的信息,并为远程重新配置提供一个 API。
|
||||
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:NGINX 的灵活性
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
# 5、事件驱动数据管理
|
||||
本书主要介绍如何使用微服务构建应用程序,这是本书的第五章。[第一章](1-introduction-to-microservices.md)介绍了微服务架构模式,讨论了使用微服务的优点与缺点。[第二](2-using-an-api-gateway.md)和[第三章](3-inter-process-communication.md)描述了微服务架构内通信方式的对比。第四章探讨了与服务发现相关的内容。在本章中,我们稍微做了点调整,研究微服务架构中出现的分布式数据管理问题。
|
||||
|
||||
<a id="microservices-and-the-problem-of-distributed-data-management"></a>
|
||||
|
||||
## 5.1、微服务和分布式数据管理问题
|
||||
单体应用程序通常具有一个单一的关系型数据库。使用关系型数据库的一个主要优点是您的应用程序可以使用 [ACID 事务](https://en.wikipedia.org/wiki/ACID),这些事务提供了以下重要保障:
|
||||
|
||||
- **原子性(Atomicity)** - 所作出的改变是原子操作,不可分割
|
||||
- **一致性(Consistency)** - 数据库的状态始终保持一致
|
||||
- **隔离性(Isolation)** - 即使事务并发执行,但他们看起来更像是串行执行
|
||||
- **永久性(Durable)** - 一旦事务提交,它将不可撤销
|
||||
- **原子性(Atomicity)** — 所作出的改变是原子操作,不可分割
|
||||
- **一致性(Consistency)** — 数据库的状态始终保持一致
|
||||
- **隔离性(Isolation)** — 即使事务并发执行,但他们看起来更像是串行执行
|
||||
- **永久性(Durable)** — 一旦事务提交,它将不可撤销
|
||||
|
||||
因此,您的应用程序可以很容易地开始事务、更改(插入、更新和删除)多个行,并提交事务。
|
||||
|
||||
@ -29,6 +31,8 @@ Order Service 无法直接访问 CUSTOMER 表。它只能使用客户服务提
|
||||
|
||||
第二个挑战是如何实现从多个服务中检索数据。例如,我们假设应用程序需要显示一个顾客和他最近的订单。如果 Order Service 提供了用于检索客户订单的 API,那么您可以使用应用程序端连接以检索数据。应用程序从 Customer Service 中检索客户,并从 Order Service 中检索客户的订单。但是,假设 Order Service 仅支持通过主键查找订单(也许它使用了仅支持基于主键检索的 NoSQL 数据库)。在这种情况下,没有有效的方法来检索所需的数据。
|
||||
|
||||
<a id="event-driven-architecture"></a>
|
||||
|
||||
## 5.2、事件驱动架构
|
||||
许多应用使用了[事件驱动架构](https://martinfowler.com/eaaDev/EventNarrative.html)作为解决方案。在此架构中,微服务在发生某些重要事件时发布一个事件,例如更新业务实体时。其他微服务订阅了这些事件,当微服务接收到一个事件时,它可以更新自己的业务实体,这可能导致更多的事件被发布。
|
||||
|
||||
@ -65,9 +69,13 @@ View,并为每个 Customer 存储一个文档。Customer Order View Query Serv
|
||||
|
||||
一个缺点是其编程模型比使用 ACID 事务更加复杂。通常,您必须实现补偿事务以从应用程序级别的故障中恢复。例如,如果信用检查失败,您必须取消订单。此外,应用程序必须处理不一致的数据。因为未提交的事务所做的更改是可见的。如果从未更新的物化视图中读取,应用程序依然可以看到不一致性。另一个缺点是订阅者必须要检测和忽略重复的事件。
|
||||
|
||||
<a id="achieving-atomicity"></a>
|
||||
|
||||
## 5.3、实现原子性
|
||||
在事件驱动架构中,同样存在着原子更新数据库和发布事件相关问题。例如,Order Service 必须在 ORDER 表中插入一行数据,并发布 Order Created 事件。这两个操作必须原子完成。如果在更新数据库后但在发布事件之前发生服务崩溃,系统将出现不一致性。确保原子性的标准方法是使用涉及到数据库和 Message Broker 的分布式事务。然而,由于上述原因,如 CAP 定理,这并不是我们想做的。
|
||||
|
||||
<a id="publishing-events-using-local-transactions"></a>
|
||||
|
||||
## 5.4、使用本地事务发布事件
|
||||
实现原子性的一种方式是应用程序使用[仅涉及本地事务的多步骤过程](http://queue.acm.org/detail.cfm?id=1394128)来发布事件。诀窍在于存储业务实体状态的数据库中有一个用作消息队列的 EVENT 表。应用程序开启一个(本地)数据库事务,更新业务实体状态,将事件插入到 EVENT 表中,之后提交事务。一个单独的应用程序线程或进程查询 EVENT 表,将事件发布到 Message Broker,然后使用本地事务将事件标记为已发布。设计如图 5-6 所示。
|
||||
|
||||
@ -79,6 +87,8 @@ Order Service 将一行记录插入到 ORDER 表中,并将一个 Order Created
|
||||
|
||||
该方法通过让应用程序使用本地事务更新状态和发布事件来消除对 2PC 的依赖。现在我们来看一下通过应用程序简单地更新状态来实现原子性的方法。
|
||||
|
||||
<a id="mining-a-database-transaction-log"></a>
|
||||
|
||||
## 5.5、挖掘数据库事务日志
|
||||
不依靠 2PC 来实现原子性的另一种方式是使用线程或进程发布事件,该线程或进程对数据库的事务或者提交日志进行挖掘。当应用程序更新数据库时,更改信息被记录到数据库的事务日志中。Transaction Log Miner 线程或进程读取事务日志并向 Message Broker 发布事件。设计如图 5-7 所示。
|
||||
|
||||
@ -92,6 +102,8 @@ Order Service 将一行记录插入到 ORDER 表中,并将一个 Order Created
|
||||
|
||||
事务日志挖掘消除了应用程序在做一件事时对 2PC 的依赖:更新数据库。现在我们来看看另一种可以消除更新并仅依赖于事件的不同方式。
|
||||
|
||||
<a id="using-event-sourcing"></a>
|
||||
|
||||
## 5.6、使用事件溯源
|
||||
[事件溯源](https://github.com/cer/event-sourcing-examples/wiki/WhyEventSourcing)通过使用完全不同的、不间断的方式来持久化业务实体,实现无 2PC 原子性。应用程序不存储实体的当前状态,而是存储一系列状态改变事件。该应用程序通过回放事件来重建实体的当前状态。无论业务实体的状态何时发生变化,其都会将新事件追加到事件列表中。由于保存事件是一个单一操作,因此具有原子性。
|
||||
|
||||
@ -107,19 +119,29 @@ Order Service 将一行记录插入到 ORDER 表中,并将一个 Order Created
|
||||
|
||||
事件溯源同样有缺点。这是一种不同而陌生的编程风格,因此存在学习曲线。事件存储仅支持通过主键查找业务实体。您必须使用[命令查询责任分离](https://github.com/cer/event-sourcing-examples/wiki)(CQRS)来实现查询。因此,应用程序必须处理最终一致的数据。
|
||||
|
||||
<a id="summary"></a>
|
||||
|
||||
## 5.7、总结
|
||||
在微服务架构中,每个微服务都有自己私有的数据存储。不同的微服务可能会使用不同的 SQL 或者 NoSQL 数据库。虽然这种数据库架构具有明显的优势,但它创造了一些分布式数据管理挑战。第一个挑战是如何实现维护多个服务间的业务事务一致性。第二个挑战是如何实现从多个服务中检索数据。
|
||||
|
||||
大部分应用使用的解决方案是事件驱动架构。实现事件驱动架构的一个挑战是如何以原子的方式更新状态以及如何发布事件。有几种方法可以实现这点,包括了将数据库作为消息队列、事务日志挖掘和事件溯源。
|
||||
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:NGINX 与存储优化
|
||||
|
||||
by Floyd Smith
|
||||
|
||||
基于微服务的存储方法涉及大数量和各种数据存储,访问和更新数据将变得更加复杂,DevOps 在维护数据一致性方面面临着更大的挑战。NGINX 为这种数据管理提供了重要支持,主要有三个方面:
|
||||
|
||||
1. **数据缓存与微缓存(microcaching)** - 使用 NGINX 缓存静态文件和微缓存应用程序生成的内容可减轻应用程序的负载、提高性能并减少问题的发生。
|
||||
2. **数据存储的灵活性与可扩展性** - 一旦将 NGINX 作为反向代理服务器,您的应用程序在创建、调整大小、运行和调整数据存储服务器的大小时可获得很大的灵活性,以满足不断变化的需求 - 每个服务都拥有自己的数据存储是很重要的。
|
||||
3. **服务监控与管理,包括数据服务** - 随着数据服务器数量的增加,支持复杂操作和具有监控和管理工具显得非常重要。[NGINX Plus](https://www.nginx.com/products/) 内置了这些工具和应用程序性能管理[合作伙伴](https://www.nginx.com/partners/)的接口,如 Data Dog、Dynatrace 和 New Relic。
|
||||
1. **数据缓存与微缓存(microcaching)**
|
||||
|
||||
使用 NGINX 缓存静态文件和微缓存应用程序生成的内容可减轻应用程序的负载、提高性能并减少问题的发生。
|
||||
2. **数据存储的灵活性与可扩展性**
|
||||
|
||||
一旦将 NGINX 作为反向代理服务器,您的应用程序在创建、调整大小、运行和调整数据存储服务器的大小时可获得很大的灵活性,以满足不断变化的需求 - 每个服务都拥有自己的数据存储是很重要的。
|
||||
3. **服务监控与管理,包括数据服务**
|
||||
|
||||
随着数据服务器数量的增加,支持复杂操作和具有监控和管理工具显得非常重要。[NGINX Plus](https://www.nginx.com/products/) 内置了这些工具和应用程序性能管理[合作伙伴](https://www.nginx.com/partners/)的接口,如 Data Dog、Dynatrace 和 New Relic。
|
||||
|
||||
微服务相关的数据管理示例可在 NGINX [微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)的三大模型中找到,其为您设计决策和实施提供了起点。
|
||||
@ -1,6 +1,8 @@
|
||||
# 6、选择部署策略
|
||||
本书内容主要关于如何使用微服务构建应用程序,这是本书的第六章。第一章介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),讨论了使用微服务的优点与缺点。之后的章节讨论了微服务架构的方方面面:[使用 API 网关](2-using-an-api-gateway.md)、[进程间通信](3-inter-process-communication.md)、[服务发现](4-service-discovery.md)和[事件驱动数据管理](5-event-driven-data-management-for-microservices.md)。在本章中,我们将介绍部署微服务的策略。
|
||||
|
||||
<a id="motivations"></a>
|
||||
|
||||
## 6.1、动机
|
||||
部署[单体应用](http://microservices.io/patterns/monolithic.html)程序意味着运行一个或多个相同副本的单个较大的应用程序。您通常会在每个服务器上配置 N 个服务器(物理或虚拟)并运行 M 个应用程序实例。单体应用程序的部署并不总是非常简单,但它比部署微服务应用程序要简单得多。
|
||||
|
||||
@ -8,6 +10,8 @@
|
||||
|
||||
有几种不同的微服务部署模式。我们首先看看单主机多服务实例模式。
|
||||
|
||||
<a id="multiple-service-instances-per-host-pattern"></a>
|
||||
|
||||
## 6.2、单主机多服务实例模式
|
||||
部署微服务的一种方式是使用[单主机多服务实例](http://microservices.io/patterns/deployment/multiple-services-per-host.html)(Multiple Service Instances per Host)模式。当使用此模式时,您可以提供一个或多个物理主机或虚拟主机,并在每个上运行多个服务实例。从多方面来讲,这是应用程序部署的传统方式。每个服务实例在一个或多个主机的标准端口上运行。主机通常被[当作宠物对待](https://www.nginx.com/blog/microservices-at-netflix-architectural-best-practices/#stateless-servers)。
|
||||
|
||||
@ -33,9 +37,13 @@
|
||||
|
||||
正如您所见,尽管这种方式简单,但单主机多服务实例模式确实存在一些明显的缺点。现在让我们来看看可以绕过这些问题部署微服务的其他方式。
|
||||
|
||||
<a id="service-instance-per-host-pattern"></a>
|
||||
|
||||
## 6.3、每个主机一个服务实例模式
|
||||
部署微服务的另一种方式是使用[每个主机一个服务实例](http://microservices.io/patterns/deployment/single-service-per-host.html)(Service Instance per Host)模式。当使用此模式时,您可以在主机上单独运行每个服务实例。这种模式有两种不同形式:每个虚拟机一个服务实例和每个容器一个服务实例模式。
|
||||
|
||||
<a id="service-instance-per-virtual-machine-pattern"></a>
|
||||
|
||||
### 6.3.1、每个虚拟机一个服务实例模式
|
||||
当您使用[每个虚拟机一个服务实例](http://microservices.io/patterns/deployment/service-per-vm.html)模式时,将每个服务打包为一个虚拟机(VM)镜像(如 [Amazon EC2 AMI](https://aws.amazon.com/cn/ec2/))。每个服务实例都是一个使用该 VM 镜像启动的 VM(例如,一个 EC2 实例)。
|
||||
|
||||
@ -67,6 +75,8 @@
|
||||
|
||||
接下来让我们看看另一种部署更轻量级微服务的替代方式,它也有许多与虚拟机一样的优势。
|
||||
|
||||
<a id="service-instance-per-container-pattern"></a>
|
||||
|
||||
### 6.3.2、每个容器一个服务实例模式
|
||||
当您使用[每个容器一个服务实例模式](http://microservices.io/patterns/deployment/service-per-container.html)(Service Instance per Container)模式时,每个服务实例都在其自己的容器中运行。容器是一个[操作系统级虚拟化机制](https://en.wikipedia.org/wiki/Operating-system-level_virtualization)。一个容器是由一个或多个运行在沙箱中的进程组成。从进程的角度来看,它们有自己的端口命名空间和根文件系统。您可以限制容器的内存和 CPU 资源。一些容器实现也具有 I/O 速率限制。容器技术的相关例子有 [Docker](https://www.docker.com/) 和 [Solaris Zones](https://en.wikipedia.org/wiki/Solaris_Containers)。
|
||||
|
||||
@ -92,6 +102,8 @@
|
||||
|
||||
还有一个日益流行的 server-less(无服务器)部署概念,这是一种避免了“在容器中还是在虚拟机中部署服务”问题的方法。接下来我们来看看。
|
||||
|
||||
<a id="serverless-deployment"></a>
|
||||
|
||||
## 6.4、Serverless 部署
|
||||
[AWS Lambda](https://aws.amazon.com/lambda/) 就是一个 serverless 部署技术示例。它支持 Java、Node.js 和 Python 服务。要部署微服务器,请将其打包成 ZIP 文件并将上传到 AWS Lambda。您还要提供元数据,其中包括了被调用来处理请求(又称为事件)的函数的名称。AWS Lambda 自动运行足够的微服务服务实例来处理请求。您只需根据每个请求所用时间和内存消耗来付费。当然,问题往往出现在细节上,您很快注意到了 AWS Lambda 的局限性。但是,作为开发人员的您或组织中的任何人都无需担心服务器、虚拟机或容器的任何方面 ,这非常有吸引力,足以令人难以置信。
|
||||
|
||||
@ -108,9 +120,13 @@ Lambda 函数是无状态服务。它通常通过调用 AWS 服务来处理请
|
||||
|
||||
然而,其也存在一些明显的局限性。Lambda 函数不适用于部署长时间运行的服务,例如消耗第三方消息代理消息的服务。请求必须在 300 秒内完成。服务必须是无状态的,因为理论上,AWS Lambda 可能为每个请求运行一个单独的实例。他们必须使用受支持的语言之一来编写。服务也必须快速启动;否则,他们可能会因超时而终止。
|
||||
|
||||
<a id="summary"></a>
|
||||
|
||||
## 6.5、总结
|
||||
部署微服务应用程序充满着挑战。您可能有数个甚至数百个使用了各种语言和框架编写的服务。每个应用程序都是一个迷你应用程序,有自己特定的部署、资源、扩展和监视需求。有几个微服务部署模式,包括每个虚拟机一个服务实例和每个容器一个服务实例模式。部署微服务的另一个有趣的选择是 AWS Lambda,一种 serverless 方式。在本书的下一章也是最后一章中,我们将介绍如何将单体应用程序迁移到微服务架构。
|
||||
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:使用 NGINX 在不同主机上部署微服务
|
||||
|
||||
by Floyd Smith
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
|
||||
然而,您正工作于大型复杂的单体应用程序上,这是相当不错的机会。您开发和部署应用程序的日常经历是缓慢而痛苦的。微服务似乎是一个遥不可及的天堂。幸运的是,有一些战略可以用来逃离单体地狱。在本文中,我将描述如何将单体应用程序逐渐重构为一组微服务。
|
||||
|
||||
<a id="overview-of-refactoring-to-microservices"></a>
|
||||
|
||||
## 7.1、微服务重构概述
|
||||
单体应用程序转换为微服务的过程是[应用程序现代化](https://en.wikipedia.org/wiki/Software_modernization)的一种形式。这是几十年来开发人员一直在做的事情。因此,在将应用程序重构为微服务时,有一些想法是可以重用的。
|
||||
|
||||
@ -18,6 +20,8 @@ Martin Fowler 将这种应用现代化策略称为[杀手应用](http://www.mart
|
||||
|
||||
让我们来看看能做到这点的不同策略。
|
||||
|
||||
<a id="strategy-1-Stop-digging"></a>
|
||||
|
||||
## 7.2、策略一:停止挖掘
|
||||
[洞穴定律](https://en.wikipedia.org/wiki/Law_of_holes)说到,每当您身处在一个洞穴中,您应该停止挖掘。当您的单体应用变得难以管理时,这是一个很好的建议。换句话说,您应该停止扩张,避免使单体变得更大。这意味着当您要实现新功能时,您不应该向单体添加更多的代码。相反,这一策略的主要思想是将新代码放在独立的微服务中。
|
||||
|
||||
@ -41,12 +45,20 @@ Martin Fowler 将这种应用现代化策略称为[杀手应用](http://www.mart
|
||||
|
||||
然而,这种方法没有解决单体问题。要解决这些问题,您需要分解单体。让我们来看看这样做的策略。
|
||||
|
||||
<a id="strategy-2-split-frontend-and-backend"></a>
|
||||
|
||||
## 7.3、策略二:前后端分离
|
||||
缩小单体应用的一个策略是从业务逻辑层和数据访问层拆分出表现层。一个典型的企业应用由至少三种不同类型的组件组成:
|
||||
|
||||
- **表现层(Presentation Layer,PL)** - 处理 HTTP 请求并实现(REST)API 或基于 HTML 的 Web UI 组件。在具有复杂用户界面的应用中,表现层通常存在大量代码。
|
||||
- **业务逻辑层(Business Logic Layer,BLL)** - 作为应用程序核心,实现业务规则的组件。
|
||||
- **数据访问层(Data Access Layer,DAL)** - 访问基础架构组件的组件,如数据库和消息代理。
|
||||
- **表现层(Presentation Layer,PL)**
|
||||
|
||||
处理 HTTP 请求并实现(REST)API 或基于 HTML 的 Web UI 组件。在具有复杂用户界面的应用中,表现层通常存在大量代码。
|
||||
- **业务逻辑层(Business Logic Layer,BLL)**
|
||||
|
||||
作为应用程序核心,实现业务规则的组件。
|
||||
- **数据访问层(Data Access Layer,DAL)**
|
||||
|
||||
访问基础架构组件的组件,如数据库和消息代理。
|
||||
|
||||
一方面的表现逻辑和另一方的业务和数据访问逻辑之间通常有一个完全的隔离。业务层具有由一个或多个门面组成的粗粒度 API,其封装了业务逻辑组件。这个 API 是一个天然的边界,您可以沿着该边界将单体拆分成两个较小的应用程序。一个应用程序包含表现层。另一个应用程序包含业务和数据访问逻辑。分割后,表现逻辑应用程序对业务逻辑应用程序进行远程调用。
|
||||
|
||||
@ -58,9 +70,13 @@ Martin Fowler 将这种应用现代化策略称为[杀手应用](http://www.mart
|
||||
|
||||
然而,这一策略只是一个局部解决方案。两个应用程序中的一个或两个很可能是一个无法管理的单体。您需要使用第三种策略来消除剩余的整体或单体。
|
||||
|
||||
<a id="strategy-3-extract-services"></a>
|
||||
|
||||
## 7.4、策略三:提取服务
|
||||
第三个重构策略是将庞大的现有模块转变为独立的微服务。每次提取一个模块并将其转换成服务时,单体就会缩小。一旦您转换了足够的模块,单体将不再是一个问题。或者它完全消失,或者变得足够小,它就可以被当做一个服务看待。
|
||||
|
||||
<a id="prioritizing-which-modules-to-convert-into-services"></a>
|
||||
|
||||
### 7.4.1、优先将哪些模块转换为微服务
|
||||
一个庞大而复杂的单体应用由几十个或几百个模块组成,所有模块都是提取的候选项。弄清楚要先转换哪些模块往往存在一定的挑战。一个好的方法是从容易提取的几个模块开始。您将得到微服务的相关经验,特别是在提取过程方面。之后,您应该提取那些能给您最大利益的模块。
|
||||
|
||||
@ -70,6 +86,8 @@ Martin Fowler 将这种应用现代化策略称为[杀手应用](http://www.mart
|
||||
|
||||
当找到要提取的模块时,寻找现有的粗粒度边界(又称为接缝)是有用的。它们使模块转成服务变得更容易和更连廉价。有关这种边界的一个例子是一个仅通过异步消息与应用程序的其他部分进行通信的模块。将该模块转变为微服务相对比较廉价和简单。
|
||||
|
||||
<a id="how-to-extract-a-module"></a>
|
||||
|
||||
## 7.4.2、如何提取模块
|
||||
提取模块的第一步是在模块和单体之间定义一个粗粒度的接口。因为单体需要服务拥有的数据,它很可能是一个双向 API,反之亦然。由于模块和应用程序的其余之间存在着复杂的依赖关系和细粒度的交互模式,因此实现这样的 API 通常存在挑战。由于领域模型类之间的众多关联,使用[领域模型模式](http://martinfowler.com/eaaCatalog/domainModel.html)来实现的业务逻辑尤其具有挑战性。您通常需要进行重大的代码更改才能打破这些依赖。图 7-3 展示了重构。
|
||||
|
||||
@ -83,10 +101,14 @@ Martin Fowler 将这种应用现代化策略称为[杀手应用](http://www.mart
|
||||
|
||||
一旦您提取了一个模块,您就可以独立于单体和任何其他服务开发、部署和扩展其他服务。您甚至可以从头开始重写服务;在这种情况下,整合服务与单体的 API 代码成为在两个领域模型之间转换的防护层。每次提取服务时,您都会朝微服务方向迈近一步。随着时间的推移,单体将缩小,您将拥有越来越多的微服务。
|
||||
|
||||
<a id="summary"></a>
|
||||
|
||||
## 7.5、总结
|
||||
将现有应用程序迁移到微服务的过程是应用程序现代化的一种形式。您不应该从头开始重写您的应用来迁移到微服务。相反,您应该将应用程序逐渐重构为一组微服务。可以使用这三种策略:将新功能实现为微服务;从业务组件和数据访问组件中分离出表现组件;将单体中的现有模块转换为服务。随着时间推移,微服务的数量将会增长,您的开发团队的灵活性和速度也同样会增加。
|
||||
|
||||
## 微服务实战:用 NGINX 驯服单体
|
||||
<a id="microservices-in-action"></a>
|
||||
|
||||
## 微服务实战:用 NGINX 征服单体
|
||||
|
||||
by Floyd Smith
|
||||
|
||||
@ -94,8 +116,14 @@ by Floyd Smith
|
||||
|
||||
您可以通过将 NGINX 放在您现有的单体应用之前,以节省迁移微服务所花费的大量时间。以下简要说明与微服务有关的好处:
|
||||
|
||||
- **更好地支持微服务** — 如第五章最后栏目所述,NGINX 和 [NGINX Plus](https://www.nginx.com/products/) 具有利于开发基于微服务的应用的功能。当您开始重新设计单体应用时,由于 NGINX 的功能,您的微服务将执行得更好、更易于管理。
|
||||
- **跨环境的功能抽象** — 从您管理的服务器到各种公共云、私有云和混合云上将功能迁移到 NGINX 作为反向代理服务器可以减少部署在新的环境中的设施数量变化。这补充扩展了微服务所固有的灵活性。
|
||||
- **NGINX 微服务参考架构可用性** — 当您迁移到 NGINX 时,您可以借鉴 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)(MRA,Microservices Reference Architecture),以便在迁移到微服务之后定义应用程序的最终结构,并根据需要使用的 MRA 部分应用于您创建的每个新的微服务。
|
||||
- **更好地支持微服务**
|
||||
|
||||
如第五章最后栏目所述,NGINX 和 [NGINX Plus](https://www.nginx.com/products/) 具有利于开发基于微服务的应用的功能。当您开始重新设计单体应用时,由于 NGINX 的功能,您的微服务将执行得更好、更易于管理。
|
||||
- **跨环境的功能抽象**
|
||||
|
||||
从您管理的服务器到各种公共云、私有云和混合云上将功能迁移到 NGINX 作为反向代理服务器可以减少部署在新的环境中的设施数量变化。这补充扩展了微服务所固有的灵活性。
|
||||
- **NGINX 微服务参考架构可用性**
|
||||
|
||||
当您迁移到 NGINX 时,您可以借鉴 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)(MRA,Microservices Reference Architecture),以便在迁移到微服务之后定义应用程序的最终结构,并根据需要使用的 MRA 部分应用于您创建的每个新的微服务。
|
||||
|
||||
总而言之,实现NGINX作为您的转型的第一步,将压倒您的单片应用程序,使其更容易获得微服务的所有优势,并为您提供用于进行转换的模型。您可以了解有关 MRA 的更多信息,并获得 NGINX Plus 的免费试用版。
|
||||
147
README.md
147
README.md
@ -1,92 +1,97 @@
|
||||
# 《微服务:从设计到部署》中文版
|
||||
本仓库为电子书 [Designing and Deploying Microservices](https://www.nginx.com/resources/library/designing-deploying-microservices/) 的中文翻译版本,不涉及任何商业利益,纯属个人爱好。
|
||||
本书为 Nginx 微服务电子书 [Designing and Deploying Microservices](https://www.nginx.com/resources/library/designing-deploying-microservices/) 中文版,其从不同角度全面介绍了微服务:微服务的优点与缺点、API 网关、进程间通信(IPC)、服务发现、事件驱动数据管理、微服务部署策略、重构单体。
|
||||
|
||||

|
||||
- [文档仓库:https://github.com/oopsguy/microservices-from-design-to-deployment-chinese](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese)
|
||||
- [在线阅读:http://oopsguy.com/books/microservices](http://oopsguy.com/books/microservices/index.html)
|
||||
|
||||
本书对 Nginx 的描述不是很多,主要针对微服务领域。如果您想了解更多关于 Nginx 的内容,请参阅正在更新的 **[Nginx 中文文档](https://github.com/oopsguy/nginx-docs)**。
|
||||
|
||||

|
||||
|
||||
## 目录
|
||||
|
||||
### [0、前言](0-foreword.md)
|
||||
|
||||
### [1、微服务简介](1-introduction-to-microservices.md#1微服务简介)
|
||||
- [1.1、构建单体应用](1-introduction-to-microservices.md#11构建单体应用)
|
||||
- [1.2、走向单体地狱](1-introduction-to-microservices.md#12走向单体地狱)
|
||||
- [1.3、微服务-解决复杂问题](1-introduction-to-microservices.md#13微服务-解决复杂问题)
|
||||
- [1.4、微服务的优点](1-introduction-to-microservices.md#14微服务的优点)
|
||||
- [1.5、微服务的缺点](1-introduction-to-microservices.md#15微服务的缺点)
|
||||
- [1.6、总结](1-introduction-to-microservices.md#16总结)
|
||||
- [微服务实战:NGINX Plus 作为反向代理服务器](1-introduction-to-microservices.md#微服务实战nginx-plus-作为反向代理服务器)
|
||||
### [1、微服务简介](1-introduction-to-microservices.md)
|
||||
- [1.1、构建单体应用](1-introduction-to-microservices.md#building-monolithic-applications)
|
||||
- [1.2、走向单体地狱](1-introduction-to-microservices.md#marching-toward-monolithic-hell)
|
||||
- [1.3、微服务-解决复杂问题](1-introduction-to-microservices.md#tackling-the-complexity)
|
||||
- [1.4、微服务的优点](1-introduction-to-microservices.md#the-benefits-of-microservices)
|
||||
- [1.5、微服务的缺点](1-introduction-to-microservices.md#the-drawbacks-of-microservices)
|
||||
- [1.6、总结](1-introduction-to-microservices.md#summary)
|
||||
- [微服务实战:NGINX Plus 作为反向代理服务器](1-introduction-to-microservices.md#microservices-in-action)
|
||||
|
||||
### [2、使用 API 网关](2-using-an-api-gateway.md)
|
||||
- [2.1、简介](2-using-an-api-gateway.md#21简介)
|
||||
- [2.2、客户端与微服务直接通信](2-using-an-api-gateway.md#22客户端与微服务直接通信)
|
||||
- [2.3、使用 API 网关](2-using-an-api-gateway.md#23使用API网关)
|
||||
- [2.4、API 网关的优点和缺点](2-using-an-api-gateway.md#24API网关的优点和缺点)
|
||||
- [2.5、实施 API 网关](2-using-an-api-gateway.md#25实施API网关)
|
||||
- [2.5.1、性能与扩展](2-using-an-api-gateway.md#251性能与扩展)
|
||||
- [2.5.2、使用响应式编程模型](2-using-an-api-gateway.md#252使用响应式编程模型)
|
||||
- [2.5.3、服务调用](2-using-an-api-gateway.md#253服务调用)
|
||||
- [2.5.4、服务发现](2-using-an-api-gateway.md#254服务发现)
|
||||
- [2.5.5、处理局部故障](2-using-an-api-gateway.md#255处理局部故障)
|
||||
- [2.6、总结](2-using-an-api-gateway.md#26总结)
|
||||
- [微服务实战:NGINX Plus 作为 API 网关](2-using-an-api-gateway.md#微服务实战nginx-plus-作为-api-网关)
|
||||
- [2.1、简介](2-using-an-api-gateway.md#introduction)
|
||||
- [2.2、客户端与微服务直接通信](2-using-an-api-gateway.md#direct-client-to-microservice-communication)
|
||||
- [2.3、使用 API 网关](2-using-an-api-gateway.md#using-an-api-gateway)
|
||||
- [2.4、API 网关的优点与缺点](2-using-an-api-gateway.md#benefits-and-drawbacks-of-an-api-gateway)
|
||||
- [2.5、实施 API 网关](2-using-an-api-gateway.md#implementing-an-api-gateway)
|
||||
- [2.5.1、性能与扩展](2-using-an-api-gateway.md#performance-and-scalability)
|
||||
- [2.5.2、使用响应式编程模型](2-using-an-api-gateway.md#using-a-reactive-programming-model)
|
||||
- [2.5.3、服务调用](2-using-an-api-gateway.md#service-invocation)
|
||||
- [2.5.4、服务发现](2-using-an-api-gateway.md#service-discovery)
|
||||
- [2.5.5、处理局部故障](2-using-an-api-gateway.md#handling-partial-failures)
|
||||
- [2.6、总结](2-using-an-api-gateway.md#summary)
|
||||
- [微服务实战:NGINX Plus 作为 API 网关](2-using-an-api-gateway.md#microservices-in-action)
|
||||
|
||||
### [3、进程间通信](3-inter-process-communication.md)
|
||||
- [3.1、简介](3-inter-process-communication.md#31简介)
|
||||
- [3.2、交互方式](3-inter-process-communication.md#32交互方式)
|
||||
- [3.3、定义 API](3-inter-process-communication.md#33定义api)
|
||||
- [3.4、演化 API](3-inter-process-communication.md#34演化api)
|
||||
- [3.5、处理局部故障](3-inter-process-communication.md#35处理局部故障)
|
||||
- [3.6、IPC 技术](3-inter-process-communication.md#36ipc-技术)
|
||||
- [3.7、异步、基于消息的通信](3-inter-process-communication.md#37异步基于消息的通信)
|
||||
- [3.8、同步的请求/响应 IPC](3-inter-process-communication.md#38同步的请求响应ipc)
|
||||
- [3.8.1、REST](3-inter-process-communication.md#381rest)
|
||||
- [3.8.2、Thrift](3-inter-process-communication.md#382thrift)
|
||||
- [3.9、消息格式](3-inter-process-communication.md#39消息格式)
|
||||
- [3.10、总结](3-inter-process-communication.md#310总结)
|
||||
- [微服务实战:NGINX 与应用程序架构](3-inter-process-communication.md#微服务实战nginx-与应用程序架构)
|
||||
- [3.1、简介](3-inter-process-communication.md#introduction)
|
||||
- [3.2、交互方式](3-inter-process-communication.md#interaction-styles)
|
||||
- [3.3、定义 API](3-inter-process-communication.md#defining-apis)
|
||||
- [3.4、演化 API](3-inter-process-communication.md#evolving-apis)
|
||||
- [3.5、处理局部故障](3-inter-process-communication.md#handling-partial-failure)
|
||||
- [3.6、IPC 技术](3-inter-process-communication.md#ipc-technologies)
|
||||
- [3.7、异步、基于消息的通信](3-inter-process-communication.md#asynchronous-message-based-communication)
|
||||
- [3.8、同步的请求/响应 IPC](3-inter-process-communication.md#synchronous-request-response-ipc)
|
||||
- [3.8.1、REST](3-inter-process-communication.md#rest)
|
||||
- [3.8.2、Thrift](3-inter-process-communication.md#thrift)
|
||||
- [3.9、消息格式](3-inter-process-communication.md#message-formats)
|
||||
- [3.10、总结](3-inter-process-communication.md#summary)
|
||||
- [微服务实战:NGINX 与应用程序架构](3-inter-process-communication.md#microservices-in-action)
|
||||
|
||||
### [4、服务发现](4-service-discovery.md)
|
||||
- [4.1、为何使用服务发现](4-service-discovery.md#41为何使用服务发现)
|
||||
- [4.2、客户端发现模式](4-service-discovery.md#42客户端发现模式)
|
||||
- [4.3、服务端发现模式](4-service-discovery.md#43服务端发现模式)
|
||||
- [4.4、服务注册中心](4-service-discovery.md#44服务注册中心)
|
||||
- [4.5、服务注册方式](4-service-discovery.md#45服务注册方式)
|
||||
- [4.6、自注册模式](4-service-discovery.md#46自注册模式)
|
||||
- [4.7、第三方注册模式](4-service-discovery.md#47第三方注册模式)
|
||||
- [4.8、总结](4-service-discovery.md#48总结)
|
||||
- [微服务实战:NGINX 的灵活性](4-service-discovery.md#微服务实战nginx-的灵活性)
|
||||
- [4.1、为何使用服务发现](4-service-discovery.md#why-use-service-discovery)
|
||||
- [4.2、客户端发现模式](4-service-discovery.md#the-client-side-discovery-pattern)
|
||||
- [4.3、服务端发现模式](4-service-discovery.md#the-server-side-discovery-pattern)
|
||||
- [4.4、服务注册中心](4-service-discovery.md#the-service-registry)
|
||||
- [4.5、服务注册方式](4-service-discovery.md#service-registration-options)
|
||||
- [4.6、自注册模式](4-service-discovery.md#the-self-registration-pattern)
|
||||
- [4.7、第三方注册模式](4-service-discovery.md#the-third-party-registration-pattern)
|
||||
- [4.8、总结](4-service-discovery.md#summary)
|
||||
- [微服务实战:NGINX 的灵活性](4-service-discovery.md#microservices-in-action)
|
||||
|
||||
### [5、事件驱动数据管理](5-event-driven-data-management-for-microservices.md)
|
||||
- [5.1、微服务与分布式数据管理问题](5-event-driven-data-management-for-microservices.md#51微服务与分布式数据管理问题)
|
||||
- [5.2、事件驱动架构](5-event-driven-data-management-for-microservices.md#52事件驱动架构)
|
||||
- [5.3、实现原子性](5-event-driven-data-management-for-microservices.md#53实现原子性)
|
||||
- [5.4、使用本地事务发布事件](5-event-driven-data-management-for-microservices.md#54使用本地事务发布事件)
|
||||
- [5.5、挖掘数据库事务日志](5-event-driven-data-management-for-microservices.md#55挖掘数据库事务日志)
|
||||
- [5.6、使用事件溯源](5-event-driven-data-management-for-microservices.md#56使用事件溯源)
|
||||
- [5.7、总结](5-event-driven-data-management-for-microservices.md#57总结)
|
||||
- [微服务实战:NGINX 与存储优化](5-event-driven-data-management-for-microservices.md#微服务实战nginx-与存储优化)
|
||||
- [5.1、微服务与分布式数据管理问题](5-event-driven-data-management-for-microservices.md#microservices-and-the-problem-of-distributed-data-management)
|
||||
- [5.2、事件驱动架构](5-event-driven-data-management-for-microservices.md#event-driven-architecture)
|
||||
- [5.3、实现原子性](5-event-driven-data-management-for-microservices.md#achieving-atomicity)
|
||||
- [5.4、使用本地事务发布事件](5-event-driven-data-management-for-microservices.md#publishing-events-using-local-transactions)
|
||||
- [5.5、挖掘数据库事务日志](5-event-driven-data-management-for-microservices.md#mining-a-database-transaction-log)
|
||||
- [5.6、使用事件溯源](5-event-driven-data-management-for-microservices.md#using-event-sourcing)
|
||||
- [5.7、总结](5-event-driven-data-management-for-microservices.md#summary)
|
||||
- [微服务实战:NGINX 与存储优化](5-event-driven-data-management-for-microservices.md#microservices-in-action)
|
||||
|
||||
### [6、选择部署策略](6-choosing-deployment-strategy.md)
|
||||
- [6.1、动机](6-choosing-deployment-strategy.md#61动机)
|
||||
- [6.2、单主机多服务实例模式](6-choosing-deployment-strategy.md#62单主机多服务实例模式)
|
||||
- [6.3、每个主机一个服务实例模式](6-choosing-deployment-strategy.md#63每个主机一个服务实例模式)
|
||||
- [6.3.1、每个虚拟机一个服务实例模式](6-choosing-deployment-strategy.md#631每个虚拟机一个服务实例模式)
|
||||
- [6.3.2、每个容器一个服务实例模式](6-choosing-deployment-strategy.md#632每个容器一个服务实例模式)
|
||||
- [6.4、Serverless 部署](6-choosing-deployment-strategy.md#64serverless-部署)
|
||||
- [6.5、总结](6-choosing-deployment-strategy.md#65总结)
|
||||
- [微服务实战:使用 NGINX 在不同主机上部署微服务](6-choosing-deployment-strategy.md#微服务实战使用-nginx-在不同主机上部署微服务)
|
||||
- [6.1、动机](6-choosing-deployment-strategy.md#motivations)
|
||||
- [6.2、单主机多服务实例模式](6-choosing-deployment-strategy.md#multiple-service-instances-per-host-pattern)
|
||||
- [6.3、每个主机一个服务实例模式](6-choosing-deployment-strategy.md#service-instance-per-host-pattern)
|
||||
- [6.3.1、每个虚拟机一个服务实例模式](6-choosing-deployment-strategy.md#service-instance-per-virtual-machine-pattern)
|
||||
- [6.3.2、每个容器一个服务实例模式](6-choosing-deployment-strategy.md#service-instance-per-container-pattern)
|
||||
- [6.4、Serverless 部署](6-choosing-deployment-strategy.md#serverless-deployment)
|
||||
- [6.5、总结](6-choosing-deployment-strategy.md#summary)
|
||||
- [微服务实战:使用 NGINX 在不同主机上部署微服务](6-choosing-deployment-strategy.md#microservices-in-action)
|
||||
|
||||
### [7、重构单体为微服务](7-refactoring-a-monolith-into-microservices.md)
|
||||
- [7.1、微服务重构概述](7-refactoring-a-monolith-into-microservices.md#71微服务重构概述)
|
||||
- [7.2、策略一:停止挖掘](7-refactoring-a-monolith-into-microservices.md#72策略一停止挖掘)
|
||||
- [7.3、策略二:前后端分离](7-refactoring-a-monolith-into-microservices.md#73策略二前后端分离)
|
||||
- [7.4、策略三:提取服务](7-refactoring-a-monolith-into-microservices.md#74策略三提取服务)
|
||||
- [7.4.1、优先将哪些模块转换为微服务](7-refactoring-a-monolith-into-microservices.md#741优先将哪些模块转换为微服务)
|
||||
- [7.4.2、如何提取模块](7-refactoring-a-monolith-into-microservices.md#742如何提取模块)
|
||||
- [7.5、总结](7-refactoring-a-monolith-into-microservices.md#总结)
|
||||
- [微服务实战:用 NGINX 驯服单体](7-refactoring-a-monolith-into-microservices.md#微服务实战用-nginx-驯服单体)
|
||||
- [7.1、微服务重构概述](7-refactoring-a-monolith-into-microservices.md#overview-of-refactoring-to-microservices)
|
||||
- [7.2、策略一:停止挖掘](7-refactoring-a-monolith-into-microservices.md#strategy-1-Stop-digging)
|
||||
- [7.3、策略二:前后端分离](7-refactoring-a-monolith-into-microservices.md#strategy-2-split-frontend-and-backend)
|
||||
- [7.4、策略三:提取服务](7-refactoring-a-monolith-into-microservices.md#strategy-3-extract-services)
|
||||
- [7.4.1、优先将哪些模块转换为微服务](7-refactoring-a-monolith-into-microservices.md#prioritizing-which-modules-to-convert-into-services)
|
||||
- [7.4.2、如何提取模块](7-refactoring-a-monolith-into-microservices.md#how-to-extract-a-module)
|
||||
- [7.5、总结](7-refactoring-a-monolith-into-microservices.md#summary)
|
||||
- [微服务实战:用 NGINX 征服单体](7-refactoring-a-monolith-into-microservices.md#microservices-in-action)
|
||||
|
||||
## Licenses
|
||||

|
||||
## 许可
|
||||

|
||||
|
||||
本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。
|
||||
本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。
|
||||
Loading…
x
Reference in New Issue
Block a user