第一章——微服务简介
走向单体地狱
- 应用程序变得过于庞大,难以正确修复 bug 和实现新功能
- 复杂的单体应用阻碍了持续部署
- 任何一个模块的 bug 影响到了整个应用的可靠性
- 采用新技术和框架很困难
总结一下:您有个成功的关键业务应用程序,它已经发展成为只有少数开发人员(如果有的话)能够理解的巨大单体。它使用了过时、非生产性技术编写,这使得招聘优秀开发人员变得非常困难。应用程序变得难以扩展,不可靠。因此敏捷开发和应用交付是不可能的。
微服务——解决复杂问题
微服务的优点
- 解决了复杂问题。个体服务能被更快地开发,并更容易理解和维护。
- 每一个服务都可以由一个团队专注开发。可使用新技术重写旧服务。
- 微服务架构模式可以实现每一个微服务独立部署,做到持续部署。也能够独立扩展。
微服务的缺点
- 过多偏向于服务的规模。
- 模块间的相互调用复杂。
- 分区数据库架构,不支持 NoSQL 和消息代理,不得不采用最终一致性的方法。
- 项目部署复杂。
第二章——API网关
客户端与微服务直接通信
问题在于——
- 客户端的需求与每个微服务暴露的细粒度 API 不匹配。
- 有些微服务可能使用了非 Web 友好协议(如Thrift,AMQP),这两个协议对浏览器和防火墙都是不友好的。应用程序在防火墙之外最好使用 HTTP 或 WebSocket 协议。ggg
- 难以重构微服务。
使用 API 网关
API 网关是一个服务器,是系统的单入口点。它类似于面向对象设计模式中的门面(Facade)模式。API 网关封装了内部系统架构,并针对每个客户端提供一个定制 API。它还可用于认证、监控、负载均衡、缓存和静态响应处理。
API 网关的优点和缺点
优点
它封装了应用程序的内部结构。客户端只需要与网关通信,而不必调用特定的服务。API 网关为每种类型的客户端提供了特定的 API,减少了客户端与应用程序之间的往返次数。同时,它还简化了客户端的代码。
缺点
它是另一个高度可用的组件,需要开发、部署和管理。另外,还有一个风险是 API 网关可能会成为开发瓶颈。开发人员必须更新 API 网关以暴露每个微服务的端点。重要的是更新 API 网关的过程应尽可能地放缓一些。否则,开发人员将被迫排队等待网关更新。
总结
对于大多数基于微服务的应用程序来说,实现一个 API 网关是很有意义的,API 网关充当着系统的单入口点,并且负责请求路由,组合和协议转换。它为每个应用程序客户端提供了一个自定义 API。API 网关还可以通过返回缓存或默认数据来掩盖后端服务故障。
第三章——进程间通信
交互方式分类
一对一交互 与 一对多交互
- 一对一 —— 每个客户端请求都由一个服务实例处理。
- 一对多 —— 每个请求由多个服务实例处理。
同步交互 与 异步交互
- 同步 —— 客户端要求服务及时响应,在等待过程中可能会发生阻塞。
- 异步 —— 客户端在等待响应时不会发生阻塞,但响应(如果有)不一定立即返回。
一对一交互分为以下列举的类型,包括同步(请求/响应)与异步(通知与请求/异步响应)
- 请求/响应 —— 客户端向服务发出请求并等待响应。客户端要求响应及时到达。在基于线程的应用程序中,发出请求的线程可能在等待时发生阻塞。
- 通知(又称为单向请求) —— 客户端向服务发送请求,但不要求响应。
- 请求/异步响应 —— 客户端向服务发送请求,服务异步响应。客户端在等待时不发生阻止,适用于假设响应可能不会立即到达的场景。
一对多交互可分为以下列举的类型,它们都是异步的
- 发布/订阅 —— 客户端发布通知消息,由零个或多个感兴趣的服务消费。
- 发布/异步响应 —— 客户端发布请求消息,之后等待一定时间来接收消费者的响应。
IPC 技术
有多种 IPC 技术可供选择。服务可以使用基于同步请求/响应的通信机制,比如基于 HTTP 的 REST 或 Thrift。或者,可以使用异步、基于消息的通信机制,如 AMQP 或 STOMP。
异步、基于消息的通信
消息通过通道进行交换。通道类型分为两类,点对点(point‑to‑point)与发布订阅(publish‑subscribe):
- 点对点通道发送一条消息给一个切确的、正在从通道读取消息的消费者。服务使用点对点通道,就是上述的一对一交互方式。
- 发布订阅通道将每条消息传递给所有已订阅的消费者。服务使用发布订阅通道,就是上述的一对多交互方式。
消息传递的优点
- 将客户端与服务分离
- 消息缓冲
- 灵活的客户端 — 服务交互 消息传递支持前面提到的所有交互方式。
- 毫无隐瞒的进程间通信 基于 RPC 的机制试图使调用远程服务看起来与调用本地服务相同。然而,由于物理因素和局部故障的可能性,他们实际上是完全不同的。消息传递使这些差异变得非常明显,所以开发人员不会被这些虚假的安全感所欺骗。
消息传递的缺点
- 额外的复杂操作 消息传递系统是一个需要安装、配置和操作的系统组件。消息代理程序必须高度可用,否则系统的可靠性将受到影响。
- 实施基于请求/响应式交互的复杂性 请求/响应式交互需要做些工作来实现。每个请求消息必须包含应答通道标识符和相关标识符。该服务将包含相关 ID 的响应消息写入应答信道。客户端使用相关 ID 将响应与请求相匹配。通常使用直接支持请求/响应的 IPC 机制更加容易。
同步的请求/响应 IPC
- REST
- Thrift
第四章——服务发现
为何使用服务发现
在现代基于云的微服务应用中,服务实例具有动态分配的网络位置。此外,由于自动扩缩、故障与升级,整组服务实例会动态变更。因此,客户端代码需要使用更精确的服务发现机制。
有两种主要的服务发现模式:客户端发现(client-side discovery)与服务端发现(server-side discovery)。
客户端发现模式
客户端负责确定可用服务实例的网络位置和请求负载均衡。客户端查询服务注册中心(service registry),它是可用服务实例的数据库。之后,客户端利用负载均衡算法选择一个可用的服务实例并发出请求。
服务实例的网络位置在服务注册中心启动时被注册。当实例终止时,它将从服务注册中心中移除。通常使用心跳机制周期性地刷新服务实例的注册信息。
优点与缺点分析
- 该模式相对比较简单,除了服务注册中心,没有其他移动部件。
- 此外,由于客户端能发现可用的服务实例,因此可以实现智能的、特定于应用程序的负载均衡决策,比如使用一致性哈希。
- 该模式的一个重要缺点是它将客户端与服务注册中心耦合在一起。您必须为服务客户端使用的每种编程语言和框架实现客户端服务发现逻辑。
服务端发现模式
客户端通过负载均衡器向服务发出请求。负载均衡器查询服务注册中心并将每个请求路由到可用的服务实例。与客户端发现一样,服务实例由服务注册中心注册与销毁。
优点与缺点分析
- 该模式的一大的优点是其把发现的细节从客户端抽象出来。客户端只需向负载均衡器发出请求。这消除了为服务客户端使用的每种编程语言和框架都实现发现逻辑的必要性。
- 然而,这种模式存在一些缺点。除非负载均衡器由部署环境提供,否则您需要引入这个高可用系统组件,并进行设置和管理。
服务注册中心
服务注册中心(service registry)是服务发现的一个关键部分。它是一个包含了服务实例网络位置的数据库。服务注册中心必须是高可用和最新的。虽然客户端可以缓存从服务注册中心获得的网络位置,但该信息最终会过期,客户端将无法发现服务实例。因此,服务注册中心由使用了复制协议(replication protocol)来维护一致性的服务器集群组成。
服务注册方式
-
自注册模式
服务实例负责在服务注册中心注册和注销自己。此外,如果有必要,服务实例将通过发送心跳请求来防止其注册信息过期。
该方式的一个很好的范例就是 Netflix OSS Eureka 客户端。Eureka 客户端负责处理服务实例注册与注销的所有方面。实现了包括服务发现在内的多种模式的 Spring Cloud 项目可以轻松地使用 Eureka 自动注册服务实例。您只需在 Java Configuration 类上应用 @EnableEurekaClient 注解即可。
自注册模式有好有坏。一个好处是它相对简单,不需要任何其他系统组件。然而,主要缺点是它将服务实例与服务注册中心耦合。您必须为服务使用的每种编程语言和框架都实现注册代码。 -
第三方注册模式
当使用第三方注册模式时,服务实例不再负责向服务注册中心注册自己。相反,该工作将由被称为服务注册器(service registrar)的另一系统组件负责。服务注册器通过轮询部署环境或订阅事件来跟踪运行实例集的变更情况。当它检测到一个新的可用服务实例时,它会将该实例注册到服务注册中心。此外,服务注册器可以注销终止的服务实例。
第三方注册模式同样有好有坏。一个主要的好处是服务与服务注册中心之间解耦。您不需要为开发人员使用的每种编程语言和框架都实现服务注册逻辑。相反,仅需要在专用服务中以集中的方式处理服务实例注册。
该模式的一个缺点是,除非部署环境内置,否则您同样需要引入这样一个高可用的系统组件,并进行设置和管理。
总结
在微服务应用程序中,运行的服务实例集会动态变更。实例具有动态分配的网络位置。因此,为了让客户端向服务发出请求,它必须使用服务发现机制。
服务发现的一个关键部分是服务注册中心。服务注册中心是一个可用服务实例的数据库。服务注册中心提供了管理 API 和查询 API 的功能。服务实例通过使用管理 API 从服务注册中心注册或者注销。系统组件使用查询 API 来发现可用的服务实例。
有两种主要的服务发现模式:客户端发现与服务端发现。在使用了客户端服务发现的系统中,客户端查询服务注册中心,选择一个可用实例并发出请求。在使用了服务端发现的系统中,客户端通过路由进行请求,路由将查询服务注册中心,并将请求转发到可用实例。
服务实例在服务注册中心中注册与注销有两种主要方式。一个是服务实例向服务注中心自我注册,即自注册模式。另一个是使用其他系统组件代表服务完成注册与注销,即第三方注册模式。
第五章——事件驱动数据管理
当我们转向微服务架构时,数据访问将变得非常复杂。因为每个微服务所拥有的数据对当前微服务来说是私有的,只能通过其提供的 API 进行访问。封装数据可确保微服务松耦合、独立演进。如果多个服务访问相同的数据,模式(schema)更新需要对所有服务进行耗时、协调的更新。
更糟糕的是,不同的微服务经常使用不同类型的数据库。现代应用程序存储和处理着各种数据,而关系型数据库并不总是最佳选择。在某些场景,特定的 NoSQL 数据库可能具有更方便的数据模型,提供了更好的性能和可扩展性。例如,存储和查询文本的服务使用文本搜索引擎(如 Elasticsearch)是合理的。类似地,存储社交图数据的服务应该可以使用图数据库,例如 Neo4j。因此,基于微服务的应用程序通常混合使用 SQL 和 NoSQL 数据库,即所谓的混合持久化(polyglot persistence)方式。
分布式数据管理方面的挑战
- 如何实现维护多个服务之间的业务事务一致性。
- 如何实现从多个服务中检索数据。
解决方案——事件驱动架构
在此架构中,微服务在发生某些重要事件时发布一个事件,例如更新业务实体时。其他微服务订阅了这些事件,当微服务接收到一个事件时,它可以更新自己的业务实体,这可能导致更多的事件被发布。
假设(a)每个服务原子地更新数据库并发布事件,稍后再更新,(b)Message Broker 保证事件至少被传送一次,您可以实现跨多服务的业务事务。需要注意的是,这些并不是 ACID 事务。它们只提供了更弱的保证,如最终一致性。该事务模型称为 BASE 模型。
优点与缺点
- 它能够实现跨越多服务并提供最终一致性事务。
- 它还使得应用程序能够维护物化视图。
- 一个缺点是其编程模型比使用 ACID 事务更加复杂。通常,您必须实现补偿事务以从应用程序级别的故障中恢复。例如,如果信用检查失败,您必须取消订单。此外,应用程序必须处理不一致的数据。因为未提交的事务所做的更改是可见的。如果从未更新的物化视图中读取,应用程序依然可以看到不一致性。
- 另一个缺点是订阅者必须要检测和忽略重复的事件。
// TODO 这一章后面有的目前没看懂,以后有机会再回来看看
第六章——选择部署策略
单主机多服务实例模式
当使用此模式时,您可以提供一个或多个物理主机或虚拟主机,并在每个上运行多个服务实例。
优点
- 主要优点是其资源使用率相对较高。多个服务实例共享服务器及其操作系统。如果进程或进程组运行了多个服务实例(例如,共享相同的 Apache Tomcat 服务器和 JVM 的多个 Web 应用程序),则效率更高。
- 部署服务实例相对较快。您只需将服务复制到主机并启动它。如果服务是使用 Java 编写的,则可以复制 JAR 或 WAR 文件。对于其他语言,例如 Node.js 或 Ruby,您可以直接复制源代码。在任一情况下,通过网络复制的字节数都是相对较小的。
- 由于缺乏开销,通常启动一个服务是非常快的。如果该服务是自己的进程,你只需要启动它即可。如果服务是在同一容器进程或进程组中运行的几个实例之一,则可以将其动态部署到容器中或者重新启动容器。
缺点
- 服务实例很少或者没有隔离,除非每个服务实例是一个单独的进程。虽然您可以准确地监视每个服务实例的资源利用率,但是您不能限制每个实例使用的资源。一个行为不当的服务实例可能会占用掉主机的所有内存或 CPU。如果多个服务实例在同一进程中运行,那么将毫无隔离可言。例如,所有实例可能共享相同的 JVM 堆。行为不当的服务实例可能会轻易地破坏在同一进程中运行的其他服务。此外,您无法监控每个服务实例使用的资源。
- 部署服务的运维团队必须了解执行此操作的具体细节。服务可以用多种语言和框架编写,因此开发团队必须与运维交代许多细节。这种复杂性无疑加大了部署过程中的错误风险。
每个主机一个服务实例模式
这种模式有两种不同形式:每个虚拟机一个服务实例模式和每个容器一个服务实例模式。
每个虚拟机一个服务实例模式
优点
- VM 的主要优点是每个服务实例运行是完全隔离的。它有固定数量的 CPU 和内存,且不能从其他服务窃取资源。
- 可以利用成熟的云基础架构。如 AWS 之类的云提供了有用的功能,例如负载平衡和自动扩缩。
- 它封装了服务的实现技术。一旦服务被打包成一个虚拟机,它就成为一个黑匣子。VM 的管理 API 成为部署服务的 API。部署变得更加简单、可靠。
缺点
- 资源利用率较低。每个服务实例都有一整个 VM 开销,包括操作系统。此外,在一个典型的公共 IaaS 中,VM 具有固定大小,并且 VM 可能未被充分利用。
- 公共 IaaS 中的 VM 通常是收费的,无论它们是处于繁忙还是空闲。如 AWS 之类的 IaaS 虽然提供了自动扩缩功能,但很难快速响应需求变化。因此,您经常需要过度配置 VM,从而增加部署成本。
- 部署新版本的服务时通常很慢。由于大小原因,通常 VM 镜像构建很慢。此外,VM 实例化也很慢,同样是因为它们的大小。而且,操作系统也需要一些时间来启动。但请注意,这并不普遍,因为已经存在由 Boxfuse 构建的轻量级 VM。
- 通常您(或组织中的其他人)要对很多未划分的重担负责。除非您使用 Boxfuse 这样的工具来处理构建和管理虚拟机的开销,否则这将是您的责任。这个必要而又耗时的活动会分散您的核心业务。
每个容器一个服务实例模式
优点
- 容器的优点与虚拟机的类似。它们将服务实例彼此隔离。您可以轻松地监控每个容器所消耗的资源。此外,与 VM 一样,容器封装了服务实现技术。容器管理 API 作为管理您的服务的 API。
- 然而,与虚拟机不同,容器是轻量级技术。容器镜像通常可以非常快速地构建。例如,在我的笔记本电脑上,将一个 Spring Boot 应用程序打包成一个 Docker 容器只需要 5 秒钟的时间。容器也可以很快地启动,因为没有繁琐的操作系统引导机制。当一个容器启动时,它所运行的就是服务。
缺点
- 虽然容器基础架构正在快速发展走向成熟,但它并不像虚拟机的基础架构那么成熟。此外,容器不像 VM 那样安全,因为容器彼此共享了主机的 OS 内核。
- 您需要负责未划分的容器镜像管理重担。此外,除非您使用了托管容器解决方案[如 Google Container Engine 或 Amazon EC2 Container Service(ECS)],否则您必须自己管理容器基础设施以及可能运行的 VM 基础架构。
- 此外,容器通常部署在一个按单个 VM 收费的基础设施上。因此,如之前所述,可能会产生超额配置 VM 的额外成本,以处理负载峰值。