修改部分内容,让语句更加通顺。

This commit is contained in:
Oopsguy 2019-06-26 23:16:44 +08:00
parent 8a77902780
commit 0380318b34
9 changed files with 292 additions and 210 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.DS_Store
node_modules
_book

View File

@ -1,17 +1,18 @@
# 前言
Floyd Smith
近年来,微服务在应用开发和部署方面取得了显著的进步。将应用开发或重构成微服务以分离服务,通过 API 以明确的方式来相互“对话”。例每个微服务都是自包含self-contained各自维护自己的数据存储(这非常有意义),可以独立更新其他服务。
近年来,微服务在应用开发和部署方面取得了显著的进步。将应用开发或重构成微服务以分离服务,通过 API 以明确的方式相互调用。比每个微服务都是自包含self-contained各自维护自己的数据存储可以独立更新其他服务。
使用基于微服务的方式使得应用程序开发变得更快更容易管理,它只需要较少的人力就能实现更多的功能,可以更快更容易地部署。把应用程序设计成一套微服务,更加容易在多台具有负载均衡的服务器上运行,使其能够轻松应对需求高峰、由于时间推移而平稳增长的需求和由于硬件或者软件问题导致的宕机事故。
微服务使得应用程序开发变得更快更容易管理,它只需要较少的人力就能实现更多的功能,可以更快更容易地部署。将应用程序设计成一套微服务,它更加容易在多台有负载均衡的服务器上运行,而且能轻松应对需求高峰、由于时间推移而平稳增长的需求和由于硬件或者软件问题导致的宕机事故。
微服务的最大进步在于改变了我们的工作方式。敏捷软件开发技术、应用迁移云端、DevOps 文化、持续集成与持续部署CI/CD和容器应用都使用了微服务来革新应用开发与交付。
无论是作为反向代理还是高性能的 web 服务器NGINX 软件都与微服务和上述列出的所有技术有着紧密联系。NGINX 使得基于微服务的应用更加易于开发,确保了微服务解决方案能顺利运行
无论是作为反向代理还是高性能的 web 服务器NGINX 软件都与微服务和上述列出的所有技术有着紧密联系。NGINX 使得基于微服务的应用更加易于开发,确保了微服务解决方案能顺利实施
随着 NGINX 与微服务之间的关系日渐紧密,我们已经在 NGINX 网站上运行了一个由 Chris Richardson 所编写的七部分系列微服务。他很早就参与了设计与实现,他的博文主要涵盖了微服务应用设计与开发方面的内容,包括了如何从单体应用迁移至微服务。博文提供了关于微服务问题的全面概述,非常受欢迎。
随着 NGINX 与微服务之间的关系日渐紧密,我们已经在 NGINX 网站上运行了一个由 Chris Richardson 所编写的七部分系列微服务。他很早就参与了设计与实现,他的博文主要涵盖了微服务应用设计与开发方面的内容,包括了如何从单体应用迁移至微服务,而且提供了关于微服务问题的全面概述,非常受欢迎。
在本书中,我们已经将全篇博文转换成章节,并在每一章节添加了尾栏以展示 NGINX 实现微服务的相关内容。如果您认真听取建议,您将解决许多潜在的开发时甚至是在编写代码之前可能会遇到的问题。此书在关于 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/) 方面也是一本非常不错的书籍,其实现了以下提出的大部分理论。
在本书中,我们已经将全篇博文转换成章节,并在每一章节添加了尾栏以展示 NGINX 实现微服务的相关内容。如果你认真听取建议,你将解决许多潜在的开发时甚至是在编写代码之前可能会遇到的问题。此书在关于 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/) 方面也是一本非常不错的书籍,它实现了以下大部分理论。
本书章节:
@ -21,28 +22,28 @@ Floyd Smith
2. **使用 API 网关**
API 网关是整个微服务应用的单入口,它为每一个微服务提供了 API。NGINX Plus 可以很好地应用于 API 网关,提供了负载均衡和静态文件缓存等功能。
API 网关是整个微服务应用的单入口,它为每一个微服务提供了 API。NGINX Plus 可以很好地应用在 API 网关层,它提供了负载均衡和静态文件缓存等功能。
3. **微服务架构中的进程间通信**
当把一个单体应用分解成几部分(微服务),他们就需要相互通信。事实上有许多进程间通信的方案可供您选择包括表述性状态转义REST。本章将给出详细介绍。
当把一个单体应用分解成几部分,它们就需要相互通信。事实上有许多进程间通信的方案可以选择包括表述性状态转义REST。本章将详细介绍。
4. **微服务架构中的服务发现**
当服务运行在一个动态环境中,想要找到他们并不是一件简单的事情。
当服务运行在一个动态环境中,想要找到它们并不是一件容易的事情。
5. **微服务事件驱动数据管理**
每个微服务维护自己特有的数据展示与存储,而不是共享一个统一、跨越一个(或两个)单体应用的数据存储。虽然这能给予您很大的灵活性,但也可能导致变得复杂。本章可以帮助您理清这些问题。
每个微服务维护自己特有的数据展示与存储,而不是共享一个统一、跨越一个或多个单体应用的数据存储。这种方式很灵活,但也可能导致变得复杂。本章可以帮助你理清这些问题。
6. **选择微服务部署策略**
在 DevOps 世界中您怎样做与您最初要做的事一样重要。Chris 讲解了微服务部署的主要模式,以便您可以为您的应用作出合理的选择,
DevOps 中的每一步都很重要。Chris 讲解了微服务部署的主要模式,以便你可以为应用部署作出合理的选择,
7. **重构单体应用为微服务**
在理想世界里,我们不会缺少时间与金钱,因此可以将核心软件转化为最新最好的技术、工具和方法。然而您可能会发现自己正在将一个单体应用转化为微服务而且进展非常缓慢……。Chris 将在本章中为您讲解明智的做法。
理想环境下,我们不会缺少时间与金钱,因此可以将核心软件转化为最新最好的技术、工具和方法。然而你可能会发现自己正在将一个单体应用重构为微服务而且进展非常缓慢。Chris 在本章中为你讲解明智的做法。
我们认为您将会发现本书的每一章都是值得阅读的,我们希望当您在开发自己的微服务应用时,能应用到本书的内容。
我们认为你会发现本书的每一章节都是值得阅读的,希望当你在开发自己的微服务应用时,能应用到本书的内容。
Floyd SmithNGINX 公司

View File

@ -1,118 +1,126 @@
# 1、微服务简介
如今微服务倍受关注:文章、博客、社交媒体讨论和会议演讲。微服务正在迅速朝着[加德纳技术成熟度曲线Gartner Hype cycle](http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp)的高峰前进。与此同时也有持怀疑态度的软件社区人员认为微服务没什么新鲜可言。反对者声称它的思想只是面向服务架构SOA的重塑。然而无论是炒作还是怀疑不可否认[微服务架构模式](http://microservices.io/patterns/microservices.html)具有非常明显的优势 — 特别是在实施敏捷开发和复杂的企业应用交付方面。
本书的七个章节主要介绍如何设计、构建和部署微服务,这是本书的第一章。在此章节中,您将了解到微服务的由来和与传统[单体应用模式](http://microservices.io/patterns/monolithic.html)的对比。这本电子书描述了许多关于微服务架构方面的内容。无论是在项目意义还是实施方面,您都能了解到微服务架构模式的优点与缺点。
如今微服务倍受关注:文章、博客、社交媒体和会议演讲都在讨论微服务。微服务正在迅速朝着[加德纳技术成熟度曲线Gartner Hype cycle](http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp)的高峰前进。与此同时也有持怀疑态度的软件社区人员认为微服务没什么新鲜可言。反对者声称它的思想只是面向服务架构SOA的重塑。然而无论是炒作还是怀疑不可否认[微服务架构模式](http://microservices.io/patterns/microservices.html)有非常明显的优势 —— 特别是在实施敏捷开发和复杂的企业应用交付方面。
本书的七个章节主要介绍如何设计、构建和部署微服务,这是本书的第一章。在此章节中,你将了解到微服务的由来和其与传统[单体应用模式](http://microservices.io/patterns/monolithic.html)的区别。这本电子书描述了许多关于微服务架构方面的内容。无论是在项目意义还是实施方面,你都能了解到微服务架构模式的优点与缺点。
我们先来看看为什么要考虑使用微服务。
<a id="building-monolithic-applications"></a>
## 1.1、构建单体应用
我们假设,您开始开发一个打车应用,打算与 Uber 和 Hailo 竞争。经过初步交流和需求收集,您开始手动或者使用类似 Rails、Spring Boot、Play 或者 Maven 等平台来生成一个新项目。
该新应用有一个模块化的六边形架构,如图 1-1 所示:
我们假设,你正在开发一个打车应用,打算与 Uber 和 Hailo 竞争。经过初步交流和需求收集,你开始手动或者使用类似 Rails、Spring Boot、Play 或者 Maven 等平台来生成一个新项目。
新应用有一个模块化的[六边形架构](https://www.infoq.com/news/2014/10/exploring-hexagonal-architecture),如图 1-1 所示:
![图 1-1、一个简单的打车应用][image-1]
应用的核心是由模块实现的业务逻辑,定义了服务、领域对象和事件。围绕核心的是与外部世界接口对接的适配器。适配器示例包括数据库访问组件、生产和消费消息的消息组件和暴露了 API 或实现了一个 UI 的 web 组件。
应用的核心是由模块实现的业务逻辑,这些模块定义了服务、领域对象和事件。围绕核心的是与外部世界接口对接的适配器。适配器示例包括数据库访问组件、生产和消费消息的消息组件和暴露了 API 或实现了一个 UI 的 web 组件。
尽管有一个逻辑模块化架构,但应用程序被作为一个单体进行打包和部署。实际格式取决于应用程序的语言和框架。例如,许多 Java 应用程序被打包成 WAR 文件部署在如 Tomcat 或者 Jetty 之类的应用服务器上。其他 Java 应用程序被打包成自包含self-contained的可执行 JAR。类似地Rails 和 Node.js 应用程序被打包为有目录层次的结构
尽管应用有一个逻辑模块化架构,但它被作为一个单体进行打包和部署。实际格式取决于应用所用的语言和框架。比如,许多 Java 应用被打包成 WAR 文件部署在如 Tomcat 或者 Jetty 之类的应用服务器上。还有部分 Java 应用被打包成自包含self-contained的可执行 JAR。类似地Rails 和 Node.js 应用被打包成有一定层次结构的目录
以这种风格编写的应用是很常见的。他们很容易开发,因为我们的 IDE 和其他工具就是专注于构建单体应用。这些应用程序也很容易测试,您可以通过简单地启动并使用如 Selenium 测试包来测试 UI 以轻松地实现端到端end-to-end测试。单体应用同样易于部署你只需拷贝打包好的应用程序到服务器上。您还可以通过运行多个副本和结合负载均衡器来扩展应用。在项目的早期阶段,它可以良好运作。
使用这种风格编写的应用很常见,也很容易开发,因为我们的 IDE 和其他工具本身就专注于构建单体应用。这些应用也很容易测试,你可以通过简单地启动并使用如 Selenium 测试包来测试 UI 以轻松地实现端到端end-to-end测试。单体应用同样易于部署你只需拷贝打包好的应用到服务器上。你还可以通过运行多个副本逼并结合负载均衡来扩展应用。在项目的早期阶段,它可以良好运作。
<a id="marching-toward-monolithic-hell"></a>
## 1.2、走向单体地狱
不幸的是,这种简单的方法有很大的局限性。成功的应用有一个趋势,随着时间推移而变得越来越臃肿。您的开发团队在每个冲刺阶段都要实现更多的用户需求,这意味着需要添加许多行代码。几年之后,小而简单的应用将会逐渐成长成一个[庞大的单体][3]。为了给出一个极端示例我最近和一位开发者做了交谈他正在编写一个工具该工具用于从他们的数百万行代码lines of codeLOC应用中分析出数千个 JAR 之间的依赖。我相信这是大量开发者在多年齐心协力下创造出了这样的野兽。
一旦您的应用程序成为了一个庞大、复杂的单体,您的开发组织可能会陷入了一个痛苦的境地,敏捷开发和交付的任何一次尝试都将原地徘徊。一个主要问题是应用程序实在非常复杂,其对于任何一个开发人员来说显得过于庞大,这是可以理解的。最终,正确修复 bug 和实现新功能变得非常困难而耗时。此外,这种趋势就像是往下的螺旋。如果基本代码都令人难以理解,那么改变也不会变得正确,您最终得到的将是一个巨大且不可思议的[大泥球][4]
不幸的是,这种简单的方式有很大的局限性。成功的应用有一个趋势,随着时间推移而变得越来越臃肿。你的开发团队在每个冲刺阶段都要实现更多的用户需求,这意味着需要添加许多行代码。几年之后,小而简单的应用将逐渐成长成一个[庞大的单体][3]。为了给出一个极端示例我最近和一位开发者做了交谈他正在编写一个工具该工具用于从他们的数百万行代码lines of codeLOC应用中分析出数千个 JAR 之间的依赖。我相信这是大量开发者在多年齐心协力下创造出的野兽
应用程序的规模也将减缓发展。应用程序越大,启动时间越长。我[调查][5]过开发者们的单体应用的大小和性能,一些报告的启动时间为 12 分钟。我也听说过应用程序启动需要 40 分钟以上的怪事。如果开发人员经常要重启应用服务器,那么很大一部分时间都是在等待中度过,他们的生产力将受到限制
一旦应用成为一个庞大、复杂的单体,你的开发组织可能会陷入了一个痛苦的境地,敏捷开发和交付的任何一次尝试都将原地徘徊。一个主要问题是应用实在非常复杂,它对于任何一个开发人员来说都显得过于庞大,这可以理解。而最终,正确修复 bug 和实现新功能变得非常困难而耗时。此外,这种趋势就像是往下的螺旋。如果基础代码都令人难以理解,那么改变也不会变得正确,你最终得到的将是一个巨大的[泥球][4]
另一个大问题是复杂的单体应用本身就是持续部署的障碍。如今SaaS 应用发展到了可以每天多次将变更推送到生产环境。这对于复杂的单体来说非常困难,因为您需要重新部署整个应用程序才能更新其中任何一部分。联想到我之前提到的漫长启动时间,这也不会是什么好事。此外,因变更所产生的影响通常不是很明确,您很可能需要做大量的手工测试。因此,持续部署是不可能做到的
应用的规模也将减缓发展。应用越大,启动时间越长。我[调查][5]过开发者们的单体应用的大小和性能,一些报告的启动时间为 12 分钟。我也听说过应用启动需要 40 分钟以上的怪事。如果开发人员经常要重启应用服务器,那么很大一部分时间都是在等待中度过,他们的生产力将受到限制
当不同模块存在资源需求冲突时,单体应用可能难以扩展。例如,一个模块可能会执行 CPU 密集型图像处理逻辑,理想情况下是部署在 [Amazon EC2 Compute Optimized 实例][6]中。另一个模块可能是一个内存数据库,最适合部署到 [EC2 Memory-optimized 实例][7]。然而,由于这些模块被部署在一起,您必须在硬件选择上做出妥协
另一个大问题是复杂的单体应用本身就是持续部署的障碍。如今SaaS 应用发展到了可以每天多次将变更推送到生产环境。这对于复杂的单体来说非常困难,因为你需要重新部署整个应用才能更新其中任何一部分。联想我之前提到的漫长启动过程,这也不会是什么好事。此外,因变更所产生的影响通常不是很明确,你很可能需要做大量的手工测试。因此,持续部署是不可能做到的
单体应用的另一个问题是可靠性。因为所有模块都运行在同一进程中。任何模块的一个 bug比如内存泄漏可能会拖垮整个进程。此外由于应用程序的所有实例都是相同的该错误将影响到整个应用的可用性
当不同模块存在资源需求冲突时,单体应用可能会难以扩展。例如,一个模块可能会执行 CPU 密集型图像处理逻辑,理想情况下是部署在 [Amazon EC2 Compute Optimized 实例][6]中。另一个模块可能是一个内存数据库,最适合部署到 [EC2 Memory-optimized 实例][7]。然而,由于这些模块被部署在一起,你必须在硬件选择上做出妥协
最后但同样重要,单体应用使得采用新框架和语言变得非常困难。例如,我们假设您有 200 万行代码使用了 XYZ 框架编写。如果使用较新的 ABC 框架来重写整个应用,这将非常昂贵(在时间和成本方面),即使框架非常好。因此,这对于采用新技术是一个非常大的障碍。在项目开始时,您无论选择何种新技术都会感到困扰
单体应用的另一个问题是可靠性。因为所有模块都运行在同一进程中。任何模块的一个 bug比如内存泄漏都可能会拖垮整个进程。此外由于应用的所有实例都是相同的错误将影响到整个应用的可用性
总结一下:您有一个成功的关键业务应用程序,它已经发展成为一个只有少数开发人员(如果有的话)能够理解的巨大单体。它使用了过时、非生产性技术编写,这使得招聘优秀开发人员变得非常困难。应用程序变得难以扩展,不可靠。因此敏捷开发和应用交付是不可能的
最后但同样重要,单体应用使得采用新框架和语言变得非常困难。例如,我们假设你有 200 万行代码使用了 XYZ 框架编写。如果使用较新的 ABC 框架来重写整个应用,这将非常昂贵(时间和成本方面),即使框架非常好。因此,这对于新技术采用是一个非常大的障碍。在项目开始时,你无论选择何种新技术都会感到困扰
那么您能做些什么呢?
总结一下:你有一个成功的关键业务应用,它已经发展成为一个只有少数开发人员能够理解的巨大单体。它使用了过时、非生产性技术编写,使得招聘优秀开发人员变得非常困难。应用变得难以扩展,不可靠。因此敏捷开发和应用交付是不可能的。
那么你能做些什么呢?
<a id="tackling-the-complexity"></a>
## 1.3、微服务 — 解决复杂问题
许多如 Amazon、eBay 和 [Netflix][8] 这样的组织,已经采用现在所谓的[微服务架构模式][9]解决了这个问题,而不是构建一个臃肿的单体应用。它的思路是将应用程序分解成一套较小的互连服务。
一个服务通常实现了一组不同的特性或功能,例如订单管理、客户管理等。每一个微服务都是一个迷你应用,它自己的六边形架构包括了业务逻辑以及多个适配器
许多如 Amazon、eBay 和 [Netflix][8] 这样的组织,已经采用现在所谓的[微服务架构模式][9]解决了这个问题,而不是构建一个臃肿的单体应用。它的思路是将应用分解成一套较小的互连服务
一些微服务会暴露一个供其他微服务或应用客户端消费的 API。其他微服务可能实现了一个 web UI。在运行时每个实例通常是一个云虚拟机virtual machineVM或者一个 Docker 容器。
一个服务通常实现了一组不同的特性或功能,例如订单管理、客户管理等。每个微服务都是一个迷你应用,它自己的六边形架构包括了业务逻辑以及多个适配器。
一些微服务会暴露一个供其他微服务或应用客户端消费的 API。其他微服务可能实现一个 web UI。在运行时每个实例通常是一个云虚拟机virtual machineVM或者一个 Docker 容器。
例如,前面描述的系统可能分解成如图 1-2 所示:
![图 1-2、一个单体应用分解成微服务][image-2]
应用程序的每个功能区域现在都由自己的微服务实现。此外Web 应用程序被划分为一组更简单的应用。例如,以我们的出租车为例,一个是乘客的应用,一个是司机的应用。这使得它更容易地为特定的用户、司机、设备或者专门的用例部署不同的场景。每个后端服务暴露一个 REST API大部分服务消费的 API 由其他服务提供。例如Driver Management 使用了 Notification 服务器来通知可用司机一个可选路程。UI 服务调用了其他服务来渲染页面。服务也可以使用异步、基于消息的通信。本电子书后面将会[更加详细](3-inter-process-communication.md)介绍服务间通信。
应用的每个功能区域现在都由自己的微服务实现。此外Web 应用被划分为一组更简单的应用。例如,以我们的出租车为例,一个是乘客的应用,一个是司机的应用。这样更容易为特定的用户、司机、设备或者专门的用例部署不同的场景。每个后端服务暴露一个 REST API大部分服务消费的 API 由其他服务提供。例如Driver Management 使用了 Notification 服务器来给司机发送一个可选路线通知。UI 服务调用了其他服务来渲染页面。服务也可以使用异步、基于消息的通信。本电子书后面将会[更加详细](3-inter-process-communication.md)介绍服务间通信。
一些 REST API 也暴露给移动端应用供司机和乘客使用。然而,应用不能直接访问后端服务。相反,他们之间的通信是由一个称为 [API 网关][10]API Gateway的中介负责。API 网关负责负载均衡、缓存、访问控制、API 计量和监控,[可以通过使用 NGINX 来实现][11]。[第二章](2-using-an-api-gateway.md)将详细讨论 API 网关。
一些 REST API 也暴露给移动端应用供司机和乘客使用。然而,应用不能直接访问后端服务。相反,他们之间的通信是由一个称为 [API 网关][10]API Gateway的中介负责。API 网关负责负载均衡、缓存、访问控制、API 度量和监控,[可以使用 NGINX 来实现][11]。[第二章](2-using-an-api-gateway.md)将详细讨论 API 网关。
![图 1-3、开发和交付中的伸缩立方Scale Cube][image-3]
微服务架构模式相当于此伸缩立方的 Y 轴坐标,此立方是一个来自[《架构即未来》][12]的三维伸缩模型。另外两个坐标轴是由运行多个相同应用程序副本的负载均衡器组成的 X 轴坐标和 Z 轴坐标(或数据分区),其中请求的属性(例如,一行记录的主键或者客户标识)用于将请求路由到特定的服务器。
微服务架构模式相当于此伸缩立方的 Y 轴坐标,此立方是一个来自[《架构即未来》][12]的三维伸缩模型。另外两个坐标轴是由运行多个相同应用副本的负载均衡器组成的 X 轴坐标和 Z 轴坐标(或数据分区),其中请求的属性(例如,一行记录的主键或者客户标识)用于将请求路由到特定的服务器。
应用程序通常将这三种类型的坐标方式结合一起使用。Y 轴坐标将应用分解成微服务,如图 1-2 所示。
应用通常将这三种类型的坐标方式结合一起使用。Y 轴坐标将应用分解成微服务,如图 1-2 所示。
在运行时X 坐标轴上运行着服务的多个实例,每个服务配合负载均衡器以满足吞吐量和可用性。某些应用程序也有可能使用 Z 坐标轴来进行分区服务。图 1-4 展示了如何用 Docker 将 Trip Management 服务部署到 Amazon EC2 上运行。
在运行时X 坐标轴上运行着服务的多个实例,每个服务配合负载均衡器以满足吞吐量和可用性。某些应用也有可能使用 Z 坐标轴来进行分区服务。图 1-4 展示了如何用 Docker 将 Trip Management 服务部署到 Amazon EC2 上运行。
![图 1-4、使用 Docker 部署 Trip Management 服务][image-4]
在运行时Trip Management 服务由多个服务实例组成,每个服务实例是一个 Docker 容器。为了实现高可用,容器在多个云虚拟机上运行。服务实例之前是一个[类似 NGINX 的负载均衡器][13],用于跨实例分发请求。负载均衡器也可以处理其他问题,如[缓存][14]、[访问控制][15]、[API 度量][16]和[监控][17]。
在运行时Trip Management 服务由多个服务实例组成,每个服务实例是一个 Docker 容器。为了实现高可用,容器在多个云虚拟机上运行。服务实例之前是一个[类似 NGINX 的负载均衡器][13],用于跨实例分发请求。负载均衡器也可以处理其他问题,如[缓存][14]、[访问控制][15]、[API 度量][16]和[监控][17]。
微服务架构模式明显影响到了应用程序与数据库之间的关系与其他共享单个数据库模式schema服务有所不同其每一个服务都有自己的数据库模式。一方面这种做法与企业级数据库数据模型的想相背,此外,它经常导致部分数据冗余。然而,如果想从微服务中受益,每一个服务都应该有自己的数据库模式,因为它能实现松耦合。图 1-5 展示了数据库架构示例应用程序
微服务架构模式明显影响到了应用与数据库之间的关系与其他共享单个数据库模式schema服务有所不同,其每一个服务都有自己的数据库模式。一方面,这种做法与企业级数据库数据模型的想相背,此外,它经常导致部分数据冗余。然而,如果想从微服务中受益,每一个服务都应该有自己的数据库模式,因为它能实现松耦合。图 1-5 展示了数据库架构示例应用。
每个服务都拥有各自的数据库。而且服务可以使用一种最适合其需求、号称多语言持久架构polyglot persistence architecture的数据库。例如Driver Management要找到与潜在乘客接近的司机就必须使用支持高效地理查询的数据库。
![图 1-5、打车应用的数据库架构][image-5]
从表面上看,微服务架构模式类似于 SOA。微服务是由一组服务组成。然而换另一种方式去思考微服务架构模式它是没有商业化的 SOA没有集成 [Web 服务规范][18]WS-\*和企业服务总线Enterprise Service BusESB。基于微服务的应用支持更简单、轻量级的协议例如REST而不是 WS-\*。他们也尽量避免使用 ESB而是实现微服务本身具有类似 ESB 的功能。微服务架构也拒绝了 SOA 的其他部分,例如,数据访问[规范模式][19]概念。
从表面上看,微服务架构模式类似于 SOA。微服务是由一组服务组成。然而换另一种方式去思考微服务架构模式它是没有商业化的 SOA没有集成 [Web 服务规范][18]WS-\*和企业服务总线Enterprise Service BusESB。基于微服务的应用支持更简单的轻量级协议例如REST而不是 WS-\*。它们也尽量避免使用 ESB而是实现微服务本身具有类似 ESB 的功能。微服务架构也拒绝了 SOA 的其他部分,例如,数据访问[规范模式][19]概念。
<a id="the-benefits-of-microservices"></a>
## 1.4、微服务的优点
微服务架构模式有许多非常好的地方。第一它解决了复杂问题。它把可能会变得庞大的单体应用程序分解成一套服务。虽然功能数量不变但是应用程序已经被分解成可管理的块或者服务。每个服务都有一个明确定义边界的方式如远程过程调用RPC驱动或消息驱动 API。微服务架构模式强制一定程度的模块化实际上使用单体代码来实现是极其困难的。因此使用微服务架构模式个体服务能被更快地开发并更容易理解与维护。
微服务架构模式有许多非常好的地方。第一它解决了复杂问题。它把可能会变得庞大的单体应用分解成一套服务。虽然功能数量不变但应用已经被分解成可管理的块或者服务。每个服务都有一个明确的边界定义方式如远程过程调用RPC驱动或消息驱动 API。微服务架构模式强制一定程度的模块化实际上使用单体代码来实现是极其困难的。因此使用微服务架构模式个体服务能被更快地开发并且易于理解和维护。
第二,这种架构使得每个服务都可以由一个团队独立专注开发。开发者可以自由选择任何符合服务 API 契约的技术。当然,更多的组织是希望通过技术选型限制来避免完全混乱的状态。然而,这种自由意味着开发人员不再有可能在这种自由的新项目开始时使用过时的技术。当编写一个新服务时,他们可以选择当前的技术。此外,由于服务较小,使用当前技术重写旧服务将变得更加可行。
第三微服务架构模式可以实现每个微服务独立部署。开发人员根本不需要去协调部署本地变更到服务。这些变更一经测试即可立即部署。比如UI 团队可以执行 A|B 测试,并快速迭代 UI 变更。微服务架构模式使得持续部署成为可能。
第三微服务架构模式可以实现每个微服务独立部署。开发人员根本不需要去协调部署本地变更到服务。这些变更一经测试即可立即部署。比如UI 团队可以执行 A/B 测试,并快速迭代 UI 变更。微服务架构模式使得持续部署成为可能。
最后,微服务架构模式使得每个服务能够独立扩展。您可以仅部署满足每个服务的容量和可用性约束的实例数目。此外,您可以使用与服务资源要求最匹配的硬件。例如,您可以在 EC2 Compute Optimized 实例上部署一个 CPU 密集型图像处理服务,并且在 EC2 Memory-optimized 实例上部署一个内存数据库服务。
最后,微服务架构模式使得每个服务能够独立扩展。你可以仅部署满足每个服务的容量和可用性约束的实例数目。此外,你可以使用与服务资源要求最匹配的硬件。例如,你可以在 EC2 Compute Optimized 实例上部署一个 CPU 密集型图像处理服务,并且在 EC2 Memory-optimized 实例上部署一个内存数据库服务。
<a id="the-drawbacks-of-microservices"></a>
## 1.5、微服务的缺点
就像 Fred Brooks 大约在 30 年前写的[《人月神话》][20]中说的,**没有银弹**。与其他技术一样,微服务架构模式也存在着缺点。其中一个缺点就是名称本身。微服务这个术语的重点过多偏向于服务的规模。事实上,有些开发者主张构建极细粒度的 10 至 100 LOC代码行 服务,虽然这对于小型服务可能比较好,但重要的是要记住,小型服务只是一种手段,而不是主要目标。微服务的目标在于充分分解应用程序以方便应用敏捷开发和部署。
微服务另一个主要缺点是由于微服务是一个分布式系统,其使得整体变得复杂。开发者需要选择和实现基于消息或者 RPC 的进程间通信机制。此外,由于目标请求可能很慢或者不可用,他们必须要编写代码来处理局部故障。虽然这些并不是很复杂、高深,但模块间通过语言级方法/过程调用相互调用,这比单体应用要复杂得多
就像 Fred Brooks 大约在 30 年前写的[《人月神话》][20]中说的,**没有银弹**。与其他技术一样,微服务架构模式也存在着缺点。其中一个缺点就是名称本身。微服务这个术语的重点过多偏向于服务的规模。事实上,有些开发者主张构建极细粒度的 10 至 100 LOC代码行服务虽然这对小型服务来说可能比较好但重要的是小型服务只是一种手段而不是主要目标。微服务的目标在于充分分解应用以方便应用敏捷开发和部署
微服务的另一个挑战是分区数据库架构。更新多个业务实体的业务事务是相当普遍的。这些事务在单体应用中的实现显得微不足道,因为单体只存在一个单独的数据库。在基于微服务的应用程序中,您需要更新不同服务所用的数据库。通常不会选择分布式事务,不仅仅是因为 [CAP 定理][21]。他们根本不支持如今高度可扩展的 NoSQL 数据库和消息代理。您最后不得不使用基于最终一致性的方法,这对于开发人员来说更具挑战性
微服务的另一个主要缺点是由于微服务是一个分布式系统,其使得整体变得复杂。开发者需要选择和实现基于消息或者 RPC 的进程间通信机制。此外,由于目标请求可能很慢或者不可用,他们必须要编写代码来处理局部故障。虽然这些并不是很复杂、高深,但模块间通过语言级方法/过程调用相互调用,这比单体应用要复杂得多
测试微服务应用程序也很复杂。例如,使用现代框架如 Spring Boot只需要编写一个测试类来启动一个单体 web 应用程序并测试其 REST API。相比之下一个类似的测试类对于微服务来说需要启动该服务及其所依赖的所有服务或者至少为这些服务配置存根。再次声明虽然这不是一件高深的事情但不要低估了这样做的复杂性。
微服务的另一个挑战是分区数据库架构。更新多个业务实体的业务事务是相当普遍的。这些事务在单体应用中的实现显得微不足道,因为单体只存在一个单独的数据库。在基于微服务的应用中,你需要更新不同服务所用的数据库。通常不会选择分布式事务,不仅仅是因为 [CAP 定理][21]。他们根本不支持如今高度可扩展的 NoSQL 数据库和消息代理。你最后不得不使用基于最终一致性的方法,这对于开发人员来说更具挑战性。
微服务架构模式的另一个主要挑战是实现了跨越多服务变更。例如,我们假设您正在实现一个变更服务 A、服务 B 和 服务 C 的需求,其中 A 依赖于 B且 B 依赖于 C。在单体应用程序中您可以简单地修改相应的模块、整合变更并一次性部署他们。相反在微服务中您需要仔细规划和协调出现的变更至每个服务。例如您需要更新服务 C然后更新服务 B最后更新服务 A。幸运的是大多数变更只会影响一个服务需要协调的多服务变更相对较少
测试微服务应用也很复杂。例如,使用现代框架如 Spring Boot你只需要编写一个测试类来启动一个单体 web 应用并测试其 REST API。相比之下一个类似的测试类对于微服务来说需要启动该服务及其所依赖的所有服务或者至少为这些服务配置存根。再次强调虽然这不是一件高深的事但不要低估了这样做的复杂性
部署基于微服务的应用程序也是相当复杂的。一个单体应用可以很容易地部署到基于传统负载均衡器的一组相同服务器上。每个应用程序实例都配置有基础设施服务的位置(主机和端口),比如数据库和消息代理。相比之下,微服务应用程序通常由大量的服务组成。例如,据 [Adrian Cockcroft][22] 了解到Hailo 拥有 160 个不同的服务Netflix 拥有的服务超过 600 个
微服务架构模式的另一个主要挑战是实现了跨越多服务变更。例如,我们假设你正在实现一个修改服务 A、服务 B 和 服务 C 的需求,其中 A 依赖于 B且 B 依赖于 C。在单体应用中你可以简单地修改相应的模块、整合变更并一次性部署它们。相反在微服务中你需要仔细规划和协调变更到每个服务。例如你需要更新服务 C然后更新服务 B最后更新服务 A。幸运的是大多数变更只会影响一个服务需要协调的多服务变更相对较少
每个服务都有多个运行时实例。还有更多的移动部件需要配置、部署、扩展和监控。此外,您还需要实现[服务发现机制](4-service-discovery.md)使得服务能够发现需要与之通信的任何其他服务的位置主机和端口。比较传统麻烦的基于票据ticket-based和手动的操作方式无法扩展到如此复杂程度。因此要成功部署微服务应用程序需要求开发人员能高度控制部署方式和高度自动化
部署基于微服务的应用也是相当复杂的。一个单体应用可以很容易地部署到基于传统负载均衡器的一组相同服务器上。每个应用实例都配置有基础设施服务的位置(主机和端口),比如数据库和消息代理。相比之下,微服务应用通常由大量的服务组成。例如,据 [Adrian Cockcroft][22] 了解到Hailo 拥有 160 个不同的服务Netflix 拥有的服务超过 600 个
一种自动化方式是使用现成的平台即服务PaaS如 [Cloud Foundry][23]。PaaS 为开发人员提供了一种简单的方式来部署和管理他们的微服务。它让开发人员避开了诸如采购和配置 IT 资源等烦恼。同时,配置 PaaS 的系统人员和网络专业人员可以确保达到最佳实践以落实公司策略
每个服务都有多个运行时实例。还有更多的移动部件需要配置、部署、扩展和监控。此外,你还需要实现[服务发现机制](4-service-discovery.md)使得服务能够发现需要与之通信的任何其他服务的位置主机和端口。比较传统麻烦的基于票据ticket-based和手动的操作方式无法扩展到如此复杂程度。因此要成功部署微服务应用需要求开发人员能高度控制部署方式和高度自动化
自动化微服务部署的另一个方式是开发自己的 PaaS。一个普遍的起点是使用集群方案如 [Kubernetes][24],与 Docker 等容器技术相结合。在本书最后我们将看到如 NGINX 的[基于软件的应用交付][25]方式是如何在微服务级别处理缓存、访问控制、API 计量和监控,这些可以帮助解决此问题。
一种自动化方式是使用现成的平台即服务PaaS如 [Cloud Foundry][23]。PaaS 为开发人员提供了简单的方式来部署和管理他们的微服务。它让开发人员避开了诸如采购和配置 IT 资源等烦恼。同时,配置 PaaS 的系统人员和网络专业人员可以确保达到最佳实践以落实公司策略。
自动化微服务部署的另一个方式是开发自己的 PaaS。一个普遍的起点是使用集群方案如 [Kubernetes][24],与 Docker 等容器技术相结合。在本书最后我们将看到如 NGINX 的[基于软件的应用交付][25]方式是如何在微服务级别轻松做到缓存处理、访问控制、API 度量和监控,这些可以帮助解决此问题。
<a id="summary"></a>
## 1.6、总结
构建复杂的微服务应用程序本质上是困难的。单体架构模式只适用于简单、轻量级的应用程序,如果您使用它来构建复杂应用,您最终会陷入痛苦的境地。微服务架构模式是复杂、持续发展应用的一个更好的选择。尽管它存在着缺点和实现挑战。
构建复杂的微服务应用本质上是困难的。单体架构模式只适用于简单、轻量级的应用,如果你使用它来构建复杂应用,你最终会陷入痛苦的境地。对于复杂、持续发展的应用而言,微服务架构模式是一个更好的选择。尽管它存在着缺点和挑战。
在后面的章节中,我将介绍微服务架构的方方面面并探讨诸如服务发现、服务部署方案以及将单体应用重构为服务的策略。
@ -122,11 +130,11 @@
By Floyd Smith
[10000 个网站中有超过 50%][26] 使用 NGINX这主要是因为它具有作为反向代理服务器的能力。您可以把 NGINX 放在当前应用程序甚至是数据库服务器之前以获取各种功能 — 更高的性能、更高的安全性、可扩展性、灵活性等。你现有的应用程序只需要配置代码和作出很少或无需改变。然而,对于存在性能压力的站点,或者预计未来存在高负荷,使用 NGINX 的效果看起来可能没那么神奇。
[10000 个网站中有超过 50%][26] 使用 NGINX这主要是因为它具有作为反向代理服务器的能力。你可以把 NGINX 放在当前应用甚至是数据库服务器之前以获取各种功能 — 更高的性能、更高的安全性、可扩展性、灵活性等。你现有的应用只需要配置代码和作出极少的改变,甚至无需改变。然而,对于存在性能压力或者预计未来存在高负荷的站点,使用 NGINX 的效果看起来可能没那么神奇。
那么这与微服务有什么关系呢?实现一个反向代理服务器,并使用 NGINX 的其他功能来为您提供架构灵活性反向代理服务器、静态和应用文件缓存、SSL/TLS 和 HTTP/2 都将从您的应用程序剔除。让应用程序只做它该做的事NGINX 还可作为负载均衡器,这是微服务实施过程中的一个关键角色。先进的 NGINX Plus 的功能包含了复杂的负载均衡算法、多种方式的会话持久和管理监控这些对微服务尤其有用NGINX 最近还增加了使用 DNS SRV 记录的服务发现支持这是一个顶尖的功能。而且如本章所述NGINX 可以自动化部署微服务。
那么这与微服务有什么关系呢?实现一个反向代理服务器,并使用 NGINX 的其他功能来为你提供架构灵活性反向代理服务器、静态和应用文件缓存、SSL/TLS 和 HTTP/2 这些功能都将从你的应用中剔除。让应用只做它该做的事NGINX 还可以作为负载均衡器这是微服务实施过程中的一个关键角色。NGINX Plus 的功能包含了复杂的负载均衡算法、多种方式的会话持久和管理监控这些对微服务尤其有用NGINX 最近还增加了使用 DNS SRV 记录的服务发现支持这是一个顶尖的功能。而且如本章所述NGINX 可以自动化部署微服务。
此外NGINX 还提供了必要的功能来支撑 [NGINX 微服务参考架构][27]中的三大模型。代理模型使用 NGINX 作为 API 网关;网格路由模型使用一个额外的 NGINX 作为进程间通信中枢Fabric 模型中的每个微服务使用一个 NGINX 来控制 HTTP 流量,在微服务之间实现 SSL/TLS这非常具有突破性。
此外NGINX 还提供了必要的功能来支撑 [NGINX 微服务参考架构][27]中的三大模型。代理模型使用 NGINX 作为 API 网关;路由网格模型使用一个额外的 NGINX 作为进程间通信中枢Fabric 模型中的每个微服务使用一个 NGINX 来控制 HTTP 流量,在微服务之间实现 SSL/TLS这非常具有突破性。
[1]: http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp
[2]: http://microservices.io/patterns/monolithic.html

View File

@ -1,14 +1,16 @@
# 2、使用 API 网关
本书的七个章节是关于如何设计、构建和部署微服务。[第一章](1-introduction-to-microservices.md)介绍了微服务架构模式。它阐述了使用微服务的优点与缺点,以及尽管如此,微服务通常是复杂应用的理想选择。该系列的第二章将探讨使用 API 网关构建微服务。
当您选择将应用程序构建成为一组微服务时您需要决定应用程序客户端将如何与微服务进行交互。单体应用程序只有一组端点endpoint通常使用复制replicated结合负载均衡来分配流量
本书的七个章节介绍如何设计、构建和部署微服务。[第一章](1-introduction-to-microservices.md)介绍了微服务架构模式。它阐述了使用微服务的优点与缺点,以及尽管如此,微服务通常是复杂应用的理想选择。该系列的第二章将探讨使用 API 网关构建微服务
然而,在微服务架构中,每个微服务都暴露一组通常比较细颗粒的端点。在本文中,我们将研究如何改进客户端通信,并提出一个使用 API 网关的方案。
当你选择将应用构建成为一组微服务时你需要决定应用客户端将如何与微服务进行交互。单体应用只有一组端点endpoint通常使用副本replicated结合负载均衡来分配流量。
然而,在微服务架构中,通常每个微服务都暴露一组比较细颗粒的端点。在本文中,我们将研究如何改进客户端通信,并提出一个使用 API 网关的方案。
<a id="introduction"></a>
## 2.1、简介
我们假设您正在为一个购物应用开发一个原生移动客户端。您可能需要实现一个产品详细信息页面,用于展示给定商品的信息。
我们假设你正在为一个购物应用开发一个原生移动客户端。你可能需要实现一个产品详细信息页面,用于展示商品信息。
例如,图 2-1 展示了在 Amazon 的 Android 移动应用中滚动产品信息时所看到的内容。
@ -24,13 +26,13 @@
6. 各种推荐,包括了购买此产品的客户购买的其他产品
7. 选择性购买选项
在使用单体应用架构的情况下,移动客户端通过对应用程序进行单个 REST 调用来检索此数据,例如:
在使用单体应用架构的情况下,移动客户端通过对应用进行单个 REST 调用来检索此数据,例如:
```
GET api.company.com/productdetails/productId
```
负载均衡器将请求路由到几个相同应用程序实例中的其中一个。之后,应用程序查询各个数据库表并返回响应给客户端。相比之下,当使用微服务架构时,产品详细页面上展示的数据来自多个微服务。以下是一些微服务,可能拥有给定产品页面展示的数据
负载均衡器将请求路由到几个相同应用实例中的其中一个。之后,应用查询各个数据库表并返回响应给客户端。相比之下,当使用微服务架构时,产品详细页面上展示的数据来自多个微服务。以下是一些产品详细信息数据可能涉及到的微服务
- **订单服务** — 订单历史
- **目录catalog服务** — 基本的产品信息,如产品名称、图片和价格
@ -46,16 +48,18 @@ GET api.company.com/productdetails/productId
<a id="direct-client-to-microservice-communication"></a>
## 2.2、客户端与微服务直接通信
理论上,客户端可以直接向每个微服务发送请求。每个微服务都有一个公开的端点:
```
https://serviceName.api.company.name
```
该 URL 将映射到用于跨可用实例分发请求的微服务负载均衡器。为了检索特定的产品页面信息,移动客户端将向上述的每个微服务发送请求。
不幸的是,这种方式存在着挑战与限制。第一个问题是客户端的需求与每个微服务暴露的细粒度的 API 不匹配。在此示例中客户端需要进行七次单独请求。如果在更加复杂的应用中它可能需要做更多的工作。例如Amazon 展示了在产品页面渲染中如何牵涉到数百个微服务。虽然客户端可以通过 LAN 发送许多请求,但在公共互联网下效率低下,在移动网络必然是不切实际。
不幸的是,这种方式存在着挑战与限制。第一个问题是客户端的需求与每个微服务暴露的细粒度的 API 不匹配。在此示例中,客户端需要进行七次单独请求。如果在更加复杂的应用中它可能需要做更多的工作。例如Amazon 的产品页面渲染牵涉到数百个微服务。虽然客户端可以通过 LAN 发送许多请求,但在公共互联网下效率已经低下,在移动网络必然是不切实际。
客户端直接调用微服务存在的另一个问题是有些可能使用了非 web 友好协议。一个服务可能使用了 Thrift 二进制 RPC而另一个则可能使用 AMQP 消息协议。这两个协议无论是对浏览器还是防火墙都是不友好的,最好是在内部使用。应用程序在防火墙之外应该使用 HTTP 或者 WebSocket 之类的协议。
客户端直接调用微服务的另一个问题是有些服务可能使用了非 web 友好协议。一个服务可能使用了 Thrift 二进制 RPC而另一个则可能使用 AMQP 消息协议。这两个协议无论是对浏览器还是防火墙来说都是不友好的,最好是在内部使用。应用在防火墙之外应该使用 HTTP 或者 WebSocket 之类的协议。
这种方法的另一个缺点是它难以重构微服务。随着时间推移,我们可能会想改变系统划分服务。例如,我们可能会合并两个服务或者将服务拆分为两个或者多个。然而,如果客户端直接与服务进行通信,实施这类的重构将变得非常困难。
@ -64,77 +68,86 @@ https://serviceName.api.company.name
<a id="using-an-api-gateway"></a>
## 2.3、使用 API 网关
通常更好的方法是使用 API 网关。API 网关是一个服务器是系统的单入口点。它类似于面向对象设计模式中的门面Facade模式。API 网关封装了内部系统架构,并针对每个客户端提供一个定制 API。它还可用于认证、监控、负载均衡、缓存和静态响应处理。
通常更好的方式是使用 API 网关。API 网关是一个服务器是系统的单入口点。它类似于面向对象设计模式中的门面Facade模式。API 网关封装了内部系统架构,并针对每个客户端提供一个定制 API。它还可用于认证、监控、负载均衡、缓存和静态响应处理。
图 2-3 展示了 API 通常如何整合架构
![使用 API 网关的微服务](resources/2-3.png)
API 网关负责请求路由、组合和协议转换。所有客户端请求首先要通过 API 网关之后请求被路由到适当的服务。API 网关通常会通过调用多个微服务和聚合结果来处理一个请求。它可以在 Web 协议(如 HTTP 和 WebSocket和用于内部的非 Web 友好协议之间进行转换。
API 网关负责请求路由、组合和协议转换。所有客户端请求首先要通过 API 网关之后请求被路由到适当的服务。API 网关通常会通过调用多个微服务来处理一个请求并聚合结果。它可以在 Web 协议(如 HTTP 和 WebSocket和用于内部的非 Web 友好协议之间进行转换。
API 网关还可以为每个客户端提供一个定制 API。它通常会为移动客户端暴露一个粗粒度的 API。例如考虑一下产品详细信息场景API 网关可以提供一个端点 `/productdetails?productid=xxx`,如图 2-3 所示,一个使用了 API 网关的微服务允许移动客户端通过一个单独的请求来检索所有产品详细信息。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。
[Netflix API 网关](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)就是一个很好的 API 网关案例。Netflix 流媒体服务可在数百种不同类型的设备上使用包括电视机、机顶盒、智能手机、游戏机和平板电脑等。起初Netflix 尝试为他们的流媒体服务提供一个[通用](http://www.programmableweb.com/news/why-rest-keeps-me-night/2012/05/15)的 API。后来他们发现由于设备种类繁多并且他们各自有着不同需求所以并不是能很好地运作。如今他们使用了 API 网关,通过运行特定设备适配代码来为每个设备提供一个定制 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 网关存在这些缺点,但对于大多数的真实应用来说,使用 API 是合理的。
API 网关也存在一些缺点,它本身也是一个高度可用的组件,需要开发、部署和管理。另外,还有一个风险是 API 网关可能会成为开发瓶颈。开发人员必须更新 API 网关来暴露每个微服务的端点。
重要的是更新 API 网关的过程应尽可能地放缓一些。否则,开发人员将被迫排队等待网关更新。尽管 API 网关存在这些缺点,但对于大多数的真实应用来说,使用 API 网关是正确的。
<a id="implementing-an-api-gateway"></a>
## 2.5、实现 API 网关
我们已经了解了使用 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。
[NGINX Plus](https://www.nginx.com/solutions/api-gateway/) 提供了一个成熟、可扩展和高性能的 Web 服务器和反向代理它易于部署、配置和编程。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 网关应该并发执行独立请求
使用传统的异步回调方式来编写 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 网关首先必须检索包含该信息的客户资料,然后检索每个产品的信息。还有一个有趣的 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 网关代码。
<a id="service-invocation"></a>
### 2.5.3、服务调用
一个基于微服务的应用程序是一个分布式系统必须使用一个进程间inter-process通信机制。有两种进程间通信方案。一是使用基于消息的异步机制。某些实现采用了消息代理如 JMS 和 AMQP。其他采用无代理的方式直接与服务通信如 Zeromq。
基于微服务的应用是一个分布式系统必须使用进程间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-service-discovery.md)中更详细地描述了服务发现。现在需要注意的是如果系统使用客户端发现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 网关通过返回默认数据或缓存数据,确保系统发生故障时最小程度上影响到用户体验。
[Netflix Hystrix](https://github.com/Netflix/Hystrix) 是一个非常有用的库用于编写调用远程服务代码。Hystrix 可以使超出指定阈值的调用超时。它实现了断路器模式防止客户端不必要地等待无响应的服务。如果服务的错误率超过指定阈值Hystrix 将会跳闸所有请求将在指定的时间内立即失败。Hystrix 允许您在请求失败时定义回退操作,例如从缓存读取或返回默认值。如果您正在使用 JVM那么您一定要考虑使用 Hystrix。如果您是在非 JVM 环境中运行,则应使用同等作用的库。
[Netflix Hystrix](https://github.com/Netflix/Hystrix) 是一个非常有用的库用于编写调用远程服务代码。Hystrix 可以使超出指定阈值的调用超时。它实现了断路器模式防止客户端不必要地等待无响应的服务。如果服务的错误率超过指定阈值Hystrix 将会跳闸所有请求将在指定的时间内立即失败。Hystrix 允许你在请求失败时定义回退操作,例如从缓存读取或返回默认值。如果你正在使用 JVM那么你一定要考虑使用 Hystrix。如果你是在非 JVM 环境中运行,则应使用相同功能的库。
<a id="summary"></a>
## 2.6、总结
对于大多数基于微服务的应用程序来说,实现一个 API 网关是很有意义的API 网关充当着系统的单入口点,并且负责请求路由,组合和协议转换。它为每个应用程序客户端提供了一个自定义 API。API 网关还可以通过返回缓存或默认数据来掩盖后端服务故障。在下一章中,我们将介绍服务间的通信。
对于大多数基于微服务的应用来说,实现一个 API 网关是很有必要的API 网关作为系统的单入口点,并且负责请求路由,组合和协议转换。它为每个应用客户端提供了一个自定义 API。API 网关还可以通过返回缓存或默认数据来掩盖后端服务故障。在下一章中,我们将介绍服务间的通信。
<a id="microservices-in-action"></a>
@ -142,20 +155,22 @@ API 网关需要知道与其通信的每个微服务的位置IP 地址和端
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 作为 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。
- **可管理性与弹性**
上至 Web 应用级别下至每个个体微服务级别你都可以使用各种访问控制列表ACL方法并且可以轻松实现 SSL/TLS。
- **可管理性与伸缩性**
你可以使用 NGINX 的动态重新配置 API、Lua 模块、Perl 来更新基于 NGINX Plus 的 API 服务器,也可以通过 Chef、Puppet、ZooKeeper 或 DNS 来改变。
您可以使用 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 已经可以与部分先进的工具集成在一起,如 [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微服务参考架构来了解如何在您自己的应用程序中实现这一点。
NGINX Plus 被广泛用作 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)中的 API 网关。你可以利用在这里收集的内容以及 MRA微服务参考架构来了解如何在你自己的应用中实现这一点。

View File

@ -1,10 +1,12 @@
# 3、进程间通信
本书主要介绍如何使用微服务架构构建应用程序,这是本书的第三章。[第一章](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)将详细探讨服务发现方面的内容。
本书主要介绍如何使用微服务架构构建应用,这是本书的第三章。[第一章](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、简介
在单体应用程序中,组件可通过语言级方法或者函数相互调用。相比之下,基于微服务的应用程序是一个运行在多台机器上的分布式系统。通常,每个服务实例都是一个进程。
在单体应用中,组件可通过语言级方法或者函数相互调用。相比之下,基于微服务的应用是一个运行在多台机器上的分布式系统。通常,每个服务实例都是一个进程。
因此,如图 3-1 所示服务必须使用进程间通信IPC机制进行交互。
@ -15,12 +17,13 @@
<a id="interaction-styles"></a>
## 3.2、交互方式
当为服务选择一种 IPC 机制时,首先需要考虑服务如何交互。有许多种客户端 — 服务交互方式。可以从两个维度对它们进行分类。第一个维度是交互方式是一对一还是一对多:
当为服务选择一种 IPC 机制时,首先需要考虑服务如何交互。有许多种客户端/服务交互方式。可以从两方面对它们进行分类。交互方式是一对一还是一对多:
- **一对一** — 每个客户端请求都由一个服务实例处理。
- **一对多** — 每个请求由多个服务实例处理。
第二个维度是交互是同步的还是异步的:
交互是同步的还是异步的:
- **同步** — 客户端要求服务及时响应,在等待过程中可能会发生阻塞。
- **异步** — 客户端在等待响应时不会发生阻塞,但响应(如果有)不一定立即返回。
@ -35,23 +38,26 @@
表 3-1、进程间通信方式
一对一交互分为以下列举的类型,包括同步(请求/响应)与异步(通知与请求/异步响应):
以下为一对一交互,包括同步(请求/响应)与异步(通知与请求/异步响应):
- **请求/响应**
客户端向服务发出请求并等待响应。客户端要求响应及时到达。在基于线程的应用程序中,发出请求的线程可能在等待时发生阻塞。
客户端向服务发出请求并等待响应。客户端要求响应及时到达。在基于线程的应用中,发出请求的线程可能在等待时发生阻塞。
- **通知(又称为单向请求)**
客户端向服务发送请求,但不要求响应。
- **请求/异步响应**
客户端向服务发送请求,服务异步响应。客户端在等待时不发生阻止,适用于假设响应可能不会立即到达的场景。
一对多交互可分为以下列举的类型,它们都是异步的:
一对多交互,它们都是异步的:
- **发布/订阅**
客户端发布通知消息,由零个或多个感兴趣的服务消费。
客户端发布通知消息,由零个或多个服务消费。
- **发布/异步响应**
   客户端发布请求消息,之后等待一定时间来接收消费者的响应。
@ -62,36 +68,39 @@
![使用了多种 IPC 机制的服务交互](resources/3-2.png)
服务使用了通知、请求/响应和发布/订阅组合。例如,乘客的智能手机向 Trip Management 微服务发送一条通知请求一辆车。Trip Management 服务通过使用请求/响应来调用 Passenger Management 服务以验证乘客的帐户是否可用。之后Trip Management 服务创建路线,并使用发布/订阅通知其他服务,包括用于定位可用司机的 Dispatcher。
服务使用了通知、请求/响应和发布/订阅组合。例如,乘客的智能手机向 Trip Management 微服务发送一条通知请求一辆车。Trip Management 服务通过使用请求/响应来调用 Passenger Management 服务以验证乘客的帐户是否可用。之后Trip Management 服务创建路线,并使用发布/订阅通知其他服务,包括用于定位可用司机的 Dispatcher。
现在我们来看一下交互方式,我们先来看看如何定义 API。
<a id="defining-apis"></a>
## 3.3、定义 API
服务 API 是服务与客户端之间的契约。无论您选择何种 IPC 机制使用接口定义语言interface definition languageIDL来严格定义服务 API 都是非常有必要的。有论据证明使用 [API 优先法](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 方面的内容。
服务 API 是服务与客户端之间的契约。无论你选择何种 IPC 机制使用接口定义语言interface definition languageIDL来严格定义服务 API 都是非常有必要的。有论据证明使用 [API 优先法](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 变更的方式取决于变更的程度。某些更改是次要或需要向后兼容以前的版本。例如,您可能会向请求或响应添加属性。此时设计客户端与服务遵守[鲁棒性原则](https://en.wikipedia.org/wiki/Robustness_principle)就显得很有意义了。新版本的服务要能兼容使用旧版本 API 的客户端。该服务为缺少的请求属性提供默认值,并且客户端忽略所有多余的响应属性。使用 IPC 机制和消息格式非常重要,他们可以让您轻松地演化 API
服务 API 总是随着时间而变化。在单体应用中,更改 API 和更新所有调用者通常是一件直截了当的事。但在基于微服务的应用中,即使 API 的所有消费者都是同一应用中的其他服务,要想完成这些工作也是非常困难的。通常,你无法强制所有客户端与服务升级的节奏一致。此外,你可能需要[逐步部署服务的新版本](http://techblog.netflix.com/2013/08/deploying-netflix-api.html),以便新旧版本的服务同时运行。因此,制定这些问题的处理策略还是很重要的
但有时候,您必须对 API 作出大量不兼容的更改。由于您无法强制客户端立即升级,服务也必须支持较旧版本的 API 一段时间。如果您使用了基于 HTTP 的机制(如 REST则一种方法是将版本号嵌入到 URL 中。每个服务实例可能同时处理多个版本。或者,您可以部署多个不同的实例,每个实例用于处理特定版本。
处理 API 变更的方式取决于变更的程度。某些更改是次要或需要向后兼容以前的版本。例如,你可能会向请求或响应中添加属性。此时设计客户端与服务遵守[鲁棒性原则](https://en.wikipedia.org/wiki/Robustness_principle)就显得很有意义了。新版本的服务要能兼容使用旧版本 API 的客户端。该服务为缺少的请求属性提供默认值,并且客户端忽略所有多余的响应属性。使用 IPC 机制和消息格式非常重要,他们可以让你轻松地演化 API。
但有时候,你必须对 API 作出大量不兼容的更改。由于你无法强制客户端立即升级,服务也必须支持较旧版本的 API 一段时间。如果你使用了基于 HTTP 的机制(如 REST则一种方法是将版本号嵌入到 URL 中。每个服务实例可能同时处理多个版本。或者,你可以部署多个不同的实例,每个实例处理特定的版本。
<a id="handling-partial-failure"></a>
## 3.5、处理局部故障
正如[第二章](2-using-an-api-gateway.md)中关于 API 网关所述,在分布式系统中始终存在局部故障的风险。由于客户端进程与服务进程是分开的,服务可能无法及时响应客户端的请求。由于故障或者维护,服务可能需要关闭。也有可能因服务过载,造成响应速度变得极慢。
例如,请回想[第二章](2-using-an-api-gateway.md)中的产品详细信息场景。我们假设推荐服务没有响应。低级的客户端实现可能会无限期地阻塞以等待响应。这不仅会导致用户体验糟糕,而且在许多应用程序中,它会消耗线程等宝贵的资源。最终导致运行时系统将线程消耗完,造成无法响应,如图 3-3 所示。
例如,请回想[第二章](2-using-an-api-gateway.md)中的产品详细信息场景。我们假设推荐服务没有响应。天真的客户端实现可能会不停地阻塞以等待响应。这不仅会导致用户体验糟糕,而且在许多应用中,它会消耗线程等宝贵的资源。最终导致运行时系统将线程消耗完,造成系统无法响应,如图 3-3 所示。
![因无响应服务引起的线程阻塞](resources/3-3.png)
为了防止出现此类问题,在设计服务时必须考虑处理局部故障。以下是一个由 [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)。处理局部故障的策略包括:
- **网络超时**
@ -99,21 +108,22 @@
- **限制未完成的请求数量**
对客户端请求的特定服务,设置未完成请求的数量上限。如果达到了上限,发出的额外请求可能是毫无意义的,因此这些尝试需要立即失败。
对客户端请求的某些服务,设置未完成请求的数量上限。如果达到了上限,发出的额外请求可以视为毫无意义,因此这些尝试需要立即失败。
- **[断路器模式](http://martinfowler.com/bliki/CircuitBreaker.html)**
追踪成功和失败请求的数量。如果错误率超过配置阈值,则断开断路器,以便后续的尝试能立即失败。如果出现大量请求失败,则表明服务不可用,发送请求将是无意义的。发生超时后,客户端应重新尝试,如果成功,则关闭断路器。
追踪成功和失败请求的数量。如果错误率超过配置阈值,则断开断路器,以便后续的尝试能立即失败。如果出现大量请求失败,则表明服务不可用,发送请求将毫无意义。发生超时后,客户端应重新尝试,如果成功,则关闭断路器。
- **提供回退**
请求失败时执行回退逻辑。例如,返回缓存数据或者默认值,一组空白的推荐数据。
请求失败时执行回退逻辑。例如,返回缓存数据或者默认值,可以是一组空白的推荐数据。
[Netflix Hystrix](https://github.com/Netflix/Hystrix) 是一个实现上述和其他模式的开源库。如果您正在使用 JVM那么您一定要考虑使用 Hystrix。如果您在非 JVM 环境中运行,则应使用相等作用的库。
[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 机制。
@ -121,6 +131,7 @@
<a id="asynchronous-message-based-communication"></a>
## 3.7、异步、基于消息的通信
当使用消息传递时,进程通过异步交换消息进行通信。客户端通过发送消息向服务发出请求。如果服务需要回复,则通过向客户端发送一条单独的消息来实现。由于通信是异步的,因此客户端不会阻塞等待回复。相反,客户端被假定不会立即收到回复。
一条[消息](http://www.enterpriseintegrationpatterns.com/patterns/messaging/Message.html)由头部(如发件人之类的元数据)和消息体组成。消息通过[通道](http://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageChannel.html)进行交换。任何数量的生产者都可以向通道发送消息。类似地,任何数量的消费者都可以从通道接收消息。有两种通道类型,分别是[点对点](http://www.enterpriseintegrationpatterns.com/patterns/messaging/PointToPointChannel.html)pointtopoint与[发布订阅](http://www.enterpriseintegrationpatterns.com/patterns/messaging/PublishSubscribeChannel.html)publishsubscribe
@ -128,13 +139,13 @@
- **点对点通道**发送一条消息给一个切确的、正在从通道读取消息的消费者。服务使用点对点通道,就是上述的一对一交互方式。
- **发布订阅通道**将每条消息传递给所有已订阅的消费者。服务使用发布订阅通道,就是上述的一对多交互方式。
图 3-4 展示了打车应用程序如何使用发布订阅通道。
图 3-4 展示了打车应用如何使用发布订阅通道。
![使用了发布/订阅通道的打车应用](resources/3-4.png)
Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来通知已订阅的服务,如 Dispatcher。Dispatcher 找到可用的司机并通过向发布订阅通道写入 Driver Proposed 消息来通知其他服务。
有许多消息系统可供选择,应该选择一个支持多种编程语言的。
有许多消息系统可供选择,应该选择一个支持多种编程语言的。
一些消息系统支持标准协议,如 AMQP 和 STOMP。其他消息系统有专有的文档化协议。
@ -145,12 +156,15 @@ Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来
- **将客户端与服务分离**
客户端通过向相应的通道发送一条消息来简单地发出一个请求。服务实例对客户端而言是透明的。客户端不需要使用发现机制来确定服务实例的位置。
- **消息缓冲**
使用如 HTTP 的同步请求/响应协议,客户端和服务在交换期间必须可用。相比之下,消息代理会将消息排队写入通道,直到消费者处理它们。这意味着,例如,即使订单执行系统出现缓慢或不可用的情况,在线商店还是可以接受客户的订单。订单消息只需要简单地排队。
- **灵活的客户端 — 服务交互**
消息传递支持前面提到的所有交互方式。
- **毫无隐瞒的进程间通信**
基于 RPC 的机制试图使调用远程服务看起来与调用本地服务相同。然而,由于物理因素和局部故障的可能性,他们实际上是完全不同的。消息传递使这些差异变得非常明显,所以开发人员不会被这些虚假的安全感所欺骗。
@ -160,6 +174,7 @@ Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来
- **额外的复杂操作**
消息传递系统是一个需要安装、配置和操作的系统组件。消息代理程序必须高度可用,否则系统的可靠性将受到影响。
- **实现基于请求/响应式交互的复杂性**
请求/响应式交互需要做些工作来实现。每个请求消息必须包含应答通道标识符和相关标识符。该服务将包含相关 ID 的响应消息写入应答信道。客户端使用相关 ID 将响应与请求相匹配。通常使用直接支持请求/响应的 IPC 机制更加容易。
@ -169,100 +184,110 @@ Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来
<a id="synchronous-request-response-ipc"></a>
## 3.8、同步的请求/响应 IPC
当使用基于同步、基于请求/响应的 IPC 机制时,客户端向服务器发送请求。该服务处理该请求并返回响应。
在许多客户端中,请求的线程在等待响应时被阻塞。其他客户端可能会使用异步、事件驱动的客户端代码,这些代码可能是由 [Futures](http://docs.scala-lang.org/overviews/core/futures.html) 或 [Rx Observables](http://reactivex.io/documentation/observable.html) 封装的。然而,与使用消息传递不同,客户端假定响应能及时到达
当使用基于同步、基于请求/响应的 IPC 机制时,客户端向服务器发送请求。服务处理该请求并返回响应
有许多协议可供选择。有两种流行协议分别是 REST 和 Thrift。我们先来看一下 REST。
在许多客户端中,请求的线程在等待响应时被阻塞。其他客户端可能会使用到异步、事件驱动的客户端代码,这些代码可能是由 [Futures](http://docs.scala-lang.org/overviews/core/futures.html) 或 [Rx Observables](http://reactivex.io/documentation/observable.html) 封装的。然而,与使用消息传递不同,客户端假定响应能及时到达。
有许多协议可供选择,其中有两种流行协议分别是 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 请求更新一个资源
如今,开发 [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) 风格的 API 变得非常流行。REST 是一种使用了 HTTP (几乎总是)的 IPC 机制
引用 REST 创建者 Roy Fielding
资源是 REST 中的一个关键概念它通常用于表示业务对象如客户、产品或这些业务对象的集合。REST 使用 HTTP 动词(谓词)来操纵资源,这些资源通过 URL 引用。例如GET 请求返回一个资源的表述形式,可能是 XML 文档或 JSON 对象形式。POST 请求创建一个新资源PUT 请求更新一个资源。
> “REST 提供了一套架构约束,当应用作为整体时,其强调组件交互的可扩展性、接口的通用性、组件的独立部署以及中间组件,以减少交互延迟、实施安全性和封装传统系统。” — **Roy Fielding[《架构风格与基于网络的软件架构设计》](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)**
引用 REST 创建者 Roy Fielding 的话:
图 3-5 展示了打车应用程序可能使用 REST 的方式之一。
> “REST 提供了一套架构约束,当应用作为整体时,其强调组件交互的可扩展性、接口的通用性,组件的独立部署以及中间组件,以减少交互延迟、实施安全性和封装遗留系统。” — **Roy Fielding[《架构风格与基于网络的软件架构设计》](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)**
图 3-5 展示了打车应用可能使用 REST 的方式之一。
![使用了 RESTful 交互的打车应用](resources/3-5.png)
乘客的智能手机通过向 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 请求。
级别 2 的 API 使用 HTTP 动词(谓词)执行操作:使用 GET 检索、使用 POST 创建和使用 PUT 进行更新。请求查询参数和请求体(如果有)指定操作的参数。这使服务能够利用到 Web 的基础特性,如缓存 GET 请求。
- **级别 3**
级别 3 的 API 基于非常规命名原则设计HATEOASHypermedia as the engine of application state超媒体即应用程序状态引擎)。基本思想是 GET 请求返回的资源的表述,包含用于执行该资源允许的操作的链接。例如,发送 GET 请求检索订单返回的订单响应中包含取消订单链接客户端可以用该链接来取消订单。HATEOAS 的一个[好处](http://www.infoq.com/news/2009/04/hateoas-restful-api-advantages)是不再需要将 URL 硬编码在客户端代码中。另一个好处是,由于资源的表示包含可允许操作的链接,所以客户端不必猜测可以对当前状态的资源执行什么操作。
级别 3 的 API 基于非常规命名原则设计HATEOASHypermedia as the engine of application state超媒体即应用状态引擎。基本思想是 GET 请求返回的资源的表述,包含用于执行该资源允许的操作的链接。例如,发送 GET 请求检索订单返回的订单响应中包含取消订单链接客户端可以用该链接来取消订单。HATEOAS 的一个[好处](http://www.infoq.com/news/2009/04/hateoas-restful-api-advantages)是不再需要将 URL 硬编码在客户端代码中。另一个好处是,由于资源的表示包含可允许操作的链接,所以客户端不必猜测可以对当前状态的资源执行什么操作。
使用基于 HTTP 的协议有很多好处:
- HTTP 简单易懂。
- 可以使用浏览器扩展(如 [Postman](https://www.getpostman.com/))来测试 HTTP API或者使用 curl 命令行测试 HTTP API假设使用了 JSON 或其他一些文本格式)。
- 可以使用浏览器扩展(如 [Postman](https://www.getpostman.com/))来测试 HTTP API或者使用 curl 命令行测试 HTTP API假设使用了 JSON 或其他一些文本格式)。
- 它直接支持请求/响应式通信。
- HTTP 属于防火墙友好。
- 它不需要中间代理,简化了系统架构。
使用 HTTP 也存在一些缺点:
- HTTP 仅直接支持请求/响应的交互方式。您可以使用 HTTP 进行通知,但服务器必须始终发送 HTTP 响应。
- 因为客户端和服务直接通信(没有一个中间者来缓冲消息),所以它们必须在交换期间都运行着。
- 客户端必须知道每个服务实例的位置(即 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 的工具。
- HTTP 仅直接支持请求/响应的交互方式。你可以使用 HTTP 进行通知,但服务器必须始终发送 HTTP 响应。
- 因为客户端和服务直接通信(没有一个中间者来缓冲消息),所以它们必须在交换期间都运行着。
- 客户端必须知道每个服务实例的位置(即 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 的工具。
<a id="thrift"></a>
### 3.8.2、Thrift
[Apache Thrift](https://thrift.apache.org/) 是 REST 的一个有趣的替代方案。它是一个用于编写跨语言 [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) 客户端和服务器框架。Thrift 提供了一个 C 风格的 IDL 来定义您的 API。您可以使用 Thrift 编译器生成客户端 stub 和服务器端 skeleton。编译器可以生成各种语言的代码包括 C++、Java、Python、PHP、Ruby、Erlang 和 Node.js。
[Apache Thrift](https://thrift.apache.org/) 是 REST 的一个有趣的替代方案。它是一个用于编写跨语言 [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) 客户端和服务器的框架。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 是防火墙友好的、浏览器友好的和可读的。
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。因为消息是自描述的每个消息除了它们的值之外还包含属性的名称。另一个缺点是解析文本的开销。因此可能需要考虑使用二进制格式。
使用基于文本的消息格式的缺点是消息往往是冗长的,特别是 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 作出了极好的比较
有几种二进制格式可供选择。如果你使用的是 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 机制:异步消息传递和同步请求/响应。为了进行通信,一个服务必须能够找到另一个服务。在第四章中,我们将介绍微服务架构中服务发现问题。
微服务必须使用进程间通信机制进行通信。在设计服务如何进行通信时,你需要考虑各种问题:服务如何交互、如何为每个服务指定 API、如何演变 API 以及如何处理局部故障。微服务可以使用两种 IPC 机制:异步消息传递和同步请求/响应。为了进行通信,一个服务必须能够找到另一个服务。在第四章中,我们将介绍微服务架构中服务发现问题。
<a id="microservices-in-action"></a>
## 微服务实战NGINX 与应用程序架构
## 微服务实战NGINX 与应用架构
by Floyd Smith
NGINX 使您能够实现各种伸缩和镜像操作,使您的应用程序更加灵敏和高度可用。您为伸缩和镜像所作的选择会影响到您如何进行进程间通信,这是本章的主题。
NGINX 使你能够实现各种伸缩和镜像操作,使你的应用更加灵敏和高度可用。你为伸缩和镜像所作的选择会影响到你如何进行进程间通信,这是本章的主题。
我们在 NGINX 方面建议您在实现基于微服务的应用程序时考虑使用四层架构。Forrester 在这方面有详细的报告,您可以从 NGINX 上免费下载。这些层代表客户端(包括台式机或笔记本电脑、移动、可穿戴或 IoT 客户端)、交付、聚合(包括数据存储)和服务,其中包括应用功能和特定服务,而不是共享数据存储。
我们在 NGINX 方面建议你在实现基于微服务的应用时考虑使用四层架构。Forrester 在这方面有详细的报告,你可以从 NGINX 上免费下载。这些层代表客户端(包括台式机或笔记本电脑、移动、可穿戴或 IoT 客户端)、交付、聚合(包括数据存储)和服务,其中包括应用功能和特定服务,而不是共享数据存储。
四层架构比以前的三层架构更加灵活,具有可扩展、响应灵敏、移动友好,并且内在支持基于微服务的应用程序开发和交付等优点。像 Netflix 和 Uber 这样的行业引领者能够通过使用这种架构来实现用户所需的性能水平。
四层架构比以前的三层架构更加灵活,具有可扩展、响应灵敏、移动友好,并且内在支持基于微服务的应用开发和交付等优点。像 Netflix 和 Uber 这样的行业引领者通过使用这种架构来实现用户所需的性能水平。
NGINX 本质上非常适合四层架构,从客户端层的媒体流,到交付层的负载均衡与缓存、聚合层的高性能和安全的基于 API 的通信的工具,以及服务层中支持灵活管理的短暂服务实例。

View File

@ -1,14 +1,16 @@
# 4、服务发现
本书主要介绍如何使用微服务来构建应用程序,现在是第四章。[第一章](1-introduction-to-microservices.md)已经介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),并讨论了使用微服务的优点与缺点。[第二章](2-using-an-api-gateway.md)和[第三章](3-inter-process-communication.md)介绍了微服务间的通信并对不同的通信机制作出对比。在本章中我们将探讨服务发现service discovery相关的内容。
本书主要介绍如何使用微服务来构建应用,现在是第四章。[第一章](1-introduction-to-microservices.md)已经介绍了[微服务架构模式](http://microservices.io/patterns/microservices.html),并讨论了使用微服务的优点与缺点。[第二章](2-using-an-api-gateway.md)和[第三章](3-inter-process-communication.md)介绍了微服务间的通信并对不同的通信机制作出对比。在本章中我们将探讨服务发现service discovery相关的内容。
<a id="why-use-service-discovery"></a>
## 4.1、为何使用服务发现
我们假设您正在编写某些代码,这些代码调用了有 REST API 或 Thrift API 的服务。为了发送一个请求您的代码需要知道服务实例的网络位置IP 地址与端口)。在运行于物理硬件上的传统应用中,服务实例的网络位置是相对静态的。例如,您的代码可以从偶尔更新的配置文件中读取网络位置。
我们假设你正在编写某些代码,这些代码调用了有 REST API 或 Thrift API 的服务。为了发送一个请求你的代码需要知道服务实例的网络位置IP 地址与端口)。在运行于物理硬件上的传统应用中,服务实例的网络位置是相对静态的。例如,你的代码可以从偶尔更新的配置文件中读取网络位置。
然而,在现代基于云的微服务应用中,这是一个更难解决的问题,如图 4-1 所示。
服务实例有动态分配的网络位置。此外,由于自动扩缩、故障与升级,整组服务实例会动态变更。因此,的客户端代码需要使用更精确的服务发现机制。
服务实例有动态分配的网络位置。此外,由于自动扩缩、故障与升级,整组服务实例会动态变更。因此,的客户端代码需要使用更精确的服务发现机制。
![图 4-1、需要服务寻找帮助的客户端或 API 网关](resources/4-1.png)
@ -17,6 +19,7 @@
<a id="the-client-side-discovery-pattern"></a>
## 4.2、客户端发现模式
当使用[客户端发现模式](http://microservices.io/patterns/client-side-discovery.html)时客户端负责确定可用服务实例的网络位置和请求负载均衡。客户端查询服务注册中心service registry它是可用服务实例的数据库。之后客户端利用负载均衡算法选择一个可用的服务实例并发出请求。
图 4-2 展示了该模式的结构
@ -27,77 +30,84 @@
[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 展示了该模式的结构:
![服务器间也可以处理服务发现](resources/4-3.png)
客户端通过负载均衡器向服务发出请求。负载均衡器查询服务注册中心并将每个请求路由到可用的服务实例。与客户端发现一样,服务实例由服务注册中心注册与销毁。
[AWS Elastic Load Balancer](https://aws.amazon.com/cn/elasticloadbalancing/)ELB是一个服务端发现路由示例。ELB 通常用于负载均衡来自互联网的外部流量。然而,您还可以使用 ELB 来负载均衡虚拟私有云VPC内部的流量。客户端通过 ELB 使用其 DNS 名称来发送请求HTTP 或 TCP。ELB 负载均衡一组已注册的 Elastic Compute CloudEC2实例或 EC2 Container ServiceECS容器之间的流量。这里没有单独可见的服务注册中心。相反EC2 实例与 ECS 容器由 ELB 本身注册。
[AWS Elastic Load Balancer](https://aws.amazon.com/cn/elasticloadbalancing/)ELB是一个服务端发现路由示例。ELB 通常用于均衡来自互联网的外部流量负载。然而,你还可以使用 ELB 来均衡虚拟私有云VPC内部的流量负载。客户端通过 ELB 使用其 DNS 名称来发送请求HTTP 或 TCP。ELB 均衡一组已注册的 Elastic Compute CloudEC2实例或 EC2 Container ServiceECS容器之间的流量负载。这里没有单独可见的服务注册中心。相反EC2 实例与 ECS 容器由 ELB 本身注册。
HTTP 服务器和负载均衡器(如 [NGINX Plus](https://www.nginx.com/products/) 和 NGINX也可以作为服务端发现负载均衡器。例如[此博文](https://www.airpair.com/scalable-architecture-with-docker-consul-and-nginx)描述了使用 [Consul Template](https://www.hashicorp.com/blog/introducing-consul-template.html) 动态重新配置 NGINX 反向代理。Consul Template 是一个工具,可以从存储在 [Consul 服务注册中心](https://www.consul.io/)中的配置数据中定期重新生成任意配置文件。每当文件被更改时,它都会运行任意的 shell 命令。在列举的博文描述的示例中Consul Template 会生成一个 nginx.conf 文件,该文件配置了反向代理,然后通过运行一个命令告知 NGINX 重新加载配置。更复杂的实现可以使用其 [HTTP API 或 DNS](https://www.nginx.com/products/on-the-fly-reconfiguration/) 动态重新配置 NGINX Plus。
某些部署环境(如 [Kubernetes](https://kubernetes.io/) 和 [Marathon](https://mesosphere.github.io/marathon/docs/service-discovery-load-balancing.html))在群集中的每个主机上运行着一个代理。这些代理扮演着服务端发现负载均衡器角色。为了向服务发出请求,客户端通过代理使用主机的 IP 地址和服务的分配端口来路由请求。之后,代理将请求透明地转发到在集群中某运行的可用服务实例。
某些部署环境(如 [Kubernetes](https://kubernetes.io/) 和 [Marathon](https://mesosphere.github.io/marathon/docs/service-discovery-load-balancing.html))在群集中的每个主机上运行着一个代理。这些代理扮演着服务端发现负载均衡器角色。为了向服务发出请求,客户端通过代理使用主机的 IP 地址和服务的分配端口来路由请求。之后,代理将请求透明地转发到在集群中某运行的可用服务实例。
服务端发现模式有几个优点与缺点。该模式的一大的优点是其把发现的细节从客户端抽象出来。客户端只需向负载均衡器发出请求。这消除了为服务客户端使用的每种编程语言和框架都实现发现逻辑的必要性。另外,如上所述,一些部署环境免费提供此功能。然而,这种模式存在一些缺点。除非负载均衡器由部署环境提供,否则需要引入这个高可用系统组件,并进行设置和管理。
服务端发现模式有几个优点与缺点。该模式的一大的优点是其把发现的细节从客户端抽象出来。客户端只需向负载均衡器发出请求。这消除了为服务客户端使用的每种编程语言和框架都实现发现逻辑的必要性。另外,如上所述,一些部署环境免费提供此功能。然而,这种模式存在一些缺点。除非负载均衡器由部署环境提供,否则需要引入这个高可用系统组件,并进行设置和管理。
<a id="the-service-registry"></a>
## 4.4、服务注册中心
[服务注册中心](http://microservices.io/patterns/service-registry.html)service registry是服务发现的一个关键部分。它是一个包含了服务实例网络位置的数据库。服务注册中心必须是高可用和最新的。虽然客户端可以缓存从服务注册中心获得的网络位置但该信息最终会过期客户端将无法发现服务实例。因此服务注册中心由使用了复制协议replication protocol来维护一致性的服务器集群组成。
如之前所述,[Netflix Eureka](https://github.com/Netflix/eureka) 是一个很好的服务注册中心范例。它提供了一个用于注册和查询服务实例的 REST API。服务实例使用 POST 请求注册其网络位置。它必须每隔 30 秒使用 PUT 请求来刷新其注册信息。通过使用 HTTP DELETE 请求或实例注册超时来移除注册信息。正如您所料,客户端可以使用 HTTP GET 请求来检索已注册的服务实例
[服务注册中心](http://microservices.io/patterns/service-registry.html)service registry是服务发现的一个关键部分。它是一个包含了服务实例网络位置的数据库。服务注册中心必须是高可用和最新的。虽然客户端可以缓存从服务注册中心获得的网络位置但该信息最终会过期客户端将无法发现服务实例。因此服务注册中心使用了复制协议replication protocol来维护一致性的服务器集群组成
Netflix 通过在每个 Amazon EC2 可用性区域Availability Zone中运行一个或多个 Eureka 服务器来[实现高可用](https://github.com/Netflix/eureka/wiki/Configuring-Eureka-in-AWS-Cloud)。每个 Eureka 服务器都运行在具有一个 [弹性 IP 地址](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)的 EC2 实例上。DNS TEXT 记录用于存储 Eureka 集群配置,这是一个从可用性区域到 Eureka 服务器的网络位置列表的映射。当 Eureka 服务器启动时,它将会查询 DNS 以检索 Eureka 群集配置,查找其对等体,并为其分配一个未使用的弹性 IP 地址
如之前所述,[Netflix Eureka](https://github.com/Netflix/eureka) 是一个很好的服务注册中心范例。它提供了一个用于注册和查询服务实例的 REST API。服务实例使用 POST 请求注册其网络位置。它必须每隔 30 秒使用 PUT 请求来刷新其注册信息。通过使用 HTTP DELETE 请求或实例注册超时来移除注册信息。正如你所料,客户端可以使用 HTTP GET 请求来检索已注册的服务实例
Eureka 客户端 — 服务与服务客户端 — 查询 DNS 以发现 Eureka 服务器的网络位置。客户端优先使用相同可用性区域中的 Eureka 服务器,如果没有可用的,则使用另一个可用性区域的 Eureka 服务器。
Netflix 通过在每个 Amazon EC2 可用区中运行一个或多个 Eureka 服务器来[实现高可用](https://github.com/Netflix/eureka/wiki/Configuring-Eureka-in-AWS-Cloud)。每个 Eureka 服务器都运行在有一个 [弹性 IP 地址](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)的 EC2 实例上。DNS TEXT 记录用于存储 Eureka 集群配置,这是一个从可用区到 Eureka 服务器的网络位置列表的映射。当 Eureka 服务器启动时,它将会查询 DNS 以检索 Eureka 群集配置,查找其对等体,并为其分配一个未使用的弹性 IP 地址。
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 可对服务进行健康检查,以确定服务的可用性。
一个用于发现和配置服务的工具。它提供了一个 API可用于客户端注册与发现服务。Consul 可对服务进行健康检查,以确定服务的可用性。
- **[Apache ZooKeeper](http://zookeeper.apache.org/)**
一个被广泛应用于分布式应用程序的高性能协调服务。Apache ZooKeeper 最初是一个 Hadoop 子项目,但现在已经成为一个独立的顶级项目。
一个被广泛应用于分布式应用的高性能协调服务。Apache ZooKeeper 最初是 Hadoop 的一个子项目,但现在已经成为一个独立的顶级项目。
另外,如之前所述,部分系统如 Kubernetes、Marathon 和 AWS没有明确的服务注册中心。相反服务注册中心只是基础设施的一个内置部分。
另外,如之前所述,部分系统如 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、自注册模式
当使用自注册模式时,服务实例负责在服务注册中心注册和注销自己。此外,如果有必要,服务实例将通过发送心跳请求来防止其注册信息过期。
图 4-4 展示了该模式的结构。
![图 4-4、服务可以自我处理注册](resources/4-4.png)
该方式的一个很好的范例就是 [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的另一系统组件负责。服务注册器通过轮询部署环境或订阅事件来跟踪运行实例集的变更情况。当它检测到一个新的可用服务实例时它会将该实例注册到服务注册中心。此外服务注册器可以注销终止的服务实例。
图 4-5 展示了该模式的结构:
@ -110,24 +120,25 @@ Eureka 客户端 — 服务与服务客户端 — 查询 DNS 以发现 Eureka
服务注册器在部分部署环境中是一个内置组件。Autoscaling Group 创建的 EC2 实例可以自动注册到 ELB。Kubernetes 服务能够自动注册并提供发现。
第三方注册模式同样有好有坏。一个主要的好处是服务与服务注册中心之间解耦。不需要为开发人员使用的每种编程语言和框架都实现服务注册逻辑。相反,仅需要在专用服务中以集中的方式处理服务实例注册。
第三方注册模式同样有好有坏。一个主要的好处是服务与服务注册中心之间解耦。不需要为开发人员使用的每种编程语言和框架都实现服务注册逻辑。相反,仅需要在专用服务中以集中的方式处理服务实例注册。
该模式的一个缺点是,除非部署环境内置,否则同样需要引入这样一个高可用的系统组件,并进行设置和管理。
该模式的一个缺点是,除非部署环境内置,否则同样需要引入这样一个高可用的系统组件,并进行设置和管理。
<a id="summary"></a>
## 4.8、总结
在微服务应用程序中,运行的服务实例集会动态变更。实例具有动态分配的网络位置。因此,为了让客户端向服务发出请求,它必须使用服务发现机制。
[服务发现](http://microservices.io/patterns/service-registry.html)的一个关键部分是服务注册中心。服务注册中心是一个可用服务实例的数据库。服务注册中心提供了管理 API 和查询 API 的功能。服务实例通过使用管理 API 从服务注册中心注册或者注销。系统组件使用查询 API 来发现可用的服务实例。
在微服务应用中,运行的服务实例集会动态变更。实例有动态分配的网络位置。因此,为了让客户端向服务发出请求,它必须使用服务发现机制。
[服务发现](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) ectd 或 [Apache ZooKeeper](http://zookeeper.apache.org/) 等服务注册中心来设置自己的服务发现基础设施。在其他部署环境中,服务发现是内置的,例如,[Kubernetes](https://kubernetes.io/) 和 [Marathon](https://mesosphere.github.io/marathon/docs/service-discovery-load-balancing.html),可以处理服务实例的注册与注销。他们还在每一个扮演服务端发现路由角色的集群主机上运行一个代理。
在某些部署环境中,你需要使用如 [Netflix Eureka](https://github.com/Netflix/eureka) 、ectd 或 [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>
@ -135,16 +146,16 @@ Eureka 客户端 — 服务与服务客户端 — 查询 DNS 以发现 Eureka
by Floyd Smith
在微服务环境中,由于自动扩缩、故障和升级,您的后端基础设施可能会不断变化,这些变化包括了服务的创建,部署和扩展。如本章所述,在动态重新分配服务位置的环境中需要服务发现机制。
在微服务环境中,由于自动扩缩、故障和升级,你的后端基础设施可能会不断变化,这些变化包括了服务的创建、部署和扩展。如本章所述,在动态重新分配服务位置的环境中需要服务发现机制。
将 NGINX 应用于微服务的一部分好处是,可以轻松地配置其自动响应后端基础设施作出的变更。NGINX 配置不仅简单灵活,而且兼容 [Amazon Web Services](http://docs.aws.amazon.com/cli/latest/reference/elasticbeanstalk/create-configuration-template.html) 使用的模板,可以更轻松地管理特定的服务变更与受负载均衡的变更服务组。
将 NGINX 应用于微服务的一部分好处是,可以轻松地配置其自动响应后端基础设施作出的变更。NGINX 配置不仅简单灵活,而且兼容 [Amazon Web Services](http://docs.aws.amazon.com/cli/latest/reference/elasticbeanstalk/create-configuration-template.html) 使用的模板,可以更轻松地管理特定的服务变更与受负载均衡的变更服务组。
[NGINX Plus](https://www.nginx.com/products/) 有[即时重新配置 API](https://www.nginx.com/products/on-the-fly-reconfiguration/),无需重新启动 NGINX Plus 或手动重新加载配置就能感知受负载均衡服务组的变更。在 [NGINX Plus Release 8 及更高版本](https://www.nginx.com/blog/nginx-plus-r8-released/#on-the-fly)中,可以将对 API 所做的变更配置为在重新启动和配置重新加载时保持不变(重新加载不需要重新启动,不要断开连接)[NGINX Plus Release 9 及更高版本](https://www.nginx.com/blog/nginx-plus-r9-released/#dns-srv)支持使用 DNS SRV 记录进行服务发现,可与现有服务器发现平台(如 Consul 和 etcd进行更紧密地集成。
[NGINX Plus](https://www.nginx.com/products/) 有[即时重新配置 API](https://www.nginx.com/products/on-the-fly-reconfiguration/),无需重新启动 NGINX Plus 或手动重新加载配置就能感知受负载均衡服务组的变更。在 [NGINX Plus Release 8 及更高版本](https://www.nginx.com/blog/nginx-plus-r8-released/#on-the-fly)中,可以将对 API 所做的变更配置为在重新启动和配置重新加载时保持不变(重新加载不需要重新启动,不要断开连接)[NGINX Plus Release 9 及更高版本](https://www.nginx.com/blog/nginx-plus-r9-released/#dns-srv)支持使用 DNS SRV 记录进行服务发现,可与现有服务器发现平台(如 Consul 和 etcd进行更紧密地集成。
我们在 NGINX 创建了一个用于管理服务发现的模型:
1. 为几个应用程序每个应用运行单独的 Docker 容器,包括如 etcd 的服务发现应用程序、服务注册工具、一个或多个后端服务器以及用于负载均衡其他容器的 NGINX Plus 本身。
2. 注册工具监控 Docker 的容器,并使用服务发现工具注册新服务,此外,还可以删除消失的容器。
1. 为每个应用运行单独的 Docker 容器,包括如 etcd 的服务发现应用、服务注册工具、一个或多个后端服务器以及用于均衡其他容器负载的 NGINX Plus 本身。
2. 注册工具监控 Docker 新建的容器,并使用服务发现工具注册新服务,此外,还可以删除消失的容器。
3. 容器及其运行的服务将自动添加到负载均衡上游服务器中或从中删除。
此 Demo 应用程序可用于多个服务发现应用程序[Consul API](https://www.nginx.com/blog/service-discovery-with-nginx-plus-and-consul/)、[来自 Consul 的 DNS SRV 记录](https://www.nginx.com/blog/service-discovery-nginx-plus-srv-records-consul-dns/)、[etcd](https://www.nginx.com/blog/service-discovery-nginx-plus-etcd/) 以及 [ZooKeeper](https://www.nginx.com/blog/service-discovery-nginx-plus-zookeeper/) 等。
此 Demo 应用可用于多个服务发现应用:[Consul API](https://www.nginx.com/blog/service-discovery-with-nginx-plus-and-consul/)、[来自 Consul 的 DNS SRV 记录](https://www.nginx.com/blog/service-discovery-nginx-plus-srv-records-consul-dns/)、[etcd](https://www.nginx.com/blog/service-discovery-nginx-plus-etcd/) 以及 [ZooKeeper](https://www.nginx.com/blog/service-discovery-nginx-plus-zookeeper/) 等。

View File

@ -1,42 +1,45 @@
# 5、事件驱动数据管理
本书主要介绍如何使用微服务构建应用程序,这是本书的第五章。[第一章](1-introduction-to-microservices.md)介绍了微服务架构模式,讨论了使用微服务的优点与缺点。[第二](2-using-an-api-gateway.md)和[第三章](3-inter-process-communication.md)描述了微服务架构内通信方式的对比。第四章探讨了与服务发现相关的内容。在本章中,我们稍微做了点调整,研究微服务架构中出现的分布式数据管理问题。
本书主要介绍如何使用微服务构建应用,这是本书的第五章。[第一章](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),这些事务提供了以下重要保障:
单体应用通常有一个单一的关系型数据库。使用关系型数据库的一个主要优点是你的应用可以使用 [ACID 事务](https://en.wikipedia.org/wiki/ACID),这些事务提供了以下重要保障:
- **原子性Atomicity** — 所作出的更改是原子操作,不可分割
- **一致性Consistency** — 数据库的状态始终保持一致
- **隔离性Isolation** — 即使事务并发执行,但们看起来更像是串行执行
- **隔离性Isolation** — 即使事务并发执行,但们看起来更像是串行执行
- **永久性Durable** — 一旦事务提交,它将不可撤销
因此,您的应用程序可以很容易地开始事务、更改(插入、更新和删除)多行记录,并提交事务。
因此,你的应用可以很容易地开始事务、更改(插入、更新和删除)多行记录,并提交事务。
使用关系型数据库的另一大好处是它提供了 SQL这是一种丰富、声明式和标准化的查询语言。您可以轻松地编写一个查询,组合来自多个表的数据之后RDBMS 查询计划程序将确定执行查询的最佳方式。您不必担心如何访问数据库等底层细节。因为您所有的应用程序数据都存放在同个数据库中,因此很容易查询。
使用关系型数据库的另一大好处是它提供了 SQL这是一种声明式和标准化的查询语言。你可以轻松地编写一个查询组合来自多个表的数据之后RDBMS 查询计划程序将确定执行查询的最佳方式。你不必担心如何访问数据库等底层细节。因为你所有的应用数据都存放在同个数据库中,因此很容易查询。
很不幸的是,当我们转向微服务架构时,数据访问将变得非常复杂。因为每个微服务所拥有的数据[对当前微服务来说是私有的](http://microservices.io/patterns/data/database-per-service.html),只能通过其提供的 API 进行访问。封装数据可确保微服务松耦合、独立演进。如果多个服务访问相同的数据模式schema更新需要对所有服务进行耗时协调更新。
很不幸的是,当我们转向微服务架构时,数据访问将变得非常复杂。因为每个微服务所拥有的数据[对当前微服务来说是私有的](http://microservices.io/patterns/data/database-per-service.html),只能通过其提供的 API 进行访问。封装数据可确保微服务松耦合、独立演进。如果多个服务访问相同的数据,模式schema更新时,需要耗时协调更新所有服务
更糟糕的是,不同的微服务经常使用不同类型的数据库。现代应用程序存储和处理着各种数据,而关系型数据库并不总是最佳选择。在某些场景,特定的 NoSQL 数据库可能具有更方便的数据模型,提供了更好的性能和可扩展性。例如,存储和查询文本的服务使用文本搜索引擎(如 Elasticsearch是合理的。类似地,存储社交图数据的服务应该可以使用图数据库,例如 Neo4j。因此基于微服务的应用程序通常混合使用 SQL 和 NoSQL 数据库,即所谓的[混合持久化](http://martinfowler.com/bliki/PolyglotPersistence.html)polyglot persistence方式。
更糟糕的是,不同的微服务常常使用不同类型的数据库。现代应用存储和处理着各种数据,而关系型数据库并不一定是最佳选择。在某些场景,某些 NoSQL 数据库可能有更方便的数据模型,提供了更好的性能和可扩展性。例如,存储和查询文本的服务适合使用文本搜索引擎(如 Elasticsearch。类似地存储社交图数据的服务可以使用图数据库例如 Neo4j。因此基于微服务的应用通常混合使用 SQL 和 NoSQL 数据库,即所谓的[混合持久化](http://martinfowler.com/bliki/PolyglotPersistence.html)polyglot persistence方式。
分区的数据存储混合持久化架构有许多优点,包括了松耦合的服务以及更好的性能可扩展性。然而,它也引入了一些分布式数据管理方面的挑战。
分区的数据存储混合持久化架构有许多优点,包括了松耦合的服务以及更好的性能可扩展性。然而,它也引入了一些分布式数据管理方面的挑战。
第一个挑战是如何实现业务的事务在多个服务之间保持一致性。要了解此问题,让我们先来看一个在线 B2B 商店示例。Customer Service 顾客服务维护客户相关的信息包括信用额度。Order Service (订单)负责管理订单,并且必须验证新订单,不得超过客户的信用额度。在此应用程序的单体版本中Order Service 可以简单地使用 ACID 事务来检查可用信用额度并创建订单。
第一个挑战是如何实现业务的事务在多个服务之间保持一致性。要了解此问题,让我们先来看一个在线 B2B 商店示例。Customer Service 顾客服务维护客户相关的信息包括信用额度。Order Service (订单)负责管理订单,并且必须验证新订单没有超过客户的信用额度。在此应用的单体版本中Order Service 可以简单地使用 ACID 事务来检查可用信用额度并创建订单。
相比之下在微服务架构中ORDER (订单)和 CUSTOMER (顾客)表对其各自的服务都是私有的,如图 5-1 所示:
![图 5-1、每个微服务都有各自的数据](./resources/5-1.png)
Order Service 无法直接访问 CUSTOMER 表。它只能使用客户服务提供的 API。订单服务可能使用了[分布式事务](https://en.wikipedia.org/wiki/Two-phase_commit_protocol)也称为两阶段提交2PC。然而2PC 在现代应用中通常是不可行的。[CAP 定理](https://en.wikipedia.org/wiki/CAP_theorem)要求在可用性与 ACID 式一致性之间做出选择,可用性通常是更好的选择。此外,许多现代技术,如大多数 NoSQL 数据库,都不支持 2PC。维护服务和数据库之间的数据一致性至关重要因此我们需要另一套解决方案。
Order Service 无法直接访问 CUSTOMER 表。它只能使用 Customer Service 提供的 API。Order Service 可能使用了[分布式事务](https://en.wikipedia.org/wiki/Two-phase_commit_protocol)也称为两阶段提交2PC。然而2PC 在现代应用中通常是不可行的。[CAP 定理](https://en.wikipedia.org/wiki/CAP_theorem)要求在可用性与 ACID 式一致性之间做出选择,可用性通常是更好的选择。此外,许多现代技术,如大多数 NoSQL 数据库,都不支持 2PC。维护服务和数据库之间的数据一致性至关重要因此我们需要另一套解决方案。
第二个挑战是如何实现从多个服务中检索数据。例如,我们假设应用程序需要显示一个顾客和他最近的订单。如果 Order Service 提供了用于检索客户订单的 API那么您可以使用应用程序端连接以检索数据。应用程序从 Customer Service 中检索客户,并从 Order Service 中检索客户的订单。但是,假设 Order Service 仅支持通过主键查找订单(也许它使用了仅支持基于主键检索的 NoSQL 数据库)。这种情况下,没有有效的方法来检索所需的数据。
第二个挑战是如何实现从多个服务中检索数据。例如,我们假设应用需要显示一个顾客和他最近的订单。如果 Order Service 提供了检索客户订单的 API那么你可以使用应用端连接join来检索数据。应用从 Customer Service 中检索客户,并从 Order Service 中检索客户的订单。但是,假设 Order Service 仅支持通过主键查找订单(也许它使用了仅支持基于主键检索的 NoSQL 数据库)。这种情况下,没有有效的方法来检索所需的数据。
<a id="event-driven-architecture"></a>
## 5.2、事件驱动架构
许多应用使用了[事件驱动架构](https://martinfowler.com/eaaDev/EventNarrative.html)作为解决方案。在此架构中,微服务在发生某些重要事件时发布一个事件,例如更新业务实体时。其他微服务订阅了这些事件,当微服务接收到一个事件时,它可以更新自己的业务实体,这可能导致更多的事件被发布。
您可以使用事件实现跨多服务的业务的事务。一个事务由一系列的步骤组成。每个步骤包括了微服务更新业务实体和发布一个事件来触发下一步骤。下图依次展示了如何在创建订单时使用事件驱动方法来检查可用信用额度。
许多应用使用了[事件驱动架构](https://martinfowler.com/eaaDev/EventNarrative.html)作为解决方案。在此架构中,微服务在发生某些重要事件时发布一个事件,比如更新业务实体时。其他微服务订阅了这些事件,当接收到一个事件时,它可以更新自己的业务实体,可能会触发更多的事件被发布。
你可以使用事件来实现跨多服务业务的事务。一个事务由一系列的步骤组成。每个步骤包括了微服务更新业务实体和发布一个下一步需要触发的事件。下图依次展示了如何在创建订单时使用事件驱动方法来检查可用信用额度。
微服务通过 Message Broker (消息代理)进行交换事件:
@ -54,73 +57,78 @@ Order Service 无法直接访问 CUSTOMER 表。它只能使用客户服务提
更复杂的场景可能会涉及额外的步骤,例如在检查客户信用的同时保留库存。
假设a每个服务原子地更新数据库并发布一个事件 稍后再详细说明bMessage Broker 保证事件至少被送达一次,然后就实现了跨多服务的业务事务。需要注意的是,这些并不是 ACID 事务。它们只提供了更弱的保证,如[最终一致性](https://en.wikipedia.org/wiki/Eventual_consistency)。该事务模型称为 [BASE 模型](http://queue.acm.org/detail.cfm?id=1394128)。
假设a每个服务原子地更新数据库并发布一个事件 稍后再详细说明bMessage Broker 保证事件至少被送达一次,然后就实现了跨多服务的业务事务。需要注意的是,这些并不是 ACID 事务。它们只提供了更弱的保证,如[最终一致性](https://en.wikipedia.org/wiki/Eventual_consistency)。该事务模型称为 [BASE 模型](http://queue.acm.org/detail.cfm?id=1394128)。
属于多个微服务的数据构成的物化视图,你也可以用事件来维护。维护视图的服务订阅了相关事件并更新视图。图 5-5 展示了 Customer Order View Updater Service (客户订单视图更新服务)根据 Customer
你也可以用事件来维护由多个微服务数据构成的物化视图。维护视图的服务订阅了相关事件并更新视图。图 5-5 展示了 Customer Order View Updater Service (客户订单视图更新服务)根据 Customer
Service 和 Order Service 发布的事件更新 Customer Order View (客户订单视图)。
![图 5-5 Customer Order View 被两个服务访问](resources/5-5.png)
当 Customer Order View Updater Service 接收到 Customer 或 Order 事件时,它会更新 Customer Order View 的数据存储。可以使用如 MongoDB 之类的文档数据库实现 Customer Order View并为每个 Customer 存储一个文档。Customer Order View Query Service客户订单视图查询服务通过查询 Customer Order View 数据存储来处理获取一位客户和最近的订单的请求。
当 Customer Order View Updater Service 接收到 Customer 或 Order 事件时,它会更新 Customer Order View 的数据存储。可以使用如 MongoDB 之类的文档数据库实现 Customer Order View并为每个 Customer 存储一个文档。Customer Order View Query Service客户订单视图查询服务通过查询 Customer Order View 数据存储来处理获取一位客户和最近的订单的请求。
事件驱动的架构有几个优点与缺点。它能够实现跨越多服务并提供最终一致性的事务。另一个好处是它还使得应用程序能够维护[物化视图](https://en.wikipedia.org/wiki/Materialized_view)。
事件驱动的架构有几个优点与缺点。它能够实现跨越多服务并提供最终一致性的事务。另一个好处是它还使得应用能够维护[物化视图](https://en.wikipedia.org/wiki/Materialized_view)。
一个缺点是其编程模型比使用 ACID 事务更加复杂。通常,您必须实现补偿事务以从应用程序级别的故障中恢复。例如,如果信用检查失败,您必须取消订单。此外,应用程序必须处理不一致的数据。因为未提交的事务所做的更改是可见的。如果从未更新的物化视图中读取,应用程序依然可以看到不一致性。另一个缺点是订阅者必须要检测和忽略重复的事件。
一个缺点是其编程模型比使用 ACID 事务更加复杂。通常,你必须实现事务补偿以从应用级别的故障中恢复。例如,如果信用检查失败,你必须取消订单。此外,应用必须处理不一致的数据。因为未提交的事务所做的更改是可见的。如果从未更新的物化视图中读取,应用依然可以看到不一致性。另一个缺点是订阅者必须要检测和忽略重复的事件。
<a id="achieving-atomicity"></a>
## 5.3、实现原子性
在事件驱动架构中同样存在着原子更新数据库和发布事件相关问题。例如Order Service 必须在 ORDER 表中插入一行数据,并发布 Order Created 事件。这两个操作必须原子完成。如果在更新数据库后但在发布事件之前发生服务崩溃,系统将出现不一致性。确保原子性的标准方法是使用涉及到数据库和 Message Broker 的分布式事务。然而,由于上述原因,如 CAP 定理,这并不是我们想做的。
在事件驱动架构中同样存在着原子更新数据库和发布事件相关问题。例如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 所示。
实现原子性的一种方式是应用使用[仅涉及本地事务的多步骤过程](http://queue.acm.org/detail.cfm?id=1394128)来发布事件。诀窍在于存储业务实体状态的数据库中有一个用作消息队列的 EVENT 表。应用开启一个(本地)数据库事务,更新业务实体状态,将事件插入到 EVENT 表中,之后提交事务。有一个单独的应用线程或进程查询 EVENT 表,将事件发布到 Message Broker然后使用本地事务将事件标记为已发布。设计如图 5-6 所示。
![图 5-6、本地事务实现原子性](resources/5-6.png)
Order Service 将一行记录插入到 ORDER 表中,并将一个 Order Created 事件插入到 EVENT 表中。Event Publisher事件发布者线程或进程从 EVENT 表中查询未发布的事件,之后发布这些事件,最后更新 EVENT 表将事件标记为已发布。
这种方法有好有坏。好处是它保证了被发布的事件每次更新都不依赖于 2PC。此外应用程序发布业务级事件,这些事件可以消除推断的要。这种方法的缺点是它很容易出错,因为开发人员必须要记得发布事件。这种方法的局限性在于,由于其有限的事务和查询功能,在使用某些 NoSQL 数据库时,实现起来将是一大挑战。
这种方法有好有坏。好处是它保证了被发布的事件每次更新都不依赖于 2PC。此外应用发布业务级事件这些事件可以消除推断的要。这种方法的缺点是它很容易出错,因为开发人员必须要记得发布事件。这种方法的局限性在于,由于其有限的事务和查询功能,在使用某些 NoSQL 数据库时,实现起来将是一大挑战。
该方法通过让应用程序使用本地事务更新状态和发布事件来消除对 2PC 的依赖。现在我们来看一下通过应用程序简单地更新状态来实现原子性的方法。
该方法通过让应用使用本地事务更新状态和发布事件来消除对 2PC 的依赖。现在我们来看一下通过应用简单地更新状态来实现原子性的方法。
<a id="mining-a-database-transaction-log"></a>
## 5.5、挖掘数据库事务日志
不依靠 2PC 来实现原子性的另一种方式是用一个对数据库的事务或者提交日志进行挖掘的线程或进程来发布事件。当应用程序更新数据库时更改信息被记录到数据库的事务日志中。事务日志挖掘器Transaction Log Miner 线程或进程读取事务日志并向 Message Broker 发布事件。设计如图 5-7 所示。
不依靠 2PC 来实现原子性的另一种方式是用一个线程或进程来挖掘数据库的事务或提交日志来发布事件。当应用更新数据库时更改信息被记录到数据库的事务日志中。事务日志挖掘器Transaction Log Miner 线程或进程读取事务日志并向 Message Broker 发布事件。设计如图 5-7 所示。
![图 5-7、Message Broker 可以公断数据事务](resources/5-7.png)
一个使用此方法的示例是 LinkedIn Databus 开源项目。Databus 挖掘 Oracle 事务日志并发布与更改对应的事件。LinkedIn 使用 Databus 保持与系统的记录一致的各种派生数据存储
LinkedIn Databus 开源项目使用此方法。Databus 挖掘 Oracle 事务日志并发布与更改对应的事件。LinkedIn 使用 Databus 保持各种数据存储与系统的记录一致。
另一个例子是 AWS DynamoDB 中的流机制,它是一个托管的 NoSQL 数据库。DynamoDB 流包含了在过去 24 小时内对 DynamoDB 表中的项进行的更改(创建、更新和删除操作),其按时间顺序排列。应用程序可以从流中读取这些更改,比如,将其作为事件发布。
另一个例子是 AWS DynamoDB 中的流机制,它是一个托管的 NoSQL 数据库。DynamoDB 流包含了在过去 24 小时内对 DynamoDB 表中的项进行的更改(创建、更新和删除操作),其按时间顺序排列。应用可以从流中读取这些更改,比如,将其作为事件发布。
事务日志挖掘有各种好处与坏处。一个好处是它能保证被发布的事件每次更新都不依赖于 2PC。事务日志挖掘还可以通过将事件发布与应用程序的业务逻辑分离来简化应用程序。一个主要的缺点是事务日志的格式对于每个数据库来说都是专有的,甚至在不同数据库版本之间格式就发生了改变。而且,记录于事务日志中的低级别更新可能难以对高级业务事件进行逆向工程
事务日志挖掘有各种好处与坏处。一个好处是它能保证被发布的事件每次更新都不依赖于 2PC。事务日志挖掘还可以通过将事件发布从应用业务逻辑分离出来简化应用。一个主要的缺点是事务日志的格式对于每个数据库来说都是特有的,有些甚至在不同数据库版本之间格式就有了改变。而且,记录在事务日志中的低级别更新可能难以逆向到高级业务事件
事务日志挖掘消除了应用程序在做一件事时对 2PC 的依赖:更新数据库。现在我们来看看另一种可以消除更新并仅依赖于事件的不同方式。
事务日志挖掘消除了应用需要依赖 2PC 来做的一件事:更新数据库。现在我们来看看另一种可以消除更新并仅依赖于事件的不同方式。
<a id="using-event-sourcing"></a>
## 5.6、使用事件溯源
[事件溯源](https://github.com/cer/event-sourcing-examples/wiki/WhyEventSourcing)通过使用不同于之前的、以事件为中心的方式来持久化业务实体,实现无 2PC 原子性。应用程序不存储实体的当前状态,而是存储一系列状态改变事件。应用程序通过回放事件来重建实体的当前状态。无论业务实体的状态何时发生变化,其都会将新事件追加到事件列表中。由于保存事件是一个单一操作,因此具有原子性。
[事件溯源](https://github.com/cer/event-sourcing-examples/wiki/WhyEventSourcing)通过使用不同于之前的、以事件为中心的方式来持久化业务实体,实现无 2PC 原子性。应用不用存储实体的当前状态,而是存储一系列状态改变事件。应用通过回放事件来重建实体的当前状态。无论业务实体的状态何时发生变化,其都会将新事件追加到事件列表中。由于保存事件是单一操作,因此是原子性。
要了解事件溯源的工作原理,以 Order订单实体为例。在传统方式中每个订单都与 ORDER 表中的某行记录相映射,也可以映射到例如 ORDER_LINE_ITEM 表中的记录。
但当使用事件溯源时Order Service 将以状态更改事件的形式存储 OrderCreated创建、Approved批准、Shipped发货、Cancelled取消。每个事件包含足够的数据来重建 Order 的状态。
但当使用事件溯源时Order Service 将以状态更改事件的形式存储 OrderCreated创建、Approved批准、Shipped发货、Cancelled取消。每个事件包含足够的数据来重建 Order 的状态。
![图 5-8、事件有完整的数据](resources/5-8.png)
事件被持久化在事件存储中,事件存储是一个事件的数据库。该存储有一个用于添加和检索实体事件的 API。事件存储还与我们之前描述的架构中的 Message Broker 类似。它提供了一个 API使得服务能够订阅事件。事件存储向所有感兴趣的订阅者派发所有事件。可以说事件存储是事件驱动微服务架构的支柱。
事件溯源有几个好处。它解决了实现事件驱动架构的关键问题之一,可以在状态发生变化时可靠地发布事件。因此,它解决了微服务架构中的数据一致性问题。此外,由于它持久化的是事件,而不是领域对象,所以它主要避免了[对象关系阻抗失配问题](https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch)。事件溯源还提供了对业务实体所做更改的 100 可靠的审计日志,可以实现在任何时间点对实体进行时间查询以确定状态。事件溯源的另一个主要好处是您的业务逻辑包括松耦合的交换事件业务实体,这使得从单体应用程序迁移到微服务架构将变得更加容易。
事件溯源有几个好处。它解决了实现事件驱动架构的关键问题之一,可以在状态发生变化时可靠地发布事件。因此,它解决了微服务架构中的数据一致性问题。此外,由于它持久化的是事件,而不是领域对象,所以它主要避免了[对象关系阻抗失配问题](https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch)。事件溯源还提供了一个 100 可靠的审计日志,其记录了对业务实体的变更。此外,它可以在任何时间点对实体进行查询以确定状态。事件溯源的另一个好处是你的业务逻辑包括松耦合的交换事件业务实体,从单体应用迁移到微服务架构将变得更加容易。
事件溯源同样有缺点。这是一种不同而陌生的编程风格,因此存在学习曲线。事件存储仅支持通过主键查找业务实体。必须使用[命令查询责任分离](https://github.com/cer/event-sourcing-examples/wiki)CQRS来实现查询。因此应用程序必须处理最终一致的数据。
事件溯源同样有缺点。这是一种陌生的编程风格,因此存在学习曲线。事件存储仅支持通过主键查找业务实体。必须使用[命令查询责任分离](https://github.com/cer/event-sourcing-examples/wiki)CQRS来实现查询。因此应用必须处理最终一致的数据。
<a id="summary"></a>
## 5.7、总结
在微服务架构中,每个微服务都有私有的数据存储。不同的微服务可能会使用不同的 SQL 或者 NoSQL 数据库。虽然这种数据库架构具有明显的优势,但它创造了一些分布式数据管理挑战。第一个挑战是如何实现维护多个服务间的业务事务一致性。第二个挑战是如何实现从多个服务中检索数据。
在微服务架构中,每个微服务都有私有的数据存储。不同的微服务可能会使用不同的 SQL 或者 NoSQL 数据库。虽然这种数据库架构有明显的优势,但它创造了一些分布式数据管理挑战。第一个挑战是如何实现维护多个服务间的业务事务一致性。第二个挑战是如何实现从多个服务中检索数据。
大部分应用使用的解决方案是事件驱动架构。实现事件驱动架构的一个挑战是如何以原子的方式更新状态以及如何发布事件。有几种方法可以实现这点,包括了将数据库作为消息队列、事务日志挖掘和事件溯源。
@ -130,16 +138,18 @@ Order Service 将一行记录插入到 ORDER 表中,并将一个 Order Created
by Floyd Smith
基于微服务的存储方式涉及大数量和各种数据存储访问和更新数据将变得更加复杂DevOps 在维护数据一致性方面面临着更大的挑战。NGINX 为这种数据管理提供了重要支持,主要有三个方面:
基于微服务的存储方式涉及大数量和各种数据存储访问和更新数据将变得更加复杂DevOps 在维护数据一致性方面面临着更大的挑战。NGINX 为这种数据管理提供了重要支持,主要有三个方面:
1. **数据缓存与微缓存microcaching**
使用 NGINX 缓存静态文件和微缓存应用程序生成的内容可减轻应用程序的负载、提高性能并减少问题的发生。
使用 NGINX 缓存静态文件和微缓存应用可减轻应用的负载、提高性能并减少问题的发生。
2. **数据存储的灵活性与可扩展性**
一旦将 NGINX 作为反向代理服务器,您的应用程序在创建、调整大小、运行和调整数据存储服务器的大小时可获得很大的灵活性,以满足不断变化的需求 — 每个服务都拥有自己的数据存储是很重要的。
一旦将 NGINX 作为反向代理服务器,你的应用在创建、调整大小、运行和调整数据存储服务器大小时都能获得很大的灵活性,以满足不断变化的需求 — 每个服务都拥有自己的数据存储是很重要的。
3. **服务监控与管理,包括数据服务**
随着数据服务器数量的增加,支持复杂操作和具有监控和管理工具显得非常重要。[NGINX Plus](https://www.nginx.com/products/) 内置了这些工具和应用程序性能管理[合作伙伴](https://www.nginx.com/partners/)的接口,如 Data Dog、Dynatrace 和 New Relic。
随着数据服务器数量的增加,支持复杂操作、有监控和管理工具就显得非常重要了。[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/)的三大模型中找到,其为您设计决策和实施提供了起点。
微服务相关的数据管理示例可在 NGINX [微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)的三大模型中找到,其为你设计决策和实施提供了一个起点。

View File

@ -1,8 +1,8 @@
# 《微服务:从设计到部署》中文版
# 《微服务:从设计到部署》
本书 Chris Richardson 和 Floyd Smith 联合编写的微服务电子书 **[Designing and Deploying Microservices](https://www.nginx.com/resources/library/designing-deploying-microservices/)** 中文版其从不同角度全面介绍了微服务微服务的优点与缺点、API 网关、进程间通信IPC、服务发现、事件驱动数据管理、微服务部署策略、重构单体。
本书 Chris Richardson 和 Floyd Smith 联合编写的微服务电子书 **[Designing and Deploying Microservices](https://www.nginx.com/resources/library/designing-deploying-microservices/)** 的中文译本其从不同角度全面介绍了微服务微服务的优点与缺点、API 网关、进程间通信IPC、服务发现、事件驱动数据管理、微服务部署策略、重构单体。
- [Githubhttps://github.com/oopsguy/microservices-from-design-to-deployment-chinese](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese)
- [Githubhttps://github.com/docshome/microservices](https://github.com/docshome/microservices)
- [GitBookhttps://www.gitbook.com/book/docshome/microservices/details](https://www.gitbook.com/book/docshome/microservices/details)
本书对 Nginx 的描述不是很多,主要针对微服务领域。如果您想了解更多关于 Nginx 的内容,请参阅正在更新的 **[Nginx 中文文档](https://github.com/oopsguy/nginx-docs)**。
@ -12,6 +12,7 @@
### [0、前言](0-foreword.md)
### [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)
@ -21,6 +22,7 @@
- [微服务实战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#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)
@ -35,6 +37,7 @@
- [微服务实战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#introduction)
- [3.2、交互方式](3-inter-process-communication.md#interaction-styles)
- [3.3、定义 API](3-inter-process-communication.md#defining-apis)
@ -50,6 +53,7 @@
- [微服务实战NGINX 与应用程序架构](3-inter-process-communication.md#microservices-in-action)
### [4、服务发现](4-service-discovery.md)
- [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)
@ -61,6 +65,7 @@
- [微服务实战:灵活的 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#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)
@ -71,6 +76,7 @@
- [微服务实战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#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)
@ -81,6 +87,7 @@
- [微服务实战:使用 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#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)
@ -91,11 +98,13 @@
- [微服务实战:使用 NGINX 征服单体](7-refactoring-a-monolith-into-microservices.md#microservices-in-action)
## 感谢
- 感谢 Chris Richardson 和 Floyd Smith。
- 感谢 Github 网友的 [issue](https://github.com/DocsHome/microservices/issues) 和 [PR](https://github.com/DocsHome/microservices/pulls)
- [Contributor 名单](https://github.com/DocsHome/microservices/graphs/contributors)
## 许可
![](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)
本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。

View File

@ -1,6 +1,6 @@
{
"author": "oopsguy.com",
"description": "Nginx 经典微服务电子书中文本",
"description": "Nginx 经典微服务电子书中文译本",
"language": "zh-hans",
"extension": null,
"generator": "site",
@ -13,8 +13,8 @@
"weibo": true
},
"sidebar": {
"Oopsguy 的博客": "http://oopsguy.com",
"《微服务:从设计到部署》": "https://github.com/oopsguy/microservices-from-design-to-deployment-chinese"
"Oopsguy": "http://oopsguy.com",
"《微服务:从设计到部署》": "https://github.com/docshome/microservices"
}
},
"output": null,