# 1、微服务简介
目前微服务受到很多关注：文章、博客、社交媒体上的讨论和会议演讲。他们正在迅速走向[加德纳技术成熟度曲线（Gartner Hype cycle）](http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp)的高峰。与此同时，也有持怀疑态度的软件社区人员认为微服务没什么新鲜可言。反对者声称它的思想只是面向服务架构（SOA）的重塑。然而，无论是炒作还是怀疑，不可否认微服务架构模式具有非常明显的优势——特别是在实施敏捷开发和复杂的企业应用交付方面。

本章节是七章之中介绍关于的设计、构建和部署微服务方面的内容。您将了解微服务的由来和与传统[单体应用模式](http://microservices.io/patterns/monolithic.html)的比较。这本电子书描述了许多关于微服务架构方面的内容。无论是对您的项目的意义，或者是如何去应用它，您都将了解到微服务架构模式的优点和缺点。

我们先来看看为什么要考虑使用微服务。

## 1.1、构建单体应用
我们想一下，您开始制作一个新的打车应用，打算与 Uber 和 Hailo 竞争。经过初步交流与需求收集，您将手动或者使用类似 Rails、Spring Boot、Play或者Maven等平台来生成一个新项目。

这个新应用是一个模块化的六边形架构，如图 1-1 所示：

![图 1-1、一个简单的打车应用](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese/blob/master/resources/1-1.png)

应用程序的核心是由模块实现的业务逻辑，它定义了服务、领域对象和事件。围绕核心的是与外部世界接口对接的适配器。适配器示例包括了数据库访问组件、生产和消费消息的消息组件和 web 组件，它们暴露了 API 或者实现了一个 UI。

尽管有一个逻辑模块化的架构，但应用程序被作为一个整体进行打包和部署。实际格式取决于应用程序的语言和框架。例如，许多 Java 应用程序被打包成 WAR 文件并部署在如 Tomcat 或者 Jetty 之类的应用服务器上。其他 Java 应用程序是打包成自包含（self-contained）的可执行 JAR。类似地，Rails 和 Node.js 应用程序被打包为目录层次结构。

以这种风格编写的应用是很常见的。他们很容易开发，因为我们的 IDE 和其他工具专注于构建单个应用程序。这些应用程序也很容易测试。您可以通过简单地启动并使用如 Selenium 测试包来测试 UI 以轻松地实现端到端（end-to-end）测试。单体应用同样易于部署。你只需拷贝打包好的应用程序到服务器。您还可以通过运行多个副本和结合负载均衡器来扩展应用。在项目的早期阶段，它运作良好。

## 1.2、走向单体地狱
不幸的是，这种简单的方法有很大的局限性。成功的应用有一个趋势，随着时间推移而变得越来越臃肿。在每个冲刺时期，您的开发团队实现了更多的用户需求，这意味着添加了许多行代码。几年之后，您的小而简单的应用将会逐渐成长为[庞然大物似的单体](http://microservices.io/patterns/monolithic.html)。为了给出一个极端的示例，我最近和一位开发者做了交谈，他正在编写一个工具用于从他们的数百万行代码（lines of code，LOC）应用中分析数千个 JAR 之间的依赖。我相信这是大量开发者在多年齐心协力下创造了这样的野兽。

一旦您的应用程序成为了一个庞大、复杂的单体，您的开发组织可能陷入了一个令人痛苦的世界。敏捷开发和交付的任何一次尝试都将原地徘徊。一个主要问题是应用程序实在是非常复杂。对于任何一个开发人员来说，这是在太大了，这是可以理解的。最终，正确地修复 bug 和实现新功能变得非常困难且耗时。此外，这个趋势就像是往下的螺旋。如果基本代码难以理解，那么改变将不会变得正确。您最终得到的是一个巨大且不可思议的[大泥球](http://www.laputan.org/mud/)。

应用程序的规模也将减缓发展。应用程序越大，启动的时间越长。我[调查](https://plainoldobjects.com/2015/05/13/monstrous-monoliths-how-bad-can-it-get/)过开发者们的单体应用的大小和性能，一些报告的启动时间为 12 分钟。我也听说过应用程序启动需要 40 分钟以上的怪事。如果开发人员经常要重启应用服务器，那么很大一部分时间都是在等待中度过，他们的生产力将受到限制。

另一个大问题是，复杂的单体应用本身就是持续部署的障碍。如今，SaaS 应用发展到了每天可将变更推送到生产中多次。这对于复杂的单体非常困难，因为您必须重新部署整个应用程序才能更新其中任何一部分。这对我之前提到的漫长的启动时间也不会有什么帮助。此外，因为变化的影响通常不是很清楚，您很可能需要做大量的手工测试。因此，持续部署是不可能做到的。

当不同模块存在资源需求冲突时，单体应用也可能难以扩展。例如，一个模块可能会执行 CPU 密集型图像处理逻辑，理想情况下是部署在 [Amazon EC2 Compute Optimized 实例](http://aws.amazon.com/about-aws/whats-new/2013/11/14/announcing-new-amazon-ec2-compute-optimized-instances/)中。另一个模块可能是内存数据库，最适合 [EC2 Memory-optimized 实例](https://aws.amazon.com/cn/about-aws/whats-new/2014/04/10/r3-announcing-the-next-generation-of-amazon-ec2-memory-optimized-instances/)。然而，因为这些模块被部署在一起，您必须在硬件选择上做出妥协。

单体应用的另一个问题是可靠性。因为所有模块运行在同一进程。任何模块的一个 bug，比如内存泄漏，可能会拖垮整个进程。此外，由于应用程序的所有的实例都是相同的，该错误将影响到整个应用的可用性。

最后但并非是最不重要的，单体应用使得采用新的框架和语言变得非常困难。例如，我们假设您有 200 万行代码使用了 XYZ 框架编写。使用较新的 ABC 框架来重写整个应用，这将是非常昂贵的（在时间和成本方面），即使那个框架非常好。因此，这对于采用新技术是一个非常大的障碍。您在项目开始时无论选择何种新技术都将会感到困扰。

总结一下：您有一个成功的关键业务应用程序，它已经发展成为一个只有少数开发人员（如果有的话）能够理解的巨大单体。它使用了过时、非生产性技术编写的，这使得招聘优秀开发人员变得困难。应用程序难以扩展，不可靠。因此敏捷开发和应用交付是不可能的。

那么您能做些什么呢？

## 1.3、微服务-解决复杂问题
许多组织，如 Amazon、eBay 和 [Netflix](http://www.nginx.com/blog/microservices-at-netflix-architectural-best-practices/)，已经采用现在所谓的[微服务架构模式](http://microservices.io/patterns/microservices.html)解决了这个问题，而不是构建一个臃肿的单体应用。它的思路是将您的应用程序分解成一套较小的互连服务。

一个服务通常实现了一组不同的特性或者功能，例如订单管理、客户管理等。每一个微服务都是一个迷你应用，它自己的六边形架构包括业务逻辑以及多个适配器。

一些微服务会暴露一个供其他微服务或应用客户端消费的 API。其他微服务可能实现了一个 web UI。在运行时，每个实例通常是一个云虚拟机（virtual machine，VM）或者一个 Docker 容器。

例如，前面描述的系统可能分解成如图 1-2 所示：

![图 1-2、一个单体应用分解成微服务](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese/blob/master/resources/1-2.png)

应用程序的每个功能区域现在都由自己的微服务实现。此外，Web 应用程序被分为一组更简单的 Web 应用程序。例如，以我们的出粗车为例，一个专门是乘客的，一个专门是司机的。这使得它更容易地为特定的用户、司机、设备或者专门的用例部署不同的场景。每个后端服务暴露一个 REST API，大部分的服务消费由其他服务提供的 API。例如，司机管理使用了通知服务器来告知一个可用司机关于一个潜在路程。UI 服务调用了其他服务来渲染页面。服务也可以使用异步、基于消息的通信。本电子书后面将会详细介绍服务间的通信。

一些 REST API 也暴露给移动端应用供司机和乘客使用。然而，应用不能直接访问后端服务。相反，他们之间的通信是由一个称为 [API 网关](http://microservices.io/patterns/apigateway.html)（API Cateway）的中介负责。API 网关负责负载均衡、缓存、访问控制、API计量和监控，[可以通过使用 NGINX 来实现](http://www.nginx.com/solutions/api-gateway/)。第 2 章详细讨论 API 网关。

![图 1-3、开发和交付中的缩放立方（Scale Cube）](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese/blob/master/resources/1-3.png)

微服务架构模式相当于此缩放立方的 Y 轴坐标，此立方是一个来自[《架构即未来》](http://theartofscalability.com/)的三维伸缩模型。另外两个坐标轴是由运行多个相同应用程序副本的负载均衡器组成的 X 轴缩放和 Z 轴坐标（或者数据分区），其中请求的属性（例如，一行记录的主键或者客户标识）用于将请求路由到特定的服务器。

应用程序通常将这三种类型的坐标方式一起使用。Y 轴坐标将应用分解成微服务，如图 1-2 所示。

在运行时，X 坐标轴上运行着服务的多个实例，每一个服务配合负载均衡器以满足吞吐量和可用性。某些应用程序也有可能使用 Z 坐标轴来进行分区服务。图 1-4 展示了如何用 Docker 将旅途管理（Trip Management）服务部署到 Amazon EC2 上运行。

![图 1-4、使用 Docker 部署旅途管理服务](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese/blob/master/resources/1-4.png)

在运行时，旅途管理服务由多个服务实例组成，每个服务实例是一个 Docker 容器。为了实现高可用，容器是在多个云虚拟机上运行的。服务实例的前方是一个[如 NGINX 的负载均衡器](http://www.nginx.com/solutions/load-balancing/)，用于跨实例分发请求。负载均衡器也可以处理其他问题，如[缓存](http://www.nginx.com/resources/admin-guide/content-caching/)、[访问控制](http://www.nginx.com/resources/admin-guide/restricting-access/)、[API 度量](http://www.nginx.com/solutions/api-gateway/)和[监控](http://www.nginx.com/products/live-activity-monitoring/)。

微服务架构模式明显影响到了应用程序与数据库之间的关系。与与其他共享单个数据库模式（schema）服务不同，每一个服务都有自己的数据库模式。一方面，这种做法是与企业级数据库数据模型的想法不符，此外，它经常导致一些数据冗余。然而，如果您想从微服务中受益，每一个服务都应该有自己的数据库模式。因为它做到了松耦合。图 1-5 展示了数据库架构示例应用程序。

每个服务都有自己的数据库。而且，服务可以使用一种最适合其需求、号称多语言持久架构（polyglot persistence architecture）的数据库。例如，司机管理，找到与潜在乘客接近的司机必须使用支持高效地理查询的数据库。

![图 1-5、打车应用的数据库架构](https://github.com/oopsguy/microservices-from-design-to-deployment-chinese/blob/master/resources/1-4.png)

从表面上看，微服务架构模式类似于 SOA。微服务是由一组服务组成。然而，换另一种方式去思考微服务架构模式，它是没有商业化的 SOA，没有集成 [Web 服务规范](http://en.wikipedia.org/wiki/List_of_web_service_specifications)（WS-\*）和企业服务总线（Enterprise Service Bus，ESB）。基于微服务的应用支持更简单、轻量级的协议，例如，REST，而不是 WS-\*。他们也尽量避免使用 ESB，而是实现微服务本身具有类似 ESB 的功能。微服务架构也拒绝了 SOA 的其他部分，例如，数据访问[规范模式](https://en.wikipedia.org/wiki/Canonical_schema_pattern)概念。

## 1.4、微服务的优点
微服务架构模式有许多非常好的地方。第一，它解决了复杂问题。它把可能会变得庞大的单体应用程序分解成一套服务。虽然功能数量不变，但是应用程序已经被分解成可管理的块或者服务。每个服务都有一个以远程过程调用（RPC）驱动或者消息驱动的 API 明确定义的边界。微服务架构模式强制一定程度的模块化，实际上，使用单体基础代码来实现是极其困难的。因此，个体服务能被更快地开发，并更容易理解与维护。

第二，这种架构使得每个服务都可以由一个团队独立专注开发。开发者可以自由选择任何符合服务 API 契约的技术。当然，更多的组织是希望通过技术选型限制来避免完全混乱的状态。然而，这种自由意味着开发人员不再有可能在这种自由的新项目开始时使用过时的技术。当编写一个新服务时，他们可以选择当前的技术。此外，由于服务比较小，使用当前技术重写旧服务将变得更加可行。

第三，微服务架构模式可以实现每一个微服务独立部署。开发人员根本不需要去协调部署本地变更到它们的服务。这些变更一经测试即可立即部署。比如，UI 团队可以执行 A|B 测试，并快速迭代 UI 变更。微服务架构模式使得持续部署成为可能。

最后，微服务架构模式使得每个服务能够独立扩展。您可以仅部署满足每个服务的容量和可用性约束的实例数目。此外，您可以使用与服务资源要求最匹配的硬件。例如，您可以在 EC2 Compute Optimized 实例上部署一个 CPU 密集型图像处理服务，并且在 EC2 Memory-optimized 实例上部署一个内存数据库服务。

## 1.5、微服务的缺点
就像 Fred Brooks 近 30 年前写的[《人月神话》](https://en.wikipedia.org/wiki/The_Mythical_Man-Month)说的，没有银弹。像其他技术一样，微服务架构模式也是如此，存在着缺点。其中一个缺点就是名称本身。微服务这个术语的重点过多偏向于服务的规模。事实上，有些开发者主张构建极细粒度的 10-100 LOC（代码行） 服务虽然小型服务可能比较好，但重要的是要记住，小型服务只是一种手段，而不是主要目标。微服务的目标在于充分分解应用程序以方便应用敏捷开发和部署。

微服务另一个主要的缺点是由于微服务是一个分布式系统而变得复杂。开发者需要选择和实现基于消息或者 RPC 的进程间通信机制。此外，由于目标请求可能很慢或者不可用，他们还必须编写代码来处理局部故障。虽然这些都不是很复杂高深的事，但模块间通过语言级方法/过程调用相互调用，这比单体应用要复杂得多。

微服务的另一个挑战是分区数据库架构。更多多个业务实体的业务事务是相当普遍的。这些事务在单体应用中的实现显得微不足道，因为只有一个单独的数据库。在基于微服务的应用程序中，您需要更新不同服务所有用的数据库。通常不会选择分布式事务，不仅仅是因为 [CAP 定理](https://en.wikipedia.org/wiki/CAP_theorem)。他们根本不支持如今高度可扩展的 NoSQL 数据库和消息代理。您最后不得不使用最终基于一致性的方法，这对于开发人员来说更具挑战性。

测试微服务应用程序也很复杂。例如，使用现代的框架如 Sprig Boot，只需要编写一个测试类来启动一个单体 web 应用程序并测试其 REST API。相比之下，一个类似的测试类对于微服务来说需要启动该服务及其所依赖的任何服务，或者至少为这些服务配置存根。再次声明，虽然这不是一件高深的事情，但不要低估了这样做的复杂性。

微服务架构模式的另一个主要的挑战是实现了跨越多服务变更。例如，我们假设您正在实现一个变更服务 A、B 和 C 的需求，其中 A 依赖于 B，并且 B 依赖于 C。在单体应用程序中，您可以简单地修改相应的模块、整合变更并一次性部署他们。相反，在微服务中您需要仔细规划和协调出现的变更到每一个服务。例如，您需要更新服务 C，然后更新服务 B，最后更新服务 A。幸运的是，大多数变更只会影响一个服务；需要协调的多服务变更相对较少。

部署基于微服务的应用程序也是非常地复杂。一个单体应用可以很容易地部署到基于传统负载均衡器的一组相同的服务器上。每个应用程序实例都配置有基础设施服务的位置（主机和端口），比如数据库和消息代理。相比之下，微服务应用程序通常由大量的服务组成。例如，据 [Adrian Cockcroft](https://twitter.com/adrianco)，Hailo 拥有 160 个不同的服务，Netflix 拥有超过 600 个服务。

每个服务都有多个运行时实例。还有更多的移动部件需要配置、部署、扩展和监控。此外，您还需要实现服务发现机制，使得服务能够发现需要与之通信的任何其他服务的位置（主机和端口）。传统比较麻烦的基于票据（ticket-based）和手动操作方式无法扩展到如此复杂程度。因此，成功部署微服务应用程序要求开发人员能高度控制部署方式和高度自动化。

一种自动化的方式是使用现成的平台即服务（PaaS），如 [Cloud Foundry](https://www.cloudfoundry.org/)。PaaS 为开发人员提供了一种简单的方式来部署和管理他们的微服务。它让开发人员避开了诸如采购和配置 IT 资源等烦恼。同时，配置 PaaS 的系统与网络专业人员可以确保最佳实践和落实公司策略。

自动化微服务部署的另一个方式是开发自己的 PaaS。一个普遍的起点是使用集群方案，如 [Kubernetes](https://kubernetes.io/)，与 Docker 等容器技术相结合。在本书最后我们将看到[基于软件的应用交付](http://www.nginx.com/products/)方式如 NGINX 是如何在微服务级别处理缓存、访问控制、API 计量和监控，可以帮助解决这个问题。

## 1.6、总结
构建复杂的微服务应用程序本质上是困难的。单体架构模式只适用于简单、轻量级的应用程序，如果您使用它来构建复杂应用，您最终会陷入一个痛苦的世界。微服务架构模式是复杂、持续发展应用的一个更好的选择。尽管它存在着缺点和实现挑战。

在后面的章节中，我将介绍微服务架构的方方面面并探讨诸如服务发现、服务部署方案以及将单体应用重构为服务的策略。

## 微服务实战：NGINX Plus 作为反向代理服务器

By Floyd Smith

[10000 个网站中有超过 50%](http://w3techs.com/technologies/cross/web_server/ranking) 使用 NGINX，这主要是因为它具有作为反向代理服务器的能力。您可以 NGINX 放在当前应用程序前面甚至是数据库服务器以获取各种功能 —— 更高的性能、更高的安全性、可扩展性、灵活性等。你现有的应用程序只需要配置代码和作出很少或无需改变。对于存在性能压力的站点，或者预计未来存在高负荷，效果看起来似乎没那么神奇。

那么这与微服务有什么关系呢？实现一个反向代理服务器，并使用 NGINX 的其他功能来为您提供架构灵活性。反向代理服务器、静态和应用文件缓存、SSL/TLS 和 HTTP/2 都会从您的应用程序剔除。让应用程序只做它该做的事，NGINX 还可作为负载均衡器，微服务实施过程中的一个关键角色。先进的 NGINX Plus 的功能包含了复杂的负载均衡算法、多种方式的会话持久和管理监控，这些对微服务尤其有用（NGINX 最近还增加了使用 DNS SRV 记录的服务发现支持，这是一个顶尖的功能）。而且，如本章所述，NGINX 可以自动化部署微服务。

此外，NGINX 还提供了必要的功能来支撑 [NGINX 微服务参考架构](https://www.nginx.com/blog/introducing-the-nginx-microservices-reference-architecture/)中的三大模型。代理模型使用 NGINX 作为 API 网关；网格路由模型使用了一个额外的 NGINX 作为进程间通信中枢；Fabric 模型中每个微服务使用一个 NGINX 来控制 HTTP 流量，在微服务之间实现 SSL/TLS，这非常具有突破性。
