vlambda博客
学习文章列表

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Chapter 12. Scale Microservices with Spring Cloud Components

为了管理互联网规模的微服务,需要比 Spring Boot 框架提供的更多的功能。 Spring Cloud 项目有一套专门构建的组件,可轻松实现这些附加功能。

本章将通过针对微服务能力模型定位它们,深入了解 Spring Cloud 项目的各种组件,例如 Eureka、Zuul、Ribbon 和 Spring Config。本章将演示 Spring Cloud 组件如何帮助扩展 BrownField Airline 的 PSS 微服务系统。

在本章结束时,我们将了解以下内容:

  • The Spring Config Server for externalizing configuration
  • The Eureka Server for service registration and discovery
  • Understanding the relevance of Zuul as a service proxy and gateway
  • The implementation of automatic microservice registration and service discovery
  • Spring Cloud messaging for asynchronous reactive microservice composition

What is Spring Cloud?


添加指向 Netflix OSS 的链接。 Spring Cloud 项目是 Spring 团队的一个伞形项目,它实现了 distributed 系统所需的一组通用模式作为一组易于使用的 Java Spring 库。尽管有它的名字,Spring Cloud 本身并不是一个云解决方案。相反,它提供了许多功能,这些功能在开发面向遵循十二要素应用程序原则的云部署的应用程序时必不可少。通过使用 Spring Cloud,开发人员只需专注于使用 Spring Boot 构建业务能力,并利用 Spring Cloud 开箱即用的分布式、容错和自愈能力。

Spring Cloud 解决方案与部署环境无关,可以在台式 PC 或弹性云中开发和部署。使用 Spring Cloud 开发的云就绪解决方案也是不可知的,并且可以在许多云提供商之间移植,例如 Cloud Foundry、AWS、Heroku 等。当不使用 Spring Cloud 时,开发人员最终将使用云供应商原生提供的服务,从而导致与 PaaS 提供商的深度耦合。开发人员的另一种选择是编写大量样板代码来构建这些服务。 Spring Cloud 还提供了简单易用的 Spring 友好 API,这些 API 抽象了云提供商的服务 API,例如 AWS 通知服务附带的那些 API。

Spring Cloud 基于 Spring 的 convention over configuration 方法构建,默认所有配置,并帮助开发人员快速入门。 Spring Cloud 还隐藏了复杂性,并提供了简单的声明式配置来构建系统。 Spring Cloud 组件的占用空间更小,使其对开发人员友好,也使开发云原生应用程序变得容易。

Spring Cloud 根据开发者的需求为开发者提供了多种解决方案选择。例如,可以使用 Eureka、Zookeeper 或 Consul 等流行选项来实现服务注册表。 Spring Cloud 的组件是相当解耦的,因此,开发人员可以灵活地选择所需的内容。

Note

Spring Cloud 和 Cloud Foundry 有什么区别? Spring Cloud 是用于开发 Internet 规模 Spring Boot 应用程序的开发工具包,而 Cloud Foundry 是用于构建、部署和扩展应用程序的开源平台即服务。

Spring Cloud releases


Spring Cloud 项目是一个overarching Spring 项目,其中包括不同组件的组合。这些组件的版本在spring-cloud-starter-中定义 BOM。

Note

在本书中,我们依赖于 Spring Cloud 的 Dalston SR1 版本。 Dalston 不支持 Spring Boot 2.0.0 和 Spring Framework 5。 Spring Cloud Finchley 计划今年年底,预计支持 Spring Boot 2.0.0。因此,前面章节中的示例需要降级到 Spring Boot 1.5.2.RELEASE 版本。

pom.xml 中添加以下依赖项以使用 Spring Cloud Dalston 依赖项:

    <dependency>
      <groupId>org.springframework.cloud</groupId> 
      <artifactId>spring-cloud-dependencies</artifactId <version>Dalston.SR1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

Setting up the environment for the BrownField PSS


我们将研究如何使用 Spring Cloud 组件使这些服务成为企业级。

为了准备本章的环境,将(chapter6.* to chapter7.*)项目导入并重命名为一个新的STS 工作区。

Spring Cloud Config


简化本节。 Spring Cloud Config Server 是一个外部化的配置服务器,应用程序和服务可以在其中存放、访问和管理所有 runtime 配置属性. Spring Config Server 还支持配置属性的版本控制。

在早期的 Spring Boot 示例中,所有配置参数都是从项目中打包的属性文件中读取的,application.propertiesapplication.yaml< /代码>。这种方法很好,因为所有属性都从代码中移出到属性文件中。但是,当微服务从一个环境迁移到另一个环境时,这些属性需要进行更改,这需要重新构建应用程序。这违反了十二要素应用程序原则之一,该原则提倡一次性构建和跨环境移动二进制文件。

更好的方法是使用配置文件的概念。配置文件,如第 11 章中所述,使用 Spring Boot 构建微服务 ,用于为不同的环境划分不同的属性。配置文件特定配置将命名为 application-{profile}.properties. 例如, application-development.properties 表示针对开发环境的属性文件。

但是,这种方法的缺点是配置与应用程序一起静态打包。配置属性中的任何更改都需要重新构建应用程序。

有其他方法可以将应用程序部署包中的配置属性外部化。还可以通过多种方式从外部源读取可配置属性,如下所示:

  • From an external JNDI server using the JNDI namespace (java:comp/env)
  • Using the Java system properties (System.getProperties()), or by using the –D command-line option
  • Using the PropertySource configuration
        @PropertySource("file:${CONF_DIR}/application.properties")
        public class ApplicationConfig {
        }
  • Using a command-line parameter pointing a file to an external location
java -jar myproject.jar --spring.config.location=<file location>

JNDI 操作代价高昂、缺乏灵活性、难以复制且不受版本控制。 System.properties 对于大规模部署不够灵活。最后两个选项依赖于安装在服务器上的本地或共享文件系统。

对于大规模部署,需要一个简单但功能强大的集中式配置管理解决方案。

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

如上图所示,所有微服务都指向一个中央服务器来获取所需的配置参数。然后微服务在本地缓存这些参数以提高性能。 Config Server 将配置状态更改传播到所有订阅的微服务,以便本地缓存的状态可以使用最新更改进行更新。配置服务器还使用配置文件来解析特定于环境的值。

如下图所示,Spring Cloud 项目下有多个选项可用于构建配置服务器。 Config Server、Zookeeper Configuration 和 Consul Configuration 可作为选项使用。但是,本章将只关注 Spring Config Server 的实现:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Spring Config Server 将属性存储在版本控制的存储库中,例如 Git 或 SVN。 Git 存储库可以是本地的或远程的。大规模分布式微服务部署首选高可用性远程 Git 服务器。

Spring Cloud Config Server 架构如下图所示:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

如图所示,嵌入在 Spring Boot 微服务中的 Config Client 使用简单的 declarative< 从中央配置服务器进行配置查找/span> 机制并将属性存储到 Spring 环境中。配置属性可以是应用程序级别的配置,例如每日交易限额,也可以是与基础设施相关的配置,例如服务器 URL、凭证等。

与 Spring Boot 不同,Spring Cloud 使用引导上下文,它是主应用程序的父上下文。引导上下文负责从 配置服务器加载配置 properties >。引导上下文查找 bootstrap.yamlbootstrap.properties 以加载初始配置属性。要使其在 Spring Boot 应用程序中工作,请将 application.* 文件重命名为 bootstrap.*

Building microservices with Config Server

接下来的几节将演示如何 在真实场景中使用配置服务器。为此,我们将修改我们的搜索微服务(chapter7.search)以使用配置服务器。以下 diagram 描述了该场景:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

在此示例中,搜索服务将通过传递服务名称在启动时读取 Config Server。在这种情况下,搜索服务的服务名称将是 search-service。为 search-service 配置的属性包括 RabbitMQ 属性和自定义属性。

Setting up the Config Server

以下步骤需要遵循 使用 STS 创建一个新的配置服务器:

  1. Create a new Spring Starter project and select Config Server and Actuator, as shown in this screenshot:
读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务
  1. Set up a Git repository. This could be done by pointing to a remote Git configuration repository, such as https://github.com/spring-cloud-samples/config-repo. This URL is an indicative one, a Git repository used by the Spring Cloud examples. We will have to use our own Git repository instead.
  2. Alternatively, a local-file-system-based Git repository can be used. In a real production scenario, an external Git is recommended. The Config Server in this chapter will use a local-file-system-based Git repository for demonstration purposes.
  3. Use the following set of commands to set up a local Git repository:
        $ cd $HOME
        $ mkdir config-repo
        $ cd config-repo
        $ git init .
        $ echo message : helloworld > application.properties
        $ git add -A .
        $ git commit -m "Added sample application.properties"

这将在本地文件系统上创建一个新的 Git 存储库。还创建了一个名为 application.properties 的属性文件,其中包含属性消息和值 helloworld

Note

application.properties 是为了演示目的而创建的。我们将在后续部分中对此进行更改。

  1. The next step is to change the configuration in the Config Server to use the Git repository created in the previous step. In order to do this, rename application.properties as bootstrap.properties:
读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务
  1. Edit the contents of the new bootstrap.properties to match the following:
        server.port=8888
        spring.cloud.config.server.git.uri:
          file://${user.home}/config-repo

Note

端口 8888 是配置服务器的默认端口。即使没有配置 server.port,配置服务器也应该绑定到 8888。在 Windows 环境中,文件 URL 中需要一个额外的 /

  1. Also add management.security.enabled=false to disable security validation.
  2. Optionally, rename the default package of the autogenerated Application.java from com.example to com.brownfield.configserver. Add @EnableConfigServer in Application.java.
        @EnableConfigServer
        @SpringBootApplication
        publicclass ConfigserverApplication {
  1. Run the Config Server by right-clicking the project, and run as Spring Boot App.
  2. Check curl http://localhost:8888/env to see whether the server is running. If everything is fine, this will list all environment configurations. Note that /env is an Actuator endpoint.
  3. Check http://localhost:8888/application/default/master to see the properties specific to application.properties, which was added in the earlier step. The browser will display properties configured in the application.properties. The browser should display content similar to this:
        {"name":"application","profiles":["default"],"label":"master",
          "version":"6046fd2ff4fa09d3843767660d963866ffcc7d28",
          "propertySources":[{"name":"file:///Users/rvlabs
          /config-repo /application.properties","source":
          {"message":"helloworld"}}]}

Understanding the Config Server URL

在上一节中,我们使用 http://localhost:8888/application/default/master 来探索属性。我们如何解释给定的 URL?

 

 

 

 

 

 

URL 中的第一个元素是应用程序名称。在最后一个示例中,应用程序名称应为 application。应用程序名称是使用 spring.application 提供给应用程序的逻辑 name。 Spring Boot 应用程序的 bootstrap.properties 中的 name 属性。每个应用程序必须有一个唯一的名称。 Config Server 将使用该名称来解析并从 Config Server 存储库中获取适当的属性。应用程序名称有时也称为服务 ID。如果有一个名为 myapp 的应用程序,那么配置库中应该有一个 myapp.properties 来存储所有相关的属性到那个应用程序。

URL 的第二部分代表配置文件。一个应用程序的存储库中可以配置多个配置文件。配置文件可用于各种场景。两种常见的场景是隔离不同的环境,例如 Dev、Test、Stage、Prod 等,或者隔离服务器配置,例如主要、次要等。第一个代表应用程序的不同环境,而第二个代表部署应用程序的不同服务器。

配置文件名称是逻辑名称,将用于匹配存储库中的文件名。默认配置文件命名为 default。要为不同的环境配置属性,我们必须配置不同的文件,如下所示。在此示例中,第一个文件用于开发环境,而第二个文件用于生产环境:

    application-development.properties
    application-production.properties

可以使用以下 URL 访问它们:

http://localhost:8888/application/development

http://localhost:8888/application/production

URL 的最后一部分是 label,默认命名为 masterlabel 是一个可选的 Git 标签,如果需要可以使用。

简而言之,URL 就是基于这种模式:

http://localhost:8888/{name}/{profile}/{label}

也可以通过忽略配置文件来访问配置。在给定的示例中,以下所有三个 URL 都指向相同的配置:

http://localhost:8888/application/default

http://localhost:8888/application/master

http://localhost:8888/application/default/master

可以选择为不同的配置文件使用不同的 Git 存储库。这对于生产系统是有意义的,因为对不同存储库的访问可能不同。

Accessing the Config Server from clients

在上一节中,使用 Web 浏览器设置并访问 配置服务器。在本节中,将修改 Search 微服务以使用 Config Server。搜索微服务将充当配置客户端。

Follow the steps listed next to use the Config Server instead of reading properties from the application.properties file:

  1. Add the Spring Cloud Config dependency and the Actuator (if Actuator is not already in place) to pom.xml. The Actuator is mandatory for refreshing the configuration properties:
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
  1. Add the following to include the Spring Cloud dependencies. This is not required if the project is created from scratch:
        <dependencyManagement>
          <dependencies>
          <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>  
        <version>Dalston.BUILD-SNAPSHOT</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>

 

 

 

 

  1. The following screenshot shows the Cloud starter library selection screen. If the application is built from the ground up, select the libraries as shown here:
读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务
  1. Rename the application.properties to bootstrap.properties, and add an application name and a configuration server URL. The configuration server URL is not mandatory if the Config Server is running on the default port (8888) on the local host.

新的 bootstrap.properties 文件将如下所示:

        spring.application.name=search-service
        spring.cloud.config.uri=http://localhost:8888
        server.port=8090
        spring.rabbitmq.host=localhost
        spring.rabbitmq.port=5672
        spring.rabbitmq.username=guest
        spring.rabbitmq.password=guest

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

search-service 是搜索微服务的逻辑名称。这将被视为服务 ID。配置服务器将在存储库中查找 search-service.properties 以解析属性。

  1. Create a new configuration file for search-service. Create a new search-service.properties under the config-repo folder where the Git repository is created. Note that search-service is the service ID given to the Search microservice in the bootstrap.properties file. Move service-specific properties from bootstrap.properties to the new search-service.properties. The following properties will be removed from bootstrap.properties and added to search-service.properties:
        spring.rabbitmq.host=localhost
        spring.rabbitmq.port=5672
        spring.rabbitmq.username=guest
        spring.rabbitmq.password=guest
  1. In order to demonstrate the centralized configuration of properties and propagation of changes, add a new application-specific property to the property file. We will add originairports.shutdown to temporarily take out an airport from the search. Users will not get any flights when searching with an airport mentioned in the shutdown list:
        originairports.shutdown=SEA

在前面的示例中,当以 SEA 为始发地进行搜索时,我们不会返回任何航班。

  1. Commit this new file into the Git repository by executing the following commands:
        git add –A .
        git commit –m “adding new configuration”      

最终的 search-service.properties 文件应如下所示:

        spring.rabbitmq.host=localhost
        spring.rabbitmq.port=5672
        spring.rabbitmq.username=guest
        spring.rabbitmq.password=guest
        originairports.shutdown:SEA

 

 

chapter7.search 项目的 bootstrap.properties 应该如下所示:

        spring.application.name=search-service
        server.port=8090
        spring.cloud.config.uri=http://localhost:8888
  1. Modify the Search microservice code to use the configured parameter, originairports.shutdown. A RefreshScope annotation has to be added at the class level to allow properties to be refreshed when there is a change. In this case, we are adding a refresh scope to the SearchRestController class.
        @RefreshScope
  1. Add the following instance variable as a placeholder for the new property that is just added in the Config Server. The property names in the search-service.properties must match:
        @Value("${originairports.shutdown}")
        private String originAirportShutdownList;
  1. Change the application code to use this property. This is done by modifying the search method as follows:
        @RequestMapping(value="/get", method = RequestMethod.POST)
        List<Flight> search(@RequestBody SearchQuery query){
logger.info("Input : "+ query);
          if(Arrays.asList(originAirportShutdownList.split(","))
          .contains(query.getOrigin())){
logger.info("The origin airport is in shutdown state");
            returnnew ArrayList<Flight>();
          }
          return searchComponent.search(query);
        }

修改搜索方法,读取参数originAirportShutdownList,查看请求的origin是否在关闭列表中。如果匹配,搜索方法将返回一个空的航班列表,而不是进行实际搜索。

  1. Start the Config Server. Then start the Search microservice. Make sure that the Rabbit MQ server is running.
  2. Modify the chapter7.website project to match the bootstrap.properties content, as follows, to utilize the Config Server:
        spring.application.name=test-client
        server.port=8001
        spring.cloud.config.uri=http://localhost:8888
  1. Change the CommandLineRunner's run method in Application.java to query SEA as origin airport.
        SearchQuery searchQuery = new SearchQuery(
          "SEA","SFO","22-JAN-18");
  1. Run the chapter7.website project. CommandLineRunner will now return an empty flight list. The following message will be printed in the server:
        The origin airport is in shutdown state

Handling configuration changes

/refresh 端点将 refresh 本地缓存的配置属性并重新加载来自配置服务器的值。

为了强制重新加载配置属性,请调用微服务的 /refresh 端点。这实际上是 Actuator 的刷新端点。以下命令将向 /refresh 端点发送一个空 POST:

curl –d {} localhost:8090/refresh

Spring Cloud Bus for propagating configuration changes

通过上述方法,可以在不重新启动微服务的情况下更改配置参数。当只有一个或两个 instances 服务运行时,这很好。如果有很多实例会发生什么

例如,如果有五个实例,那么我们必须针对每个服务实例点击 /refresh。这绝对是一项繁琐的活动。

下图展示了使用 Spring Cloud Bus 的解决方案:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Spring Cloud Bus 提供了一种机制来跨多个实例刷新配置,而无需知道有多少实例,也不知道它们的位置。当有许多微服务的服务实例在运行时,或者当有许多不同类型的微服务在运行时,这特别方便。这是通过单个消息代理连接所有服务实例来完成的。每个实例都订阅更改事件并在需要时刷新其本地配置。通过点击 /bus/refresh 端点调用任何一个实例来触发此刷新,然后通过云总线和公共消息代理传播更改。

Setting up high availability for the Config Server

前面的部分探索了如何设置配置服务器,允许实时刷新配置属性。但是,配置服务器是 single 点的 failure 在这个架构中。

上一节建立的默认架构中存在三个单点故障。其中之一是 Config Server 本身的可用性,第二个是 Git 存储库,第三个是 RabbitMQ 服务器。

下图显示了 Config Server 的高可用性架构:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

架构机制和原理解释如下:

配置服务器需要高可用性,因为如果配置服务器不可用,服务将无法引导。因此,高可用性需要冗余配置服务器。但是,如果服务启动后配置服务器不可用,应用程序可以继续运行。在这种情况下,服务将以最后已知的配置状态运行。因此,Config Server 可用性与微服务可用性不在同一关键级别。

为了使配置服务器具有高可用性,我们需要配置服务器的多个实例。由于 Config Server 是一个无状态的 HTTP 服务,因此可以并行运行多个配置服务器实例。根据配置服务器上的负载,必须调整多个实例。 bootstrap.properties 不能处理多个服务器地址。因此,应该将多个配置服务器配置为在负载平衡器之后或具有故障转移和回退功能的本地 DNS 之后运行。负载均衡器或 DNS 服务器 URL 将在微服务 bootstrap.properties 中配置。这是假设 DNS 或负载平衡器具有高可用性并且能够处理故障转移。

在生产场景中,不建议使用基于本地文件的 Git 存储库。配置服务器通常应该由高可用性 Git 服务支持。这可以通过使用外部高可用性 Git 服务或高可用性内部 Git 服务来实现。也可以考虑SVN。

话虽如此,已经引导的配置服务器始终能够使用配置的本地副本。因此,仅当配置服务器 需要 进行扩展时,我们才需要一个高可用性的 Git。因此,这也没有微服务可用性或 Config Server 可用性那么重要。

Note

用于设置高可用性的 GitLab 示例可在以下链接中找到:https:// about.gitlab.com/high-availability/

RabbitMQ 也必须配置为高可用性。 RabbitMQ 的高可用性仅需要将配置更改动态推送到所有实例。由于这更像是一种离线的、受控的活动,因此它并不真正需要组件所需的高可用性。

Rabbit MQ 高可用性可以通过使用 cloud 服务或本地配置的高可用性 Rabbit MQ 服务来实现。

Note

为 Rabbit MQ 设置高可用性记录在链接 https://www.rabbitmq 中。 com/ha.html.

Monitoring Config Server health

Config Server 只不过是一个 Spring Boot 应用程序,默认情况下,配置有一个 Actuator。因此,所有 Actuator 端点都适用于 云服务器。 server 的运行状况可以使用以下 Actuator URL 进行监控:

http://localhost:8888/health

Config Server for configuration files

我们可能会遇到需要一个完整的配置文件外化的场景,比如 logback.xml。 Config Server 提供了一种机制来配置和存储这些文件。这可以通过使用如下 URL 格式来实现:

    /{name}/{profile}/{label}/{path}

nameprofilelabel 的含义如前所述.路径表示文件名,如logback.xml

Completing changes to use Config Server

为了将这种能力构建到完整的BrownField Airline 的PSS,我们必须为所有服务使用配置服务器。

Note

我们的 chapter7.* 示例中的所有微服务都需要进行类似的更改,以查看 Config Server 以获取配置参数。

我们目前没有将搜索、预订和登记服务中使用的队列名称外部化。在本章后面,这些将被更改为使用 Spring Cloud Streams。

Eureka for registration and discovery


到目前为止,我们已经实现了外部化配置 参数,以及 load 平衡许多服务实例。

基于功能区的负载 balancing 足以满足大多数微服务需求。但是,这种方法在以下情况下不足:

  • If there is a large number of microservices, and if we want to optimize infrastructure utilization, we will have to dynamically change the number of service instances and associated servers. It is not easy to predict and preconfigure the server URLs in a configuration file.
  • When targeting cloud deployments for highly scalable microservices, static registration and discovery is not a good solution considering the elastic nature of the cloud environment.
  • In cloud deployment scenarios, IP addresses are not predictable and will be difficult to statically configure in a file. We will have to update the configuration file every time there is a change in address.

功能区方法部分解决了这个问题。使用 Ribbon,我们可以动态更改服务实例,但是每当我们添加新的服务实例或关闭实例时,我们都必须手动去更新 Config Server。尽管配置更改将自动传播到所有必需的实例,但手动配置更改不适用于大规模部署。在管理大型部署时,尽可能实现自动化至关重要。

为了弥补这一差距,微服务应通过动态注册服务可用性和为消费者提供自动发现来自我管理其生命周期。

Understanding dynamic service registration and discovery

动态注册主要是来自service 供应商的观点。通过动态注册,当新服务启动时,它会自动在中央服务注册表中登记其可用性。同样,当服务停止服务时,它会自动从服务注册表中除名。注册表始终保持可用服务的最新信息及其元数据。

从服务消费者的角度来看,动态发现是适用的。动态发现是客户端查找服务注册表以获取服务拓扑的当前状态,然后相应地调用服务的地方。在这种方法中,不是静态配置服务 URL,而是从服务注册表中获取 URL。

客户端可以保留注册表数据的本地缓存以便更快地访问。一些注册表实现允许客户端监视他们感兴趣的项目。在这种方法中,注册表服务器中的状态更改将传播给相关方,以避免使用过时的数据。

有许多选项可用于动态服务注册和发现。 Netflix Eureka、Zookeeper 和 Consul 作为 Spring Cloud 的一部分提供,如 start.spring 所示。 io 截图如下。 etcd 是 Spring Cloud 之外的另一个服务注册中心,用于实现动态服务注册和发现。在本章中,我们将重点介绍 Eureka 的实现:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Understanding Eureka

Spring Cloud Eureka 也来自 来自 Netflix OSS。 Spring Cloud 项目提供了一种 Spring 友好的声明式方法,用于将 Eureka 与基于 Spring 的应用程序集成。 Eureka 主要用于自注册、动态发现和负载平衡。 Eureka 内部使用 Ribbon 进行负载平衡。

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

如上图所示,Eureka 由一个服务器组件和一个客户端组件组成。服务器组件是所有微服务在其中注册其可用性的注册表。注册通常包括服务标识及其 URL。微服务使用 Eureka Client 来注册它们的可用性。消费组件将使用Eureka Client 用于发现服务实例。

当微服务被引导时,它到达Eureka Server 并通过绑定信息宣传其存在。注册后,服务端点每 30 秒向注册表发送 ping 请求以更新其租约。如果服务端点无法续订几次租约,则该服务端点将从服务注册表中取出。注册表信息被复制到所有 Eureka 客户端,因此客户端需要为每个请求转到远程 Eureka Server。 Eureka 客户端从服务器获取注册信息并在本地缓存。之后,客户使用该信息来查找其他服务。通过获取最后一个获取周期和当前周期之间的增量更新,定期(每 30 秒)更新此信息。

当客户端想要联系微服务端点时,Eureka Client 会根据请求的服务 ID 提供当前可用服务的列表。 Eureka Server 是区域感知的。注册服务时也可以提供区域信息。

当客户端请求服务实例时,Eureka 服务会尝试查找在同一区域中运行的服务。然后,Ribbon 客户端在 Eureka Client 提供的这些可用服务实例之间进行负载平衡。 Eureka ClientEureka Server 之间的通信使用 REST 和JSON。

Setting up the Eureka Server

在本节中,我们将运行 通过 设置 Eureka Server 所需的步骤。

Note

本节的完整源代码可在 chapter7.eurekaserver 项目中找到rajeshrv/Spring5Microservice" target="_blank">https://github.com/rajeshrv/Spring5Microservice。请注意,Eureka Server 注册和刷新周期最多需要 30 秒。因此,在运行服务和客户端时,请等待 40-50 秒。

启动一个新的 Spring Starter 项目并选择 Config Client, Eureka ServerActuator,如下图所示:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Eureka 服务器的项目结构如下图所示:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

请注意,主应用程序名为 EurekaserverApplication.java

application.properties 重命名为 bootstrap.properties,因为这是使用配置服务器。正如我们之前所做的那样,在 bootsratp.properties 中配置 Config Server 的详细信息,以便它可以找到 Config Server 实例。 bootstrap.properties 将如下所示:

        spring.application.name=eureka-server1
        server.port:8761
        spring.cloud.config.uri=http://localhost:8888

Eureka Server 可以设置为独立模式或集群模式。我们将从独立模式开始。默认情况下,Eureka Server 本身就是另一个 Eureka Client。当有多个 Eureka 服务器运行以实现高可用性时,这尤其有用。客户端组件负责从其他 Eureka 服务器同步状态。通过配置 eureka.client.serviceUrl.defaultZone 属性,Eureka Client 被带到它的对等点。

在独立模式下,我们会将 eureka.client.serviceUrl.defaultZone 指向同一个独立实例。稍后,我们将看到如何以集群模式运行 Eureka 服务器。

执行以下步骤来设置 Eureka 服务器:

  1. Create a eureka-server1.properties file and update it in the Git repository. eureka-server1 is the name of the application given in the applications bootstrap.properties in the previous step. As shown next, the serviceUrl points back to the same server. Once the following properties are added, commit the file to the Git repository:
        spring.application.name=eureka-server1
        eureka.client.serviceUrl.defaultZone:http:
          //localhost:8761/eureka/
        eureka.client.registerWithEureka:false
        eureka.client.fetchRegistry:false     
  1. Change the default Application.java. In this example, the package is renamed as com.brownfield.pss.eurekaserver and the class name is also changed to EurekaserverApplication. In EurekaserverApplication, add @EnableEurekaServer:
        @EnableEurekaServer
        @SpringBootApplication
        publicclass EurekaserverApplication {
  1. We are now ready to start the Eureka Server. Ensure the Config Server is also started. Right-click on the application and Run As, Spring Boot App. Once the application starts, open the following link in a browser to see the Eureka console:

http://localhost:8761

  1. In the console, note that there is no instance registered under the instances currently registered with Eureka. Since there are no services started with the Eureka Client enabled, the list is empty at this point.
  2. Making a few changes to our microservices will enable dynamic registration and discovery using the Eureka service. To do this, first we have to add Eureka dependencies in pom.xml. If the services are being built up fresh using the Spring Starter project, then select Config Client, Actuator, Web, as well as the Eureka Discovery client, as shown in the following screenshot:
读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务
  1. Since we are modifying all our microservices, add the following additional dependency to all the microservices in their pom.xml files:
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

以下属性必须添加到 config-repo 下各自配置文件中的所有微服务中。这将有助于微服务连接到 Eureka Server。更新完成后提交到 Git:

        eureka.client.serviceUrl.defaultZone: 
          http://localhost:8761/eureka/
  1. Add @EnableDiscoveryClient to all the microservices in their respective Spring Boot main classes. This will ask Spring Boot to register these services at startup to advertise its availability.
  2. Instead of RestTemplate, we will use @FeignClient in this example by introducing a FareServciesProxy, as shown next:
        @FeignClient(name="fares-service")
        publicinterface FareServiceProxy {    
          @RequestMapping(value = "fares/get",
          method=RequestMethod.GET)
          Fare getFare(@RequestParam(value="flightNumber") 
          String flightNumber, @RequestParam(value="flightDate") 
          String flightDate);
        }
  1. In order to do this, we have to add a Feign dependency:
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
  1. Start all services except website service.
  2. If you go to the Eureka URL, you can see that all three instances are up and running:

http://localhost:8761

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务
  1. Change the website project bootstrap.properties to make use of Eureka rather than connecting directly to the service instances. We will use the load-balanced RestTemplate. Commit these changes to the Git repository:
        spring.application.name=test-client
        eureka.client.serviceUrl.defaultZone: 
          http://localhost:8761/eureka/
  1. Add @EnableDiscoveryClient to the Application class to make the client Eureka aware.
  2. Edit both Application.java as well as BrownFieldSiteController.java. Add RestTemplate instances. This time, we annotate them with @Loadbalanced to ensure that we are using the load balancing features using Eureka and Ribbon. RestTemplate cannot be automatically injected. Hence, we have to provide a configuration entry, as follows:
        @Configuration
        class AppConfiguration {
          @LoadBalanced
          @Bean
          RestTemplate restTemplate() {
            returnnew RestTemplate();
          }
        }
        @Autowired
        RestTemplate restClient;
  1. We will use these RestTemplate instances to call the microservices. We will replace the hard-coded URLs with service IDs, which are registered in the Eureka Server. In the following code, we use the service names search-service, book-service and checkin-service instead of explicit host names and ports:
        Flight[] flights = searchClient.postForObject(
          "http://search-service/search/get",
           searchQuery, Flight[].class);=

          long bookingId = bookingClient.postForObject(
            "http://book-service/booking/create", booking, long.class);

          long checkinId = checkInClient.postForObject(
          "http://checkin- service/checkin/create", checkIn,
           long.class);
  1. We are now ready to test. Run the website project. If everything is fine, the website project's CommandLineRunner will successfully perform search, book, and check-in.
  2. The same can also be tested using the browser by pointing the browser to http://localhost:8001.

High availability for Eureka

在前面的示例中,在独立模式下只有一个 Eureka 服务器。这对于真正的生产系统来说还不够好。

Eureka Client 连接到服务器,获取注册表信息,并将其存储在本地缓存中。客户端始终使用此本地缓存。 Eureka Client 会定期检查服务器是否有任何状态更改。如果发生状态更改,它会从服务器下载更改并更新缓存。如果 Eureka Server 不可达,那么 Eureka Client 仍然可以根据客户端缓存中可用的数据使用服务器的最后一个已知状态。但是,这可能会很快导致陈旧状态问题。

本节将探讨 Eureka Server 的高可用性。高可用性架构如下图所示:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Eureka Server 采用点对点数据同步机制构建。运行时状态信息不存储在数据库中,而是使用内存缓存进行管理。高可用性实现有利于 CAP 定理中的可用性和分区容错性,而忽略了一致性。由于 Eureka Server 实例使用异步机制相互同步,因此服务器实例之间的状态可能并不总是匹配。点对点同步是通过将服务 URL 相互指向来完成的。如果有多个 Eureka Server,则每一个都必须连接到至少一个对等服务器。由于状态在所有对等方之间复制,因此 Eureka 客户端可以连接到任何可用的 Eureka 服务器。

为 Eureka 实现高可用性的最佳方法是集群多个 Eureka 服务器并在负载均衡器或本地 DNS 后面运行它们。客户端始终使用 DNS 或负载平衡器连接到服务器。在运行时,负载均衡器将负责选择合适的服务器。此负载均衡器地址将提供给 Eureka 客户端。

本节将展示如何在集群中运行两台 Eureka 服务器以实现高可用性。为此,定义两个属性文件——eureka-server1eureka-server2。这些是对等服务器——如果一个失败,另一个将接管。这些服务器中的每一个也将充当另一个的客户端,以便它们可以同步它们的状态。接下来定义定义的两个属性文件。将这些属性上传并提交到 Git 存储库。在以下配置中,客户端 URL 相互指向,形成对等网络:

  #eureka-server1.properties
  eureka.client.serviceUrl.defaultZone:http://localhost:8762/eureka/
  eureka.client.registerWithEureka:false
  eureka.client.fetchRegistry:false

  #eureka-server2.properties
  eureka.client.serviceUrl.defaultZone:http://localhost:8761/eureka/
  eureka.client.registerWithEureka:false
  eureka.client.fetchRegistry:false

更新Eureka的bootstrap.properties,将应用名称改为eureka。由于我们使用两个配置文件,配置服务器将根据活动配置文件查找 eureka-server1eureka-server2启动时提供:

  spring.application.name=eureka
  spring.cloud.config.uri=http://localhost:8888

8761server2 上启动两个 Eureka 服务器实例 - server18762 上:

    java -jar –Dserver.port=8761 -Dspring.profiles.active=server1
      demo-0.0.1-SNAPSHOT.jar

    java -jar –Dserver.port=8762 -Dspring.profiles.active=server2
      demo-0.0.1-SNAPSHOT.jar

我们所有的服务仍然指向第一台服务器 server1。打开两个浏览器窗口。

http://localhost:8761http://localhost:8762

启动所有微服务。打开 8761 的那个会立即反映变化,而另一个需要 30 秒才能反映状态。由于两台服务器都在一个集群中,因此这两台服务器之间的状态是同步的。如果我们将这些服务器保留在负载平衡器或 DNS 之后,那么客户端将始终连接到可用服务器之一。

完成本练习后,切换回独立模式进行剩余练习。

Zuul proxy as the API Gateway


在大多数微服务实现中,内部微服务端点不会暴露在外部。它们被保存为私人服务。一组公共服务使用 API 网关向客户端公开。这样做的原因有很多,其中一些原因如下:

  • Only a selected set of microservices are required by the clients.
  • If there are client-specific policies to be applied, it is easy to apply them in a single place rather than in multiple places. An example of such a scenario is the cross-origin access policy.
  • It is hard to implement client-specific transformations at the service endpoint.
  • If there is data aggregation required, especially to avoid multiple client calls in a bandwidth restricted environment, then a gateway is required in the middle.

Zuul 是一个简单的网关服务或边缘服务,非常适合这种情况。 Zuul 也来自 Netflix 微服务产品家族。与许多企业 API 网关产品不同,Zuul 为开发人员提供了完全的控制权,可以根据特定的需求进行配置或编程。

下图显示 Zuul 充当代理 负载均衡器用于微服务 A

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Zuul 代理在内部将 Eureka Server 用于 service< /a> 发现,以及用于服务实例之间负载平衡的 Ribbon。

Zuul 代理还能够进行路由、监控、管理弹性、安全性等。简单来说,我们可以将 Zuul 视为一个反向代理服务。使用 Zuul,我们甚至可以通过在 API 层重写底层服务来改变它们的行为。

Setting up Zuul

与 Eureka Server 和 Config Server 不同,在典型的部署中,Zuul 是特定于微服务的。但是,在某些部署中,一个 API 网关涵盖 多个微服务。在这种情况下,我们将为我们的每个微服务添加 Zuul——搜索、预订、票价和签到。

Note

本节的完整源代码位于代码文件中的 chapter7.*-apigateway 项目下。

执行以下步骤来设置 Zuul:

  1. Convert the microservices one by one. Start with the Search API Gateway. Create a new Spring Starter project and select Zuul, Config Client, Actuator, and Eureka Discovery:
读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

search-apigateway 的项目结构如下图所示:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务
  1. The next step is to integrate the API Gateway with Eureka and the Config Server. Create a search-apigateway.property file with the contents as given next, and commit to the Git repository.

此配置还设置了有关如何转发流量的规则。在这种情况下,API 网关的 /api 端点上的任何请求都应该发送到搜索服务:

        spring.application.name=search-apigateway
        zuul.routes.search-apigateway.serviceId=search-service
        zuul.routes.search-apigateway.path=/api/**
        eureka.client.serviceUrl.defaultZone:
        http://localhost:8761/eureka/

search-service是Search服务的服务ID,会使用Eureka Server解析。

  1. Update bootstrap.properties of search-apigateway as follows. There is nothing new in this configuration--a name to the service, port, and the Config Server URL:
        spring.application.name=search-apigateway
        server.port=8095
        spring.cloud.config.uri=http://localhost:8888
  1. Edit Application.java. In this case, the package name and the class name also change to com.brownfield.pss.search.apigateway and SearchApiGateway respectively. Also add @EnableZuulProxy to tell Spring Boot that this is a Zuul proxy:
        @EnableZuulProxy
        @EnableDiscoveryClient
        @SpringBootApplication
        publicclass SearchApiGateway {
  1. Run this as Spring Boot app. Before that, ensure that the Config Server, the Eureka Server, and the Search microservice are running.
  2. Change the website project's CommandLineRunner, as well as BrownFieldSiteController, to make use of the API Gateway:
        Flight[] flights = searchClient.postForObject(
          "http://search-apigateway/api/search/get",
          searchQuery, Flight[].class);

在这种情况下,Zuul 代理充当反向代理,将所有微服务端点代理给消费者。在前面的示例中,Zuul 代理并没有增加太多价值,因为我们只是将传入的请求传递给相应的后端服务。

当我们有如下一个或多个需求时,Zuul 特别有用:

  • Enforcing authentication and other security policies at the gateway instead of doing that on every microservice endpoint. The gateway can handle security policies, token handling, and so on before passing the request to the relevant services behind. It can also do basic rejections based on some business policies, such as blocking requests coming from certain black-listed users.
  • Business insights and monitoring can be implemented at the gateway level. Collect real-time statistical data and push it to an external system for analysis. This will be handy, as we can do this at one place rather than applying it across many microservices.
  • API gateways are useful in scenarios where dynamic routing is required based on fine-grained controls. For example, send requests based on certain specific business values such as gold customer to a different service instances. For example, all requests coming from a region to be sent to one group of service instances. Another example--all requests requesting for a particular product have to be routed to a group of service instances.
  • Handling the load shredding and throttling requirements is another scenario where API gateways are useful. This is when we have to control load based on set thresholds such as number of requests in a day. For example, control requests coming from a low-value third-party online channel.
  • The Zuul gateway is useful for fine-grained load balancing scenarios. Zuul, Eureka Client, and Ribbon together provide fine-grained controls over load balancing requirements. Since the Zuul implementation is nothing but another Spring Boot application, the developer has full control of the load balancing.
  • The Zuul gateway is also useful in scenarios where data aggregation requirements are in place. If the consumer wants higher-level coarse-grained services, then the gateway can internally aggregate data by calling more than one service on behalf of the client. This is particularly applicable when the clients are working in low-bandwidth environments.

Zuul 还提供了许多过滤器。这些过滤器分为前置过滤器、路由过滤器、后置过滤器和错误过滤器。正如名称所示,这些应用在服务调用的不同生命周期阶段。 Zuul 还为开发人员提供了编写自定义过滤器的选项。为了编写自定义过滤器,从抽象的 ZuulFilter 扩展,并实现如下所示的方法:

 

    publicclass CustomZuulFilter extends ZuulFilter{
    public Object run(){}
    publicboolean shouldFilter(){}
    publicint filterOrder(){}
    public String filterType(){}

实现自定义过滤器后,将该类添加到主上下文中。在我们的示例中,将其添加到 SearchApiGateway 类中,如下所示:

 

    @Bean
    public CustomZuulFilter customFilter() {
      returnnew CustomZuulFilter();
    }

如前所述,Zuul 代理是一个 Spring Boot 服务。我们可以按照我们想要的方式以编程方式自定义网关。如以下代码所示,我们可以将自定义端点添加到网关,网关又可以调用后端服务:

 

    @RestController
    class SearchAPIGatewayController {
      @RequestMapping("/")
      String greet(HttpServletRequest req){
        return "<H1>Search Gateway Powered By Zuul</H1>";
      }
    }

在前面的例子中,它只是添加一个新的端点,并从网关返回一个值。我们可以进一步使用 @Loadbalanced RestTemplate 来调用后端服务。由于我们拥有完全控制权,我们可以进行转换、数据聚合等。我们还可以使用 Eureka API 获取服务器列表实现完全独立的负载均衡或流量整形机制,而不是out功能区提供的开箱即用的负载平衡功能。

High availability of Zuul

Zuul 只是一个带有 HTTP 端点的 stateless 服务,因此,我们可以拥有任意数量的 Zuul 实例。不需要亲和力或粘性。但是,Zuul 的可用性非常关键,因为从消费者到提供者的所有流量都流经 Zuul 代理。但是,弹性扩展要求并不像后端微服务那么重要,所有繁重的工作都在后端微服务中进行。

Zuul 的高可用架构是由我们使用 Zuul 的场景决定的。典型的使用场景如下:

  • When a client-side java script MVC such as Angular JS accesses Zuul services from a remote browser
  • Another microservice or non-microservice accesses services via Zuul

在某些情况下,客户端可能无法使用 Eureka 客户端库,例如在 PL/SQL 上编写的遗留应用程序。在某些情况下,组织策略不允许 Internet 客户端处理客户端负载平衡。对于基于浏览器的客户端,可以使用第三方 Eureka JavaScript 库。

这一切都归结为客户端是否使用 Eureka 客户端库。基于此,我们可以通过两种方式设置 Zuul 以实现高可用性。

High availability of Zuul when the client is also a Eureka Client

在这种情况下,由于 客户端也是另一个 Eureka 客户端,因此可以像其他微服务一样配置 Zuul。 Zuul 本身使用服务 ID 注册到 Eureka。然后客户端使用 Eureka 和服务 ID 来解析 Zuul 实例。

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

如上图所示,Zuul 服务使用服务 ID 向 Eureka 注册自己,在我们的例子中是 search-apigatewayEureka Client 将要求 ID 为 search-apigateway 的服务器列表。 Eureka Server 返回基于当前 Zuul 拓扑的服务器列表。 Eureka Client 基于此列表,选择其中一个服务器,并发起调用。

正如我们之前看到的,客户端将使用服务 ID 来解析 Zuul 实例。下面的例子中,search-apigateway就是在Eureka注册的Zuul实例ID:

    Flight[] flights = searchClient.postForObject(
      "http://search-apigateway/api/search/get",
       searchQuery, Flight[].class);

High availability when client is not a Eureka Client

在这种情况下,客户端不能够使用Eureka Server处理负载均衡。如下图所示,客户端将请求发送到负载均衡器,负载均衡器依次识别正确的 Zuul 服务实例。

The Zuul instances, in this case, will be running behind a load balancer such as HAProxy, or a hardware load balancer like NetScaler:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

Zuul 仍将使用 Eureka Server 对微服务进行负载平衡。

Completing Zuul for all other services

为了完成这个练习,为所有 我们的 微服务添加一个 API 网关。完成此任务需要执行以下步骤:

  1. Create new property files per service and check in to Git repositories.
  2. Change application.properties to bootstrap.properties and add the required configurations.
  3. Add @EnableZuulProxy in the application.
  4. @EnableDiscoveryClient in all applications.
  5. Optionally, change the default generated package names and the filenames.

最后,我们将拥有以下 API Gateway 项目:

  • chapter7.fares-apigateway
  • chapter7.search-apigateway
  • chapter7.checkin-apigateway
  • chapter7.book-apigateway

Streams for reactive microservices


Spring Cloud Streams 提供了一个抽象over 消息传递基础设施。底层消息传递实现可以是 RabbitMQ、Redis 或 Kafka。 Spring Cloud Streams 提供了一种声明式的方法 用于发送和接收消息。

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

如上图所示,Cloud Streams 使用 Source 和 Sink 的概念。 Source 代表消息的发送者视角,Sink 代表消息的接收者视角。

在给定的示例中,Sender 定义了一个名为 Source.OUTPUT 的逻辑队列,< span class="strong">Sender 发送消息。 Receiver 定义了一个名为 Sink.INPUT 的逻辑队列,Receiver 检索消息。 OUTPUTINPUT 的物理绑定通过配置进行管理。在这种情况下,两者都链接到同一个物理队列,即 RabbitMQ 上的 MyQueue。因此,在一端,Source.OUTPUT 将指向 MyQueue,而在另一端,Sink.INPUT 将指向相同的 MyQueue

Spring Cloud 提供了在一个应用程序中使用多个消息传递提供程序的灵活性,例如将来自 Kafka 的输入流连接到 Redis 输出流,而无需管理复杂性。 Spring Cloud Streams 是基于消息的集成的基础。 Cloud Stream Modules 子项目是另一个提供许多端点实现的 Spring Cloud 库。

下一步,重建与 Cloud Streams 的微服务间消息通信。如图所示,我们将定义一个 SearchSink 连接到 Search 微服务下的 InventoryQ。 Booking 将定义一个 BookingSource 用于发送连接到 InventoryQ 的库存更改消息。同样,Checkin 定义了一个 CheckinSource 用于发送签入消息。 Booking 定义了一个接收器 BookingSink 用于接收消息,两者都绑定到 Rabbit MQ 上的 CheckinQ 队列。

下图显示了使用基于流的架构的示例设置:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

在本例中,我们将使用 RabbitMQ 作为消息代理。执行以下步骤:

  1. Add the following Maven dependency to Booking, Search, and Check-in, as these are the three modules using messaging:
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
  1. Add the following two properties to booking-service.properties. These properties bind the logical queues, inventoryQ to the physical inventoryQ, and the logical checkinQ to physical checkinQ.
        spring.cloud.stream.bindings.inventoryQ.destination=inventoryQ
        spring.cloud.stream.bindings.checkInQ.destination=checkInQ
  1. Add the following property to search-service.properties. This property binds the logical queue inventoryQ to the physical inventoryQ:
        spring.cloud.stream.bindings.inventoryQ.destination=inventoryQ
  1. Add this next property to the checkin-service.properties. This property binds the logical queue checkinQ to the physical checkinQ:
        spring.cloud.stream.bindings.checkInQ.destination=checkInQ
  1. Commit all files to the Git repository.
  2. The next step is to edit the code. The Search microservice consumes messages from the Booking microservice. In this case, Booking is the source and Search is the sink.
  3. Add @EnableBinding to the Sender class of the Booking service. This enables the Cloud Stream to work on auto-configurations based on the message broker library available in the class path. In our case, it is RabbitMQ. The parameter, BookingSource defines the logical channels to be used for this configuration:
        @EnableBinding(BookingSource.class)
        publicclass Sender {

在这种情况下,BookingSource 定义了一个名为 inventoryQ 的消息通道,该通道物理绑定到 Rabbit MQ inventoryQ,在配置中配置。 BookingSource 使用注释 @Output 来指示这是输出类型——从模块。此信息将用于消息通道的自动配置:

        interface BookingSource {
          publicstatic String InventoryQ="inventoryQ";
          @Output("inventoryQ")
          public MessageChannel inventoryQ();       
        }
  1. Instead of defining a custom class, we can also use the default Source class that comes with Spring Could Streams, if the service has only one source and sink:
        public interface Source {
          @Output("output")
          MessageChannel output();
        }
  1. Define a message channel in the sender based on BookingSource. The following code will inject an output message channel with the name inventory, which is already configured in BookingSource:
        @Output (BookingSource.InventoryQ)
        @Autowired
        private MessageChannel messageChannel;    
  1. Reimplement the send message method in the Booking sender:
        publicvoid send(Object message){
          messageChannel.send(
          MessageBuilder.withPayload(message).build());
        }
  1. Now add the following to the Search Receiver class the same way we did it for the Booking service:
        @EnableBinding(SearchSink.class)
        publicclass Receiver {
  1. In this case, the SearchSink interface will look like the following. This will define the logical sink queue it is connected with. The message channel in this case is defined as @Input to indicate that this message channel is to accept messages:
        interface SearchSink {
          publicstatic String INVENTORYQ="inventoryQ";

          @Input("inventoryQ")
          public MessageChannel inventoryQ()
        }
  1. Amend the Search service to accept this message:
        publicvoid accept(Map<String,Object> fare){
          searchComponent.updateInventory(
          (String)fare.get("FLIGHT_NUMBER"),
          (String)fare.get("FLIGHT_DATE"),
          (int)fare.get("NEW_INVENTORY"));}     
  1. We will still need the RabbitMQ configurations that we have in our configuration files to connect to the message broker:
        spring.rabbitmq.host=localhost
        spring.rabbitmq.port=5672
        spring.rabbitmq.username=guest
        spring.rabbitmq.password=guest
        server.port=8090
  1. Run all the services, and run the website project. If everything is fine, the website project successfully executes the search, book, and check-in functions. The same can also be tested using the browser by pointing to http://localhost:8001.

Protecting microservices with Spring Cloud Security


在单片 Web 应用程序中,一旦用户登录,用户相关信息将存储在 HTTP 会话中。所有后续请求都将根据 HTTP 会话进行验证。这很容易管理,因为所有请求都将通过同一会话路由,或者通过 session 关联或卸载、共享会话存储。

在微服务的情况下,更难防止未经授权的访问,尤其是在远程部署和访问许多服务时。微服务的一个典型或相当简单的模式是通过使用网关作为安全看门狗来实现外围安全。任何到达网关的请求都将受到挑战和验证。在这种情况下,确保对下游微服务的所有请求都通过 API 网关汇集非常重要。通常,位于前面的负载均衡器将是唯一向网关发送请求的客户端。在这种方法中,下游微服务处理所有请求,假设它们是可信的,而不需要进行身份验证。这意味着所有微服务端点都将对所有人开放。

但是,这种解决方案对于企业网络安全来说可能是不可接受的。消除这种担忧的方法之一是创建网络隔离和区域,以便仅向网关公开服务以供访问。为了简化这种情况,一种常见的模式是设置消费者驱动的网关,它结合了多个微服务访问,而不是我们在示例中使用的一对一网关。

另一种实现方式是通过令牌中继,如下图所示:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

在这种情况下,每个微服务也将充当资源服务器,并带有中央服务器进行身份验证。 API 网关会将带有令牌的请求转发给下游微服务进行身份验证。

Summarising the BrownField PSS architecture


下图显示了我们使用 Config Server、Eureka、Feign、Zuul 和 Cloud Streams 创建的overall 架构。

该架构还包括所有组件的高可用性。在这种情况下,我们假设客户端正在使用 Eureka 客户端库:

读书笔记《building-microservices-with-spring》使用Spring Cloud组件扩展微服务

下表给出了项目的摘要和它们正在侦听的端口:

微服务

项目

端口

预订微服务

chapter7.book

8060-8064

签入微服务

chapter7.checkin

8070-8074

票价微服务

chapter7.fares

8080-8084

搜索微服务

chapter7.search

8090-8094

网站客户端

chapter7.website

8001

Spring Cloud 配置服务器

chapter7.configserver

8888 / 8889

Spring Cloud 尤里卡服务器

chapter7.eurekaserver

8761 / 8762

预订 API 网关

chapter7.book-apigateway

8095-8099

签入 API 网关

chapter7.checkin-apigateway

8075-8079

票价 API 网关

chapter7.fares-apigateway

8085-8089

搜索 API 网关

chapter7.search-apigateway

8065-8069

按照以下步骤进行最终运行:

  1. Run RabbitMQ.
  2. Build all projects using pom.xml at the root level:
        mvn –Dmaven.test.skip=true clean install
  1. Run the following projects from the respective folders. Note that you should wait for 40-50 seconds before starting the next service. This will ensure that the dependent services are registered and are available before we start a new service.
        java -jar target/config-server-1.0.jar
        java -jar target/eureka-server-1.0.jar
        java -jar target/fares-1.0.jar
        java -jar target/fares-1.0.jar
        java -jar target/search-1.0.jar 
        java -jar target/checkin-1.0.jar 
        java -jar target/book-1.0.jar 
        java –jar target/fares-apigateway-1.0.jar 
        java –jar target/search-apigateway-1.0.jar 
        java –jar target/checkin-apigateway-1.0.jar 
        java –jar target/book-apigateway-1.0.jar 
        java -jar target/website-1.0.jar
  1. Open the browser window, and point to http://localhost:8001

Summary


在本章中,我们学习了如何使用 Spring Cloud 项目扩展十二要素 Spring Boot 微服务。我们在上一章开发的 BrownField Airline 的 PSS 微服务中应用了我们的学习成果。

我们探索了用于外部化微服务配置的 Spring Config Server,以及如何部署 Config Server 以实现高可用性。我们还学习了 Eureka 的负载平衡、动态服务注册和发现。通过实现 Zuul 来检查 API 网关的实现。最后,我们总结了使用 Spring Cloud Streams 的微服务的反应式集成。

BrownField Airline 的 PSS 微服务现在可以部署到互联网规模。下一章将介绍其他 Spring Cloud 组件,例如 Hyterix、Sleuth 等。