Spring微服务实战第1章 欢迎迈入云世界,Spring

第1章 欢迎迈入云世界,Spring

1.1 什么是微服务

微服务架构具有以下特征。

  • 应用程序逻辑分解为具有明确定义了职责范围的细粒度组件,这些组件互相协调提供解决方案。
  • 每个组件都有一个小的职责领域,并且完全独立部署。微服务应该对业务领域的单个部分负责。此外,一个微服务应该可以跨多个应用程序复用。
  • 微服务通信基于一些基本的原则(注意,我说的是原则而不是标准),并采用HTTP和JSON(JavaScript Object Notation)这样的轻量级通信协议,在服务消费者和服务提供者之间进行数据交换。
  • 服务的底层采用什么技术实现并没有什么影响,因为应用程序始终使用技术中立的协议(JSON是最常见的)进行通信。这意味着构建在微服务之上的应用程序能够使用多种编程语言和技术进行构建。
  • 微服务利用其小、独立和分布式的性质,使组织拥有明确责任领域的小型开发团队。这些团队可能为同一个目标工作,如交付一个应用程序,但是每个团队只负责他们在做的服务。

1.2 什么是Spring,为什么它与微服务有关

在基于Java的应用程序构建中,Spring已经成为了事实上的标准开发框架。Spring的核心是建立在依赖注入的概念上的。在普通的Java应用程序中,应用程序被分解成为类,其中每个类与应用程序中的其他类经常有明显的联系,这些联系是在代码中直接调用类的构造器,一旦代码被编译,这些联系点将无法修改。

这在大型项目中是有问题的,因为这些外部联系是脆弱的,并且进行修改可能会对其他下游代码造成多重影响。依赖注入框架(如Spring),允许用户通过约定(以及注解)将应用程序对象之间的关系外部化,而不是在对象内部彼此硬编码实例化代码,以便更轻松地管理大型Java项目。Spring在应用程序的不同的Java类之间充当一个中间人,管理着它们的依赖关系。Spring本质上就是让用户像玩乐高积木一样将自己的代码组装在一起。

Spring能够快速引入特性的特点推动了它的实际应用,使用J2EE技术栈开发应用的企业级Java开发人员迅速采用它作为一个轻量级的替代方案。J2EE栈虽然功能强大,但许多人认为它过于庞大,甚至许多特性从未被应用程序开发团队使用过。此外,J2EE应用程序强制用户使用成熟的(和沉重的)Java应用程序服务器来部署自己的应用程序。

Spring框架的迷人之处在于它能够与时俱进并进行自我改造——它已经向开发社区证明了这一点。Spring团队发现,许多开发团队正在从将应用程序的展现、业务和数据访问逻辑打包在一起并部署为单个制品的单体应用程序模型中迁移,正转向高度分布式的模型,服务能够被构建成可以轻松部署到云端的小型分布式服务。为了响应这种转变,Spring开发团队启动了两个项目,即Spring Boot和Spring Cloud。

Spring Boot是对Spring框架理念重新思考的结果。虽然Spring Boot包含了Spring的核心特性,但它剥离了Spring中的许多“企业”特性,而提供了一个基于Java的、面向REST[1]的微服务框架。只需一些简单的注解,Java开发者就能够快速构建一个可打包和部署的REST 微服务,这个微服务并不需要外部的应用容器。

注意

虽然本书会在第2章中更详细地介绍REST,但REST背后最为核心的概念是,服务应该使用HTTP动词(GET、POST、PUT和DELETE)来代表服务中的核心操作,并且使用轻量级的面向Web的数据序列化协议(如JSON)来从服务请求数据和从服务接收数据。

在构建基于云的应用时,微服务已经成为更常见的架构模式之一,因此Spring社区为开发者提供了Spring Cloud。Spring Cloud框架使实施和部署微服务到私有云或公有云变得更加简单。Spring Cloud在一个公共框架之下封装了多个流行的云管理微服务框架,并且让这些技术的使用和部署像为代码添加注解一样简便。本章随后将介绍Spring Cloud中的不同组件。

1.5 使用Spring Boot来构建微服务

我一直以来都持有这样一个观点:如果一个软件开发框架通过了被我亲切地称为“卡内尔猴子测试”[[2]](javascript:void(0))的试验,我就认为它是经过深思熟虑和易于使用的。如果一只像我(作者)这样的“猴子”能够在10 min或者更少时间内弄明白一个框架,那么这个框架就通过了这个试验。这就是我第一次写Spring Boot服务示例的感觉。我希望读者也有同样的体验和快乐,所以,让我们花一点儿时间,看看如何使用Spring编写一个简单的“Hello World”REST服务。

在本节中,我们不会详细介绍大部分代码。这个例子的目标是让读者体会一下编写Spring Boot服务的感受。第2章中会深入更多的细节。

图1-3展示了这个服务将会做什么,以及Spring Boot微服务将会如何处理用户请求的一般流程。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第1张图片

图1-3 Spring Boot抽象出了常见的REST微服务任务(路由到业务逻辑、从URL中解析HTTP参数、JSON与对象相互映射),并让开发人员专注于服务的业务逻辑

这个例子并不详尽,甚至没有说明应该如何构建一个生产级别的微服务,但它同样值得我们注意,因为它只需要写很少的代码。在第2章之前,我不打算介绍如何设置项目构建文件或代码的细节。如果读者想要查看Maven pom.xml文件以及实际代码,可以在第1章对应的代码中找到它。第1章中的所有源代码都能在本书的GitHub存储库找到。

在这个例子中,创建一个名为Application的Java类(在simpleservice/src/com/thoughtmechanix/application/simpleservice/Application.java的Java类,它公开了一个名为/hello的REST端点。Application类的代码,如代码清单1-1所示。

代码清单1-1 使用Spring Boot的Hello World:一个简单的Spring微服务

package com.thoughtmechanix.simpleservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; 
import org.springframework.web.bind.annotation.PathVariable;

@SpringBootApplication  ⇽--- 告诉Spring Boot框架,该类是Spring Boot服务的入口点
@RestController  ⇽--- 告诉Spring Boot,要将该类中的代码公开为Spring RestController@RequestMapping(value="hello")  ⇽--- 此应用程序中公开的所有URL将以/ hello前缀开头
public class Application {

    public static void main(String[] args) { 
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping(value="/{firstName}/{lastName}",  ⇽---  Spring Boo公开为一个基于GET方法的REST端点,它将使用两个参数,即firstName和lastName
    ➥  method = RequestMethod.GET)
    public String hello( @PathVariable("firstName") String firstName,   ⇽--- 将URL中传入的firstName和lastName参数映射为传递给hello方法的两个变量
    ➥  @PathVariable("lastName") String lastName) { 
        return String.format("{\"message\":\"Hello %s %s\"}",  ⇽--- 返回一个手动构建的简单JSON字符串。在第2章中,我们不需要创建任何JSON
        ➥  firstName, lastName);
        }
    }

代码清单1-1中主要公开了一个GET HTTP端点,该端点将在URL上取两个参数(firstName和lastName),然后返回一个包含消息“Hello firstName lastName”的净荷的简单JSON字符串。如果在服务上调用了端点/hello/john/carnell,返回的结果(马上展示)将会是:

{“message”:“Hello john carnell”}
让我们启动服务。为此,请转到命令提示符并输入以下命令:

mvn spring-boot:run
这条mvn命令将使用Spring Boot插件,然后使用嵌入式Tomcat服务器启动应用程序。

1.6 为什么要改变构建应用的方式

我们正处于历史的拐点。现代社会的几乎所有方面都可以通过互联网连接在一起。习惯于为当地市场服务的公司突然发现,他们可以接触到全球的客户群,全球更大的客户群一起涌进来的同时也带来了全球竞争。这些竞争压力意味着以下力量正在影响开发人员考虑构建应用程序的方式。

  • 复杂性上升——客户期望组织的所有部门都知道他们是谁。与单个数据库通信并且不与其他应用程序集成的“孤立的”应用程序已不再是常态。如今,应用程序不仅需要与多个位于公司数据中心内的服务和数据库进行通信,还需要通过互联网与外部服务提供商的服务和数据库进行通信。
  • 客户期待更快速的交付——客户不再希望等待软件包的下一次年度发布或整体版本更新。相反,他们期望软件产品中的功能被拆分,以便在几周(甚至几天)内即可快速发布新功能,而无需等待整个产品发布。
  • 性能和可伸缩性——全球性的应用程序使预测应用程序将处理多少事务量以及何时触发该事务量变得非常困难。应用程序需要快速跨多个服务器进行扩大,然后在事务量高峰过去时进行收缩。
  • 客户期望他们的应用程序可用——因为客户与竞争对手之间只有点击一下鼠标的距离,所以企业的应用程序必须具有高度的弹性。应用程序中某个部分的故障或问题不应该导致整个应用程序崩溃。

为了满足这些期望,作为应用开发人员,我们不得不接受这样一个悖论:构建高可伸缩性和高度冗余的应用程序。我们需要将应用程序分解成可以互相独立构建和部署的小型服务。如果将应用程序“分解”为小型服务,并将它们从单体制品中转移出来,那么就可以构建具有下面这些特性的系统。

  • 灵活性——可以将解耦的服务进行组合和重新安排,以快速交付新的功能。一个正在使用的代码单元越小,更改代码越不复杂,测试部署代码所需的时间越短。
  • 有弹性——解耦的服务意味着应用程序不再是单个“泥浆球”,在这种架构中其中一部分应用程序的降级会导致整个应用程序失败。故障可以限制在应用程序的一小部分之中,并在整个应用程序遇到中断之前被控制。这也使应用程序在出现不可恢复的错误的情况下能够优雅地降级。
  • 可伸缩性——解耦的服务可以轻松地跨多个服务器进行水平分布,从而可以适当地对功能 / 服务进行伸缩。单体应用程序中的所有逻辑是交织在一起的,即使只有一小部分应用程序是瓶颈,整个应用程序也需要扩展。小型服务的扩展是局部的,成本效益更高。

为此,当我们开始讨论微服务时,请记住下面一句话:小型的、简单的和解耦的服务=可伸缩的、有弹性的和灵活的应用程序

1.7 云到底是什么

术语“云”已经被过度使用了。每个软件供应商都有云,每个软件供应商的平台都是支持云的,但是如果穿透这些天花乱坠的广告宣传,我们就会发现云计算有3种基本模式。它们是:

  • 基础设施即服务(Infrastructure as a Service,IaaS);
  • 平台即服务(Platform as a Service,PaaS);
  • 软件即服务(Software as a Service,SaaS)。

为了更好地理解这些概念,让我们将每天的任务映射到不同的云计算模型中。当你想吃饭时,你有4种选择

1)在家做饭;

(2)去食品杂货店买一顿预先做好的膳食,然后你加热并享用它;

(3)叫外卖送到家里;

(4)开车去餐厅吃饭。

图1-6展示了各种模型。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第2张图片

图1-6 不同的云计算模型归结于云供应商或你各自要负责什么

这些选择之间的区别是谁负责烹饪这些膳食,以及在哪里烹饪。在内部自建模型中,想要在家里吃饭就需要自己做所有的工作,还要使用家里面的烤箱和食材。商店购买的食物就像使用基础设施即服务(IaaS)计算模型一样,使用店内的厨师和烤箱预先烘烤餐点,但你仍然有责任加热膳食并在家里吃(然后清洗餐具)。

在平台即服务(PaaS)模型中,你仍然需要负责烹饪膳食,但同时依靠供应商来负责与膳食制作相关的核心任务。例如,在PaaS模型中,你提供盘子和家具,但餐厅老板提供烤箱、食材和厨师来做饭。在“软件即服务”(SaaS)模型中,你去到一家餐厅,在那里,所有食物都已为你准备好。你在餐厅吃饭,然后在吃完后买单,你也不需要自己去准备或清洗餐具。

每个模型中的关键项都是控制:由谁来负责维护基础设施,以及构建应用程序的技术选择是什么?在IaaS模型中,云供应商提供基础设施,但你需要选择技术并构建最终的解决方案;而在SaaS模型中,你就是供应商所提供的服务的被动消费者,无法对技术进行选择,同时也没有任何责任来维护应用程序的基础设施。

新兴的云平台

本书已经介绍了目前正在使用的3种核心云平台类型(即IaaS、PaaS和SaaS)。然而,新的云平台类型正在出现。这些新平台包括“函数即服务”(Functions as a Service,FaaS)和“容器即服务”(Container as a Service,CaaS)。基于FaaS的应用程序会使用像亚马逊的Lambda技术和Google Cloud函数这样的设施,应用会将代码块以“无服务器”(serverless)的形式部署,这些代码会完全在云提供商的平台计算设施上运行。使用FaaS平台,无需管理任何服务器基础设施,只需支付执行函数所需的计算周期。

使用容器即服务模型,开发人员将微服务作为便携式虚拟容器(如Docker)进行构建并部署到云供应商。与IaaS模型不同,使用IaaS的开发人员必须管理部署服务的虚拟机,而CaaS则是将服务部署在轻量级的虚拟容器中。云供应商会提供运行容器的虚拟服务器,以及用于构建、部署、监控和伸缩容器的综合工具。亚马逊的弹性容器服务(Amazon’s Elastic Container Service,Amazon ECS)就是一个基于CaaS平台的例子。在第10章中,我们将看到如何部署已构建的微服务到Amazon ECS。

需要重点注意的是,使用云计算的FaaS和CaaS模型,开发人员仍然可以构建基于微服务的架构。请记住,微服务概念的重点在于构建有限职责的小型服务,并使用基于HTTP的接口进行通信。新兴的云计算平台(如FaaS和CaaS)是部署微服务的替代基础设施机制。

1.8 为什么是云和微服务

微服务架构的核心概念之一就是每个服务都被打包和部署为离散的独立制品。服务实例应该迅速启动,服务的每一个实例都是完全相同的。

作为编写微服务的开发人员,我们迟早要决定是否将服务部署到下列某个环境之中。

  • 物理服务器——虽然可以构建和部署微服务到物理机器,但由于物理服务器的局限性,很少有组织会这样做。开发人员不能快速提高物理服务器的容量,并且在多个物理服务器之间水平伸缩微服务可能会变得成本非常高。
  • 虚拟机镜像——微服务的主要优点之一是能够快速启动和关闭微服务实例,以响应可伸缩性和服务故障事件。虚拟机是主要云供应商的心脏和灵魂。微服务可以打包在虚拟机镜像中,然后开发人员可以在IaaS私有或公有云中快速部署和启动服务的多个实例。
  • 虚拟容器——虚拟容器是在虚拟机镜像上部署微服务的自然延伸。许多开发人员不是将服务部署到完整的虚拟机,而是将Docker容器(或等效的容器技术)部署到云端。虚拟容器在虚拟机内运行。使用虚拟容器,可以将单个虚拟机隔离成共享相同虚拟机镜像的一系列独立进程。

基于云的微服务的优势是以弹性的概念为中心。云服务供应商允许开发人员在几分钟内快速启动新的虚拟机和容器。如果服务容量需求下降,开发人员可以关闭虚拟服务器,而不会产生任何额外的费用。使用云供应商部署微服务可以显著地提高应用程序的水平可伸缩性(添加更多的服务器和服务实例)。服务器弹性也意味着应用程序可以更具弹性。如果其中一台微服务遇到问题并且处理能力正在不断地下降,那么启动新的服务实例可以让应用程序保持足够长的存活时间,让开发团队能够从容而优雅地解决问题。

本书会使用Docker容器将所有的微服务和相应的服务基础设施部署到基于IaaS的云供应商。下面列出的是用于微服务的常见部署拓扑结构。

  • 简化的基础设施管理——IaaS云计算供应商可以让开发人员最有效地控制他们的服务。开发人员可以通过简单的API调用来启动和停止新服务。使用IaaS云解决方案,只需支付使用基础设施的费用。
  • 大规模的水平可伸缩性——IaaS云服务供应商允许开发人员快速简便地启动服务的一个或多个实例。这种功能意味着可以快速扩大服务以及绕过表现不佳或出现故障的服务器。
  • 通过地理分布实现高冗余——IaaS供应商必然拥有多个数据中心。通过使用IaaS云供应商部署微服务,可以比使用数据中心里的集群拥有更高级别的冗余。

为什么不是基于PaaS的微服务

本章前面讨论了3种云平台(基础设施即服务、平台即服务和软件即服务)。对于本书,我选择专注于使用基于IaaS的方法构建微服务。虽然某些云供应商可以让开发人员抽象出微服务的部署基础设施,但我选择保持独立于供应商并部署应用程序的所有部分(包括服务器)。

例如,亚马逊、Cloud Foundry和Heroku可以让开发人员无需知道底层应用程序容器即可部署服务。它们提供了一个Web接口和API,以允许将应用程序部署为WAR或JAR文件。设置和调优应用程序服务器和相应的Java容器被抽象了出来。虽然这很方便,但每个云供应商的平台与其各自的PaaS解决方案有着不同的特点。

IaaS方案虽然需要更多的工作,但可跨多个云供应商进行移植,并允许开发人员通过产品覆盖更广泛的受众。就个人而言,我发现基于PaaS的云解决方案可以快速启动开发工作,但一旦应用程序拥有足够多的微服务,开发人员就会开始需要云服务商提供的IaaS风格的灵活性。

本章前面提到过新的云计算平台,如函数即服务(FaaS)和容器即服务(CaaS)。如果不小心,基于FaaS的平台就会将代码锁定到一个云供应商平台上,因为代码会被部署到供应商特定的运行时引擎上。使用基于FaaS的模型,开发人员可能会使用通用的编程语言(Java、Python、JavaScript等)编写服务,但开发人员仍然会将自己严格束缚在底层供应商的API和部署函数的运行时引擎上。

本书中构建的服务都会打包为Docker容器。本书选择Docker的原因之一是,作为容器技术,Docker可以部署到所有主要的云供应商之中。稍后在第10章中,本书将演示如何使用Docker打包微服务,然后将这些容器部署到亚马逊云平台。

1.9 微服务不只是编写代码

尽管构建单个微服务的概念很易于理解,但运行和支持健壮的微服务应用程序(尤其是在云中运行)不只是涉及为服务编写代码。编写健壮的服务需要考虑几个主题。图1-7强调了这些主题。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第3张图片

图1-7 微服务不只是业务逻辑,还需要考虑服务的运行环境以及服务的伸缩性和弹性

下面我们来更详细地了解一下图1-7中提及的要点。

  • 大小适当——如何确保正确地划分微服务的大小,以避免微服务承担太多的职责?请记住,适当的大小允许快速更改应用程序,并降低整个应用程序中断的总体风险。
  • 位置透明——在微服务应用程序中,多个服务实例可以快速启动和关闭时,如何管理服务调用的物理细节?
  • 有弹性——如何通过绕过失败的服务,确保采取“快速失败”的方法来保护微服务消费者和应用程序的整体完整性?
  • 可重复——如何确保提供的每个新服务实例与生产环境中的所有其他服务实例具有相同的配置和代码库?
  • 可伸缩——如何使用异步处理和事件来最小化服务之间的直接依赖关系,并确保可以优雅地扩展微服务?

本书采用基于模式的方法来回答这些问题。通过基于模式的方法,本书列出可以跨不同技术实现来使用的通用设计。虽然本书选择了使用Spring Boot和Spring Cloud来实现本书中所使用的模式,但开发人员完全可以把这些概念和其他技术平台一起使用。具体来说,本书涵盖以下6类微服务模式:

  • 核心微服务开发模式;
  • 微服务路由模式;
  • 微服务客户端弹性模式;
  • 微服务安全模式;
  • 微服务日志记录和跟踪模式;
  • 微服务构建和部署模式。

让我们深入了解一下这些模式。

1.9.1 核心微服务开发模式

核心微服务开发模式解决了构建微服务的基础问题,图1-8突出了我们将要讨论的基本服务设计的主题。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第4张图片

图1-8 在设计微服务时必须考虑服务是如何通信以及被消费的

  • 服务粒度——如何将业务域分解为微服务,使每个微服务都具有适当程度的职责?服务职责划分过于粗粒度,在不同的业务问题领域重叠,会使服务随着时间的推移变得难以维护。服务职责划分过于细粒度,则会使应用程序的整体复杂性增加,并将服务变为无逻辑的(除了访问数据存储所需的逻辑)“哑”数据抽象层。第2章将会介绍服务粒度。
  • 通信协议——开发人员如何与服务进行通信?使用XML(Extensible Markup Language,可扩展标记语言)、JSON(JavaScript对象表示法)或诸如Thrift之类的二进制协议来与微服务传输数据?本书将介绍为什么JSON是微服务的理想选择,并且JSON已成为向微服务发送和接收数据的最常见选择。第2章将会介绍通信协议。
  • 接口设计——如何设计实际的服务接口,便于开发人员进行服务调用?如何构建服务URL来传达服务意图?如何版本化服务?精心设计的微服务接口使服务变得更直观。第2章将会介绍接口设计。
  • 服务的配置管理——如何管理微服务的配置,以便在不同云环境之间移动时,不必更改核心应用程序代码或配置?第3章将会介绍管理服务配置。
  • 服务之间的事件处理——如何使用事件解耦微服务,以便最小化服务之间的硬编码依赖关系,并提高应用程序的弹性?第8章将会介绍服务之间的事件处理。

1.9.2 微服务路由模式

微服务路由模式负责处理希望消费微服务的客户端应用程序,使客户端应用程序发现服务的位置并路由到服务。在基于云的应用程序中,可能会运行成百上千个微服务实例。需要抽象这些服务的物理IP地址,并为服务调用提供单个入口点,以便为所有服务调用持续强制执行安全和内容策略。

服务发现和路由回答了这个问题:如何将客户的服务请求发送到服务的特定实例?

  • 服务发现——如何使微服务变得可以被发现,以便客户端应用程序在不需要将服务的位置硬编码到应用程序的情况下找到它们?如何确保从可用的服务实例池中删除表现不佳的微服务实例?第4章将会介绍服务发现。
  • 服务路由——如何为所有服务提供单个入口点,以便将安全策略和路由规则统一应用于微服务应用程序中的多个服务和服务实例?如何确保团队中的每位开发人员不必为他们的服务提供自己的服务路由解决方案?第6章将会介绍服务路由。在图1-9中,服务发现和服务路由之间似乎具有硬编码的事件顺序(首先是服务路由,然后是服务发现)。然而,这两种模式并不彼此依赖。例如,我们可以实现没有服务路由的服务发现,也可以实现服务路由而无需服务发现(尽管这种实现更加困难)。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第5张图片

图1-9 服务发现和路由是所有大规模微服务应用的关键部分

1.9.3 微服务客户端弹性模式

因为微服务架构是高度分布式的,所以必须对如何防止单个服务(或服务实例)中的问题级联暴露给服务的消费者十分敏感。为此,这里将介绍4种客户端弹性模式。

  • 客户端负载均衡——如何在服务客户端上缓存服务实例的位置,以便对微服务的多个实例的调用负载均衡到该微服务的所有健康实例?
  • 断路器模式——如何阻止客户继续调用出现故障的或遭遇性能问题的服务?如果服务运行缓慢,客户端调用时会消耗它的资源。开发人员希望出现故障的微服务调用能够快速失败,以便主叫客户端可以快速响应并采取适当的措施。
  • 后备模式——当服务调用失败时,如何提供“插件”机制,允许服务的客户端尝试通过调用微服务之外的其他方法来执行工作?
  • 舱壁模式——微服务应用程序使用多个分布式资源来执行工作。如何区分这些调用,以便表现不佳的服务调用不会对应用程序的其他部分产生负面影响?

图1-10展示了这些模式如何在服务表现不佳时,保护服务消费者不受影响。第5章将会介绍这些主题。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第6张图片

图1-10 使用微服务时,必须保护服务调用者远离表现不佳的服务。记住,
慢速或无响应的服务所造成的中断并不仅仅局限于直接关联的服务

1.9.4 微服务安全模式

写一本微服务的书绕不开微服务安全性。在第7章中我们将介绍3种基本的安全模式。这3种模式具体如下。

  • 验证——如何确定调用服务的客户端就是它们声称的那个主体?
  • 授权——如何确定调用微服务的客户端是否允许执行它们正在进行的操作?
  • 凭据管理和传播——如何避免客户端每次都要提供凭据信息才能访问事务中涉及的服务调用?具体来说,本书将介绍如何使用基于令牌的安全标准来获取可以从一个服务调用传递到另一个服务调用的令牌,以验证和授权用户,这里涉及的标准包括OAuth2和JSON Web Token(JWT)。

图1-11展示了如何实现上述3种模式来构建可以保护微服务的验证服务。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第7张图片

图1-11 使用基于令牌的安全方案,可以实现服务验证和授权,而无需传递客户端凭据

本书现在不会太深入图1-11中的细节。需要一整章来介绍安全是有原因的(实际上它本身就可以是一本书)。

1.9.5 微服务日志记录和跟踪模式

微服务架构的优点是单体应用程序被分解成可以彼此独立部署的小的功能部件,而它的缺点是调试和跟踪应用程序和服务中发生的事情要困难得多。

因此,本书将介绍以下3种核心日志记录和跟踪模式。

  • 日志关联——一个用户事务会调用多个服务,如何将这些服务所生成的日志关联到一起?借助这种模式,本书将会介绍如何实现一个关联(correlation)ID,这是一个唯一的标识符,在事务中调用所有服务都会携带它,通过它能够将每个服务生成的日志条目联系起来。
  • 日志聚合——借助这种模式,我们将会介绍如何将微服务(及其各个实例)生成的所有日志合并到一个可查询的数据库中。此外,本书还会研究如何使用关联ID来协助搜索聚合日志。
  • 微服务跟踪——最后,我们将探讨如何在涉及的所有服务中可视化客户端事务的流程,并了解事务所涉及的服务的性能特征。

图1-12展示了这些模式如何配合在一起。第9章中将会更加详细地介绍日志记录和跟踪模式。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第8张图片

1.9.6 微服务构建和部署模式

微服务架构的核心原则之一是,微服务的每个实例都应该和其他所有实例相同。“配置漂移”(某些文件在部署到服务器之后发生了一些变化)是不允许出现的,因为这可能会导致应用程序不稳定。

一句经常说的话

“我在交付准备服务器上只做了一个小小的改动,但是我忘了在生产服务器中也做这样的改动。”多年来,我在紧急情况团队中工作时,许多宕机系统的解决方案通常是从开发人员或系统管理员的这些话开始的。工程师(和大多数人一般)是以良好的意图在操作。工程师并不是故意犯错误或使系统崩溃,相反,他们尽可能做到最好,但他们会变得忙碌或者分心。他们调整了一些服务器上的东西,打算回去在所有环境中做相同的调整。

在以后某个时间点里,出现了中断状况,每个人都在搔头挠耳,想要知道其他环境与生产环境之间有什么不同。我发现,微服务的小规模与有限范围的特点创造了一个绝佳机会——将“不可变基础设施”概念引入组织:一旦部署服务,其运行的基础设施就再也不会被人触碰。

不可变基础设施是成功使用微服务架构的关键因素,因为在生产中必须要保证开发人员为特定微服务启动的每个微服务实例与其他微服务实例相同。

为此,本书的目标是将基础设施的配置集成到构建部署过程中,这样就不再需要将软件制品(如Java WAR或EAR)部署到已经在运行的基础设施中。相反,开发人员希望在构建过程中构建和编译微服务并准备运行微服务的虚拟服务器镜像。部署微服务时,服务器运行所需的整个机器镜像都会进行部署。

图1-13阐述了这个过程。本书最后将介绍如何更改构建和部署管道,以便将微服务及运行的服务器部署为单个工作单元。第10章将介绍以下模式和主题。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第9张图片

图1-13 开发人员希望微服务及其运行所需的服务器成为在不同环境间作为整体部署的原子制件

  • 构建和部署管道——如何创建一个可重复的构建和部署过程,只需一键即可构建和部署到组织中的任何环境?
  • 基础设施即代码——如何将服务的基础设施作为可在源代码管理下执行和管理的代码去对待?
  • 不可变服务器——一旦创建了微服务镜像,如何确保它在部署之后永远不会更改?
  • 凤凰服务器(Phoenix server)——服务器运行的时间越长,就越容易发生配置漂移。如何确保运行微服务的服务器定期被拆卸,并重新创建一个不可变的镜像?

使用这些模式和主题的目的是,在配置漂移影响到上层环境(如交付准备环境或生产环境)之前,尽可能快地公开并消除配置漂移。

注意

本书中的代码示例(除了第10章)都将在本地机器上运行。前两章的代码可以直接从命令行运行,从第3章开始,所有代码将被编译并作为Docker容器运行。

1.10 使用Spring Cloud构建微服务

本节将简要介绍在构建微服务时会使用的Spring Cloud技术。这是一个高层次的概述。在书中使用各项技术时,我们会根据需要为读者讲解这些技术的细节。

从零开始实现所有这些模式将是一项巨大的工作。幸好,Spring团队将大量经过实战检验的开源项目整合到一个称为Spring Cloud的Spring子项目中。

Spring Cloud将Pivotal、HashiCorp和Netflix等开源公司的工作封装在一起。Spring Cloud简化了将这些项目设置和配置到Spring应用程序中的工作,以便开发人员可以专注于编写代码,而不会陷入配置构建和部署微服务应用程序的所有基础设施的细节中。

图1-14将1.9节中列出的模式映射到实现它们的Spring Cloud项目。

Spring微服务实战第1章 欢迎迈入云世界,Spring_第10张图片

图1-14 可以将这些直接可用的技术与本章探讨的微服务模式对应起来

下面让我们更详细地了解一下这些技术。

1.10.1 Spring Boot

Spring Boot是微服务实现中使用的核心技术。Spring Boot通过简化构建基于REST的微服务的核心任务,大大简化了微服务开发。Spring Boot还极大地简化了将HTTP类型的动词(GET、PUT、POST和DELETE)映射到URL、JSON协议序列化与Java对象的相互转化,以及将Java异常映射回标准HTTP错误代码的工作。

1.10.2 Spring Cloud Config

Spring Cloud Config通过集中式服务来处理应用程序配置数据的管理,因此应用程序配置数据(特别是环境特定的配置数据)与部署的微服务完全分离。这确保了无论启动多少个微服务实例,这些微服务实例始终具有相同的配置。Spring Cloud Config拥有自己的属性管理存储库,也可以与以下开源项目集成。

  • Git——Git是一个开源版本控制系统,它允许开发人员管理和跟踪任何类型的文本文件的更改。Spring Cloud Config可以与Git支持的存储库集成,并读出存储库中的应用程序的配置数据。
  • Consul——Consul是一种开源的服务发现工具,允许服务实例向该服务注册自己。服务客户端可以向Consul咨询服务实例的位置。Consul还包括可以被Spring Cloud Config使用的基于键值存储的数据库,能够用来存储应用程序的配置数据。
  • Eureka——Eureka是一个开源的Netflix项目,像Consul一样,提供类似的服务发现功能。Eureka同样有一个可以被Spring Cloud Config使用的键值数据库。

1.10.3 Spring Cloud服务发现

通过Spring Cloud服务发现,开发人员可以从客户端消费的服务中抽象出部署服务器的物理位置(IP或服务器名称)。服务消费者通过逻辑名称而不是物理位置来调用服务器的业务逻辑。Spring Cloud服务发现也处理服务实例的注册和注销(在服务实例启动和关闭时)。Spring Cloud服务发现可以使用Consul和Eureka作为服务发现引擎。

1.10.4 Spring Cloud与Netflix Hystrix和Netflix Ribbon

Spring Cloud与Netflix的开源项目进行了大量整合。对于微服务客户端弹性模式,Spring Cloud封装了Netflix Hystrix库和Netflix Ribbon项目,开发人员可以轻松地在微服务中使用它们。

使用Netflix Hystrix库,开发人员可以快速实现服务客户端弹性模式,如断路器模式和舱壁模式。

虽然Netflix Ribbon项目简化了与诸如Eureka这样的服务发现代理的集成,但它也为服务消费者提供了客户端对服务调用的负载均衡。即使在服务发现代理暂时不可用时,客户端也可以继续进行服务调用。

1.10.5 Spring Cloud与Netflix Zuul

Spring Cloud使用Netflix Zuul项目为微服务应用程序提供服务路由功能。Zuul是代理服务请求的服务网关,确保在调用目标服务之前,对微服务的所有调用都经过一个“前门”。通过集中的服务调用,开发人员可以强制执行标准服务策略,如安全授权验证、内容过滤和路由规则。

1.10.6 Spring Cloud Stream

Spring Cloud Stream(https://cloud.spring.io/spring-cloud-stream/)是一种可让开发人员轻松地将轻量级消息处理集成到微服务中的支持技术。借助Spring Cloud Stream,开发人员能够构建智能的微服务,它可以使用在应用程序中出现的异步事件。此外,使用Spring Cloud Stream可以快速将微服务与消息代理进行整合,如RabbitMQ和Kafka。

1.10.7 Spring Cloud Sleuth

Spring Cloud Sleuth允许将唯一跟踪标识符集成到应用程序所使用的HTTP调用和消息通道(RabbitMQ、Apache Kafka)之中。这些跟踪号码(有时称为关联ID或跟踪ID)能够让开发人员在事务流经应用程序中的不同服务时跟踪事务。有了Spring Cloud Sleuth,这些跟踪ID将自动添加到微服务生成的任何日志记录中。

Spring Cloud Sleuth与日志聚合技术工具(如Papertrail)和跟踪工具(如Zipkin)结合时,能够展现出真正的威力。Papertail是一个基于云的日志记录平台,用于将日志从不同的微服务实时聚合到一个可查询的数据库中。Zipkin可以获取Spring Cloud Sleuth生成的数据,并允许开发人员可视化单个事务涉及的服务调用流程。

1.10.8 Spring Cloud Security

Spring Cloud Security是一个验证和授权框架,可以控制哪些人可以访问服务,以及他们可以用服务做什么。Spring Cloud Security是基于令牌的,允许服务通过验证服务器发出的令牌彼此进行通信。接收调用的每个服务可以检查HTTP调用中提供的令牌,以确认用户的身份以及用户对该服务的访问权限。

此外,Spring Cloud Security支持JSON Web Token。JSON Web Token(JWT)框架标准化了创建OAuth2令牌的格式,并为创建的令牌进行数字签名提供了标准。

1.10.9 代码供应

要实现代码供应,我们将会转移到其他的技术栈。Spring框架是面向应用程序开发的,它(包括Spring Cloud)没有用于创建“构建和部署”管道的工具。要实现一个“构建和部署”管道,开发人员需要使用Travis CI和Docker这两样工具,前者可以作为构建工具,而后者可以构建包含微服务的服务器镜像。

为了部署构建好的Docker容器,本书的最后将通过一个例子,阐述如何将整个应用程序栈部署到亚马逊云上。

1.11 通过示例来介绍Spring Cloud

在本章最后这一节中,我们概要回顾一下要使用的各种Spring Cloud技术。因为每一种技术都是独立的服务,要详细介绍这些服务,整整一章的内容都不够。在总结这一章时,我想留给读者一个小小的代码示例,它再次演示了将这些技术集成到微服务开发工作中是多么容易。

与代码清单1-1 中的第一个代码示例不同,这个代码示例不能运行,因为它需要设置和配置许多支持服务才能使用。不过,不要担心,在设置服务方面,这些Spring Cloud服务(配置服务,服务发现)的设置是一次性的。一旦设置完成,微服务就可以不断使用这些功能。在本书的开头,我们无法将所有的精华都融入一个代码示例中。

代码清单1-2中的代码快速演示了如何将远程服务的服务发现、断路器、舱壁以及客户端负载均衡集成到“Hello World”示例中。

代码清单1-2 Hello World Service使用Spring Cloud

package com.thoughtmechanix.simpleservice;

// 为了简洁,省略了其他import语句
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; 
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;

@SpringBootApplication
@RestController
@RequestMapping(value="hello")
@EnableCircuitBreaker  ⇽--- 使服务能够使用HystrixRibbon@EnableEurekaClient
public class Application {  ⇽--- 告诉服务,它应该使用Eureka服务发现代理注册自身,并且服务调用是使用服务发现来“查找”远程服务的位置的

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @HystrixCommand(threadPoolKey = "helloThreadPool")  ⇽--- 包装器使用Hystrix断路器调用helloRemoteServiceCall方法
    public String helloRemoteServiceCall(String firstName, String lastName){
    ResponseEntity<String> restExchange = 
    ➥  restTemplate.exchange(
        ➥  "http://logical-service-id/name/  ⇽--- 使用一个装饰好的RestTemplate类来获取一个“逻辑”服务ID,Eureka在幕后查找服务的物理位置
            ➥   [ca]{firstName}/{lastName}",HttpMethod.GET,null, String.class, firstName, lastName);

    return restExchange.getBody();

    }
    @RequestMapping(value="/{firstName}/{lastName}", method = RequestMethod.GET)
    public String hello(@PathVariable("firstName") String firstName,@PathVariable("lastName") String lastName) {
        return helloRemoteServiceCall(firstName, lastName);
    }
}

这段代码包含了很多内容,让我们慢慢分析。记住,这个代码清单只是一个例子,在第1章的GitHub仓库源代码中是找不到的。把它放在这里,是为了让读者了解本书后面的内容。

开发人员首先应该要注意的是@EnableCircuitBreaker@EnableEurekaClient注解。@EnableCircuitBreaker注解告诉Spring微服务,将要在应用程序使用Netflix Hystrix库。@EnableEurekaClient注解告诉微服务使用Eureka服务发现代理去注册它自己,并且将要在代码中使用服务发现去查询远程REST服务端点。注意,配置是在一个属性文件中的,该属性文件告诉服务要进行通信的Eureka服务器的地址和端口号。读者第一次看到使用Hystrix是在声明hello方法时:

@HystrixCommand(threadPoolKey = "helloThreadPool")
public String helloRemoteServiceCall(String firstName,String lastName)

@HystrixCommand注解做两件事。第一件事是,在任何时候调用helloRemoteService Call方法,该方法都不会被直接调用,这个调用会被委派给由Hystrix管理的线程池。如果调用时间太长(默认为1 s),Hystrix将介入并中断调用。这是断路器模式的实现。第二件事是创建一个由Hystrix管理的名为helloThreadPool的线程池。所有对helloRemoteServiceCall方法的调用只会发生在此线程池中,并且将与正在进行的任何其他远程服务调用隔离。

最后要注意的是helloRemoteServiceCall方法中发生的事情。@EnableEurekaClient的存在告诉Spring Boot,在使用REST服务调用时,使用修改过的RestTemplate类(这不是标准的Spring RestTemplate的工作方式)。这个RestTemplate类允许用户传入自己想要调用的服务的逻辑服务ID:

ResponseEntity restExchange = restTemplate.exchange
➥  (http://logical-service-id/name/{firstName}/{lastName}

在幕后,RestTemplate类将与Eureka服务进行通信,并查找一个或多个“name”服务实例的实际位置。作为服务的消费者,开发人员的代码永远不需要知道服务的位置。

另外,RestTemplate类使用Netflix的Ribbon库。Ribbon将会检索与服务有关的所有物理端点的列表。每当客户端调用该服务时,它不必经过集中式负载均衡器就可以对客户端上不同服务实例进行轮询(round-robin)。通过消除集中式负载平衡器并将其移动到客户端,可以消除应用程序基础设施中的其他故障点(故障的负载平衡器)。

我希望此刻读者会印象深刻,因为只需要几个注解就可以为微服务添加大量的功能。这就是Spring Cloud背后真正的美。作为开发人员,我们可以利用Netflix和Consul等知名的云计算公司的微服务功能,这些功能是久经考验的。如果在Spring Cloud之外使用这些功能,可能会很复杂并且难以设置。Spring Cloud简化了它们的使用,仅仅是使用一些简单的Spring Cloud注解和配置条目。

1.12 确保本书的示例是有意义的

我想要确保本书提供的示例都是与开发人员的工作息息相关的。为此,我将围绕一家名为ThoughtMechanix的虚构公司的冒险(不幸事件)来组织本书的章节以及对应的代码示例。

ThoughtMechanix是一家软件开发公司,其核心产品EagleEye提供企业级软件资产管理应用程序。该产品覆盖了所有关键要素:库存、软件交付、许可证管理、合规、成本以及资源管理。其主要目标是使组织获得准确时间点的软件资产的描述。

该公司成立了大概10年,尽管营收增长强劲,但在内部,他们正在讨论是否应该革新其核心产品,将它从一个单体内部部署的应用程序转移到云端。对该公司来说,与EagleEye相关的平台革新是“生死”时刻。

该公司正在考虑在新架构上重构其核心产品EagleEye。虽然应用程序的大部分业务逻辑将保持原样,但应用程序本身将从单体设计中分解为更小的微服务设计,其部件可以独立部署到云端。本书中的示例不会构建整个ThoughtMechanix应用程序。相反,读者将从问题领域构建特定的微服务,然后使用各种Spring Cloud(和一些非Spring Cloud)技术来构建支持这些服务的基础设施。

成功采用基于云的微服务架构的能力将影响技术组织的所有成员。这包括架构团队、工程团队、测试团队和运维团队。每个团队都需要投入,最终,当团队重新评估他们在这个新环境中的职责时,他们可能需要重组。让我们开始与ThoughtMechanix的旅程,读者将开始一些基础工作——识别和构建EagleEye中使用的几个微服务,然后使用Spring Boot构建这些服务。

1.13 小结

  • 微服务是非常小的功能部件,负责一个特定的范围领域。
  • 微服务并没有行业标准。与其他早期的Web服务协议不同,微服务采用原则导向的方法,并与REST和JSON的概念相一致。
  • 编写微型服务很容易,但是完全可以将其用于生产则需要额外的深谋远虑。本书介绍了几类微服务开发模式,包括核心开发模式、路由模式、客户端弹性模式、安全模式、日志记录和跟踪模式以及构建和部署模式。
  • 虽然微服务与语言无关,但本书引入了两个Spring框架,即Spring Boot和Spring Cloud,它们非常有助于构建微服务。
  • Spring Boot用于简化基于REST的JSON微服务的构建,其目标是让用户只需要少量注解,就能够快速构建微服务。
  • Spring Cloud是Netflix和HashiCorp等公司开源技术的集合,它们已经用Spring注解进行了“包装”,从而显著简化了这些服务的设置和配置。

[1] 虽然本书在稍后的第2章中会介绍REST,但是Roy Fielding阐述如何基于REST构建应用的博士论文仍然值得一读(http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)。在对REST概念的阐述上,它依然是最棒的材料之一。

端弹性模式、安全模式、日志记录和跟踪模式以及构建和部署模式。

  • 虽然微服务与语言无关,但本书引入了两个Spring框架,即Spring Boot和Spring Cloud,它们非常有助于构建微服务。
  • Spring Boot用于简化基于REST的JSON微服务的构建,其目标是让用户只需要少量注解,就能够快速构建微服务。
  • Spring Cloud是Netflix和HashiCorp等公司开源技术的集合,它们已经用Spring注解进行了“包装”,从而显著简化了这些服务的设置和配置。

[1] 虽然本书在稍后的第2章中会介绍REST,但是Roy Fielding阐述如何基于REST构建应用的博士论文仍然值得一读(http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)。在对REST概念的阐述上,它依然是最棒的材料之一。


你可能感兴趣的:(微服务,spring,java,spring,boot,微服务)