vlambda博客
学习文章列表

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》春云

Chapter 28. Spring Cloud

在本章中,我们将学习以下主题:

  • Getting started with Spring Cloud
  • Service discovery using Spring Cloud Consul
  • Using Spring Cloud Netflix—Feign
  • Service discovery using Spring Cloud Netflix—Eureka
  • Using Spring Cloud Netflix—Hystrix

Introduction


在本书中,我们学习了如何创建应用程序、配置 RESTful 服务、进行测试、集成指标和其他管理组件,以及处理打包和部署等。现在,是时候看看应用程序之外的世界了——无处不在的云环境。

在本章中,我们将了解如何使应用程序对云友好,如何处理在云中运行的分布式应用程序的动态特性,如何使我们的应用程序对世界可见,如何发现其他服务端点,如何调用它们,以及如何处理各种错误情况。

Getting started with Spring Cloud


Spring Cloud family 项目为各种框架的 Spring Boot 提供集成扩展,提供分布式服务发现、配置、路由、服务调用等。通过使用统一的 API,我们可以将这些概念添加到我们的应用程序中,并且以后可以在需要时灵活地更改特定的实现,而无需对我们的代码库进行深入的更改。

How to do it...

我们将首先使用基础 Spring Cloud 模块增强我们的 BookPub 项目,方法是将它们添加到主构建配置中:

  1. Add the following content to the build.gradle file located at the root of the project:
... 
apply plugin: 'docker' 
 
dependencyManagement { 
    imports { 
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Finchley.BUILD-SNAPSHOT' 
    } 
} 
 
jar { 
    baseName = 'bookpub' 
    version = '0.0.1-SNAPSHOT' 
} 
 
... 
 
dependencies { 
    ... 
    compile("org.springframework.boot:spring-boot-devtools") 
    compile("org.springframework.cloud:spring-cloud-context") 
    compile("org.springframework.cloud:spring-cloud-commons") 
    runtime("com.h2database:h2") 
    ... 
}
  1. Start the application by running ./gradlew clean bootRun
  2. After the application has been started, even though it seems like nothing new has happened, if we open our browser at http://localhost:8081/actuator/env (the management endpoint for environment), we will see new property sources appear:
{ 
  "name": "springCloudClientHostInfo", 
  "properties": { 
    "spring.cloud.client.hostname": { 
      "value": "127.0.0.1" 
    }, 
    "spring.cloud.client.ip-address": { 
      "value": "127.0.0.1" 
    } 
  } 
} 
  1. Create a bootstrap.properties file under the src/main/resources directory from the root of our project with the following content (the same properties should be commented out inside application.properties at this point):
spring.application.name=BookPub-ch9 
  1. Start the application by running ./gradlew clean bootRun
  2. After the application has been started, open our browser at http://localhost:8081/env and we will see new property sources appear:
{ 
  "name": "applicationConfig: [classpath:/bootstrap.properties]", 
  "properties": { 
    "spring.application.name": { 
      "value": "BookPub-ch9", 
      "origin": "class path resource [bootstrap.properties]:1:25" 
    } 
  } 
}

How it works...

在深入了解 things 的工作原理之前,让我们回顾一下我们对项目所做的更改。第一步是通过导入 Bill of Material build.gradle 构建配置用于 Spring Cloud 发布火车的strong> (BOM) 声明—mavenBom 'org .springframework.cloud:spring-cloud-dependencies:Finchley.BUILD-SNAPSHOT'。虽然我们可以有选择地导入显式定义的 spring-cloud-contextspring -cloud-commons 库,通过依赖打包的 BOM,我们确信我们将使用经过兼容性测试的不同工件的正确版本 彼此。

我们首先添加对 spring-cloud-contextspring-cloud-commons<的依赖/span> 库,用于说明 Spring Cloud 提供的基本常用设施,然后再深入了解特定的启动器集成,例如 spring-cloud-netflix spring-cloud-consul。这些基本库提供了用于在所有不同的特定于云的集成中构建的接口和通用功能的基础。以下是他们的目的:

  • spring-cloud-commons: This provides a collection of shared common interfaces and base classes that define the notions of service discovery, service routing, load balancing, circuit breaking, feature capabilities, and some basic configuration. For example, this is the library that autoconfigures the environment with the springCloudClientHostInfo property source.
  • spring-cloud-context: This is the base foundation that is responsible for bootstrapping and configuring the various integrations, such as a specific implementation of service discovery like Consul, or a specific implementation of circuit breaker like Hystrix. This is achieved by creating an isolated Bootstrap application context, which is responsible for loading and configuring all the components before the main application is started.

引导应用程序上下文在应用程序启动周期的早期创建,它由一个单独的文件配置 - bootstrap.properties(还支持 YAML 变体)。由于在云中运行的应用程序非常典型地依赖于许多外部配置源、服务查找等,因此 Bootstrap 上下文的目的是配置这些功能并从外部获取所有必要的配置。

为了清楚地将应用程序配置与 Bootstrap 分开,我们将描述应用程序的内容、配置外部配置或其他环境变量(例如在何处调用服务发现)放入 bootstrap.properties 而不是 application.properties。在我们的示例中,我们将 spring.application.name 配置放入 bootstrap.properties, 因为在引导阶段需要这些信息;它可用于从远程配置存储中查找配置。

由于 Bootstrap 应用上下文确实是真正的 Spring 应用上下文,所以两者之间存在父子关系,其中 Bootstrap 应用上下文成为 Spring Boot 应用上下文的父级。这意味着在 Bootstrap 上下文中定义的所有 bean 和属性源也可以在应用程序上下文中使用。

当 Spring Cloud 添加到应用程序时,它会自动为特定的 Spring Cloud 模块提供集成框架,例如 Spring Cloud Consul,通过使用现在众所周知的 spring.factories 配置声明。 spring-cloud-commons内部提供的注解,即@SpringCloudApplication@EnableDiscoveryClient@EnableCircuitBreaker@BootstrapConfiguraionPropertySourceLocator 接口 spring-cloud-context 库,旨在定义用于自我配置特定组件的集成点,例如 Consul 等发现客户端、Hystrix 等断路器或远程配置 来源,例如ZooKeeper

让我们详细检查一下:

  • @SpringCloudApplication: This annotation is like @SpringBootApplication, meta-annotation in nature, except it also wraps the @EnableDiscoveryClient and @EnableCircuitBreaker annotations in addition to also being meta-annotated with @SpringBootApplication. It is a good idea to use this annotation when you want to enable both the discovery client and the circuit breaker functionality in your application.
  • @EnableDiscoveryClient: This annotation is used to indicate that Spring Cloud should initialize the provided discovery client for service registry, depending on the included integration library, such as Consul, Eureka, ZooKeeper, and so on.

 

  • @EnableCircuitBreaker: This annotation is used to indicate that Spring Cloud should initialize the circuit breaker capabilities, based on the specific dependency of the integration library, such as Hystrix.
  • PropertySourceLocator: This is used by the integration libraries to implement specific functionality of how to extract remote configuration from the provided datastore. Each integration module, providing ability to load remote configuration, would register an implementing bean of this type that exposes an implementation of PropertySource that is backed by the integration.
  • @BootstrapConfiguration: This annotation is like the @ManagementContextConfiguration annotation, and is (mostly) a marker annotation geared to identify the key inside the spring.factories descriptor to indicate which configuration classes should be loaded during the Spring Cloud Bootstrap process and be part of the Bootstrap application context. Those configurations are read by BootstrapApplicationListener during startup and initialize the specified configurations. Typically, this is where the configuration classes, which define and expose PropertySourceLocator—implementing beans, are configured.

Service discovery using Spring Cloud Consul


在分布式计算的世界中,服务成为一次性非常常见 商品。一个服务的典型生命周期可以用天来衡量,如果不是几个小时的话,并且实例由于某种原因而崩溃并不是闻所未闻的,只是在几秒钟后自动出现一个新的实例。当应用程序的状态如此短暂时,维护静态连接的架构变得非常困难,因为拓扑总是在变化,服务知道其依赖服务的确切位置。

为了帮助解决这个问题,服务发现层开始发挥作用,维护服务注册的集中和分布式状态,随时准备使用最新信息按需回复。应用程序在启动时注册自己,提供有关其位置以及可能有关其功能、服务级别、健康检查状态等的信息。

在本书前面的第25章中,< span>应用打包和部署,我们被介绍给Consul,并将其用于外部应用配置 消费。在这个秘籍中,我们将继续深入了解 Consul 的功能,并学习如何使用 spring-cloud-consul 模块自动向 Consul 注册我们的应用程序。

How to do it...

请看以下设置服务发现的步骤:

  1. Replace the spring-cloud-commons and spring-cloud-context modules with spring-cloud-starter-consul-all by modifying the build.gradle file located in the root of our project with the following content:
... 
 
dependencies { 
    ... 
    compile("io.dropwizard.metrics:metrics-graphite:3.1.0") 
    compile("org.springframework.boot:spring-boot-devtools") 
    //compile("org.springframework.cloud:spring-cloud-context") 
    //compile("org.springframework.cloud:spring-cloud-commons") 
    compile("org.springframework.cloud:spring-cloud-starter-consul-all") 
    runtime("com.h2database:h2") 
    ... 
} 
... 
  1. With Consul dependencies added, we will proceed with enabling our application to automatically register with the local agent upon startup by modifying the BookPubApplication.java file located under the src/main/java/com/example/bookpub directory from the root of our project with the following content:
... 
@EnableScheduling 
@EnableDbCounting 
@EnableDiscoveryClient

 

 

 

 

 

 

 

 

public class BookPubApplication { 
    ... 
}
  1. Given that Consul was successfully installed using the steps described in the Setting up Consul recipe in Chapter 25, Application Packaging and Deployment, we should be able to start it by running consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul and our Terminal window should display the following output:
==> Starting Consul agent... 
==> Starting Consul agent RPC... 
==> Consul agent running! 
           Version: 'v1.0.2' 
...
  1. After the Consul agent is up and running successfully, we will proceed by starting our application by running ./gradlew clean bootRun
  2. As we watch the startup logs scroll by, there are a couple of interesting entries that indicate the application is interacting with the agent, so watch for the following content in the logs:
... 
2017-12-26 --- b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='consul', propertySources=[ConsulPropertySource [name='config/BookPub-ch9/'], ConsulPropertySource [name='config/application/']]] 
... 
2017-12-26 --- o.s.c.consul.discovery.ConsulLifecycle   : Registering service with consul: NewService{id='BookPub-ch9-8080', name='BookPub-ch9', tags=[], address='<your_machine_name>', port=8080, check=Check{script='null', interval=10s, ttl=null, http=http://<your_machine_name>:8081/health, tcp=null, timeout=null}} 
2017-12-26 --- o.s.c.consul.discovery.ConsulLifecycle   : Registering service with consul: NewService{id='BookPub-ch9-8080-management', name='BookPub-ch9-management', tags=[management], address='://<your_machine_name>', port=8081, check=Check{script='null', interval=10s, ttl=null, http=http://chic02qv045g8wn:8081/health, tcp=null, timeout=null}} 
...
  1. Just to verify that our application has registered and is in communication with the local Consul agent, let's open http://localhost:8081/actuator/consul in the browser to see the Consul agent information, as shown in the following screenshot:
    读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》春云

How it works...

当我们添加 spring-cloud-starter-consul-all 作为构建依赖项时,它会自动提取所有必要的组件来为我们的应用程序启用 Consul 功能.我们自动得到了 spring-cloud-consul-binder, spring-cloud-consul-core spring-cloud-consul-configspring- cloud-consul-discovery 工件添加到我们的类路径中。让我们来看看它们:

  • spring-cloud-consul-core: This artifact provides base autoconfiguration to expose generic ConsulProperties, as well as the ConsulClient initialization and setting of the /consul management endpoint, if the Spring Boot Actuator functionality is enabled.
  • spring-cloud-consul-config: This provides the ConsulPropertySourceLocator implementation, used during Bootstrap, to configure the ConsulPropertySource bean, which allows remote configuration consumption from the Consul key/value store. It also sets up a ConfigWatch change observer, which fires RefreshEvent to the application context, if a configuration key value changes in Consul key/value store while the application is running. This allows for a possible configuration properties reload without having to redeploy and restart the application.
  • spring-cloud-consul-discovery: This provides all the functionality and implementations needed for service discovery, service registration, and service invocation.
  • spring-cloud-consul-binder: This provides integration of Consul event functionality with Spring Cloud Stream Framework, enabling it to send and receive events from Consul and respond to them within the application. While outside of the scope of this chapter, more information can be obtained from http://cloud.spring.io/spring-cloud-stream/.

在类路径中添加 spring-cloud-consul-config 会自动注册 ConsulPropertySourcespring-cloud-consul-discovery 模块并非如此。服务发现功能更具侵入性,因此需要开发人员额外的确认步骤,以表明它确实是需要的。这是通过在主应用程序类中添加 @EnableDiscoveryClient注解来实现的;在我们的例子中是 BookPubApplication

一旦添加了 @EnableDiscoveryClient注解,Spring Cloud (EnableDiscoveryClientImportSelector 来自 spring-cloud-commons 模块的类,更准确地说)扫描所有 spring .factories 文件用于 org.springframework.cloud.client.discovery.EnableDiscoveryClient 键的存在,以及将所有关联的配置加载到主应用程序上下文中。如果我们查看位于 spring-cloud-consul-中的 spring.factories文件在 META-INF/目录下发现 JAR,我们会看到如下入口:

# Discovery Client Configuration 
org.springframework.cloud.client.discovery.EnableDiscoveryClient= 
org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration

这告诉我们,当 discovery 客户端启用时,ConsulDiscoveryClientConfiguration 将被使用,其所有定义的 bean 将被添加到应用程序上下文中。

如果正在使用自定义服务发现 mechanism,则可以使用类似的方法。需要创建一个自定义配置类,公开 DiscoveryClient 接口的自定义实现,并在 spring.factories 文件捆绑在档案中。加载该 JAR 后,如果启用了 discovery 客户端功能,则会自动使用配置。

Note

Spring Cloud Consul 库提供了非常细粒度的功能来配置和挑选所选功能,如果不是全部适用于特定用例的话。有关各种配置和使用选项的详细信息,请参阅 http:// cloud.spring.io/spring-cloud-consul/

Using Spring Cloud Netflix – Feign


在上一个秘籍中, 我们研究了如何启用service 发现我们的应用程序的能力,以便能够向世界注册我们的服务,并了解存在哪些其他服务以及它们的位置。这个秘籍将帮助我们更好地与这些信息交互并使用这些服务,而无需显式地编写任何逻辑来处理服务发现以及随之而来的所有相关问题。

为了实现这个目标,我们将看看另一个 Spring Cloud 集成,它是由 Spring Cloud Netflix 模块系列——Netflix Feign 提供的。 Feign,它使编写 Java HTTP 客户端更容易。其目的是简化将服务 API 调用绑定到相应的 HTTP API 对应项的过程。它提供自动服务映射和发现、将 Java 类型转换为 HTTP 请求 URL 路径、参数和响应有效负载的能力,以及错误处理。

为了简单起见,在这个秘籍中,我们将创建一个 Client 控制器,它将充当我们 BookPub  应用服务,通过Feign注解的Java服务接口调用我们的API,依赖Consul提供服务发现功能。

How to do it...

  1. We will start by adding Netflix Feign module dependencies to our project. Let's modify our build.gradle file located in the root of our project with the following content:
dependencies { 
    ... 
    compile("org.springframework.cloud:spring-
    cloud-starter-consul-all") 
    compile("org.springframework.cloud:spring-
    cloud-starter-openfeign") 
    runtime("com.h2database:h2") 
    ... 
} 
  1. With the dependency added, our next step is to create a Java API interface describing how we want to define our interaction with the BookPub service. Let's create an api package under the src/main/java/com/example/bookpub directory from the root of our project.
  2. Inside the newly-created api package, let's create our API class file named BookPubClient.java with the following content:
package com.example.bookpub.api; 
 
import com.example.bookpub.entity.Book; 
import org.springframework.cloud.netflix.feign.FeignClient; 
import org.springframework.web.bind.annotation.PathVariable; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod; 
 
@FeignClient("http://BookPub-ch9") 
public interface BookPubClient { 
    @RequestMapping(value = "/books/{isbn}",  
                    method = RequestMethod.GET) 
    public Book findBookByIsbn(@PathVariable("isbn") String isbn); 
}
  1. After we have defined the API, it is time to tell our application that we want to enable Feign support. We will do that by making a change to the BookPubApplication.java file located under the src/main/java/com/example/bookpub directory from the root of our project with the following content:
... 
@EnableDiscoveryClient 
@EnableFeignClients 
public class BookPubApplication {...} 
  1. Finally, let's create a client controller to invoke BookPubClient by making a new file named ClientController.java under the src/main/java/com/example/bookpub/controllers directory from the root of our project with the following content:
... 
@RestController 
@RequestMapping("/client") 
public class ClientController { 
 
    @Autowired 
    private BookPubClient client; 
 
    @RequestMapping(value = "/book/{isbn}",  
                    method = RequestMethod.GET) 
    public Book getBook(@PathVariable String isbn) { 
        return client.findBookByIsbn(isbn); 
    } 
} 
  1. With everything set and done, let's start the application by executing the ./gradlew clean bootRun command.

Note

确保 Consul 代理也在后台运行,否则服务注册会失败。

  1. Once the application is up and running, let's open http://localhost:8080/client/book/978-1-78528-415-1 in the browser to see the Consul agent information, as shown in the following screenshot:
    读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》春云
  2. If we look at the application console logs, we will also see entries indicating that our Feign client is initialized and functioning. You should see something similar to this:
2017-12-26 --- c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-BookPub-ch9
2017-12-26 --- c.netflix.loadbalancer.BaseLoadBalancer : Client:BookPub-ch9 instantiated a LoadBalancer:DynamicServerListLoadBalancer:{NFLoadBalancer:name=BookPub-ch9,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2017-12-26 --- c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
 2017-12-26 --- c.netflix.config.ChainedDynamicProperty : Flipping property: BookPub-ch9.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
 2017-12-26 --- c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client BookPub-ch9 initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=BookPub-ch9,current list of Servers=[192.168.1.194:8080],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
 },Server stats: [[Server:192.168.1.194:8080; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Wed Dec 31 18:00:00 CST 1969; First connection made: Wed Dec 31 18:00:00 CST 1969; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
 ]}ServerList:ConsulServerList{serviceId='BookPub-ch9', tag=null}
  1. One last thing that we should do is to get our tests to work with all the newly added frameworks. Because Spring Cloud does not add itself to the test life cycle, we should explicitly disable any reliance on beans created by Spring Cloud libraries during tests. To do so let's add to our application.properties file located under the src/test/resources directory from the root of the project of the following properties:
spring.cloud.bus.enabled=false 
spring.cloud.consul.enabled=false 
spring.cloud.consul.discovery.enabled=false 
eureka.client.enabled=false 
autoconfigure.exclude=com.example.bookpub.
MonitoringConfiguration.class 
  1. We also need to add a Mock dependency on BookPubClient into the JpaAuthorRepositoryTests.java and WebMvcBookControllerTests.java files located under the src/test/java/com/example/bookpub directory from the root of the project with the following content:
@MockBean 
private BookPubClient client; 

How it works...

与我们在上一节中看到的类似,在主应用程序类 @EnableFeignClients 注解>BookPubApplication,明确告诉 Spring Cloud 它应该扫描所有带有 @FeignClient 注释的接口和根据其定义创建服务客户端实现。  @EnableFeignClients 注解本质上类似于 @ComponentScan 一,提供属性来控制扫描哪些包以查找 @FeignClient 注释类或明确列出应该使用的 API 类。

开箱即用,所有 Feign 客户端实现都使用  FeignClientsConfiguration 类中定义的组件进行配置,但可以使用  < @EnableFeignClients 注释的 code class="literal">defaultConfiguration 属性。

简而言之,每一个使用@FeignClient注解的接口定义,都会得到一个由Java动态代理对象组成的服务实现,该对象处理所有接口方法调用(通常使用 FeignInvocationHandler 来处理所有请求)。调用处理程序负责做一些事情。

一旦调用了任何方法,首先使用提供的发现客户端(在我们的例子中是 ConsulDiscoveryClient)基于 @FeignClient 注释的 ="literal">name 属性。在我们的示例中,我们将 name 属性的值声明为 http://BookPub- ch9,因此注册表中所有名称设置为 BookPub-ch9  的服务实例将是作为可能的候选人返回。这个名称可以只是一个服务名称本身,或者,就像我们在示例中所做的那样,可以指定一个可选协议。这是一个有用的功能,因为并非所有服务发现提供者都支持准确指定服务应该如何调用的能力,所以如果我们想使用 HTTPS 进行安全调用,我们可以明确指定协议以帮助 Feign 进行正确的调用。

注解上还有许多其他可用的配置属性,例如,告诉 Feign 直接调用指定的 URL 而不是进行服务查找,有一个 url 可以配置的属性。

Note

要查看可能属性及其用例的完整列表,请转到 https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#spring-cloud-feign .

given 服务的实例列表由另一个 Netflix 库 - Ribbon 提供的内部负载均衡器包装。如果 discovery 客户说他们不健康。

Note

要查看负载平衡规则和其他设置等可能配置选项的完整列表,请转到 https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#spring -cloud-ribbon.

确定特定实例后,将使用标准 Spring 创建一个 HTTP request HttpMessageConverter beans 将方法参数转换为 HTTP 请求路径变量和查询参数。完成所有这些之后,使用配置的 HTTP 客户端发送请求,并且使用相同的转换器将响应转换为在 API 接口上声明的返回类型。

现在我们知道了 @FeignClient 注释的全部内容以及一旦调用 API 定义的方法会发生什么,让我们来看看如何注释应该转换为远程服务调用的接口方法。在 @Controller 中声明控制器映射时,我们可以方便地并且有意这样做,使用与我们已经习惯的完全相同的注解带注释的类。我们想要映射到远程服务的 API 接口中的每个方法都应该使用 @RequestMapping 注释进行注释。 path 属性对应于我们要调用的远程服务的 URL 路径。

在我们的示例中,我们要调用 BookController.getBook(...) 方法,该方法转换为 /books/{isbn} URL 路径。这正是我们为 path 属性设置的值,并确保我们还注释了 isbn 参数在我们的 findBookByIsbn(...) 方法中使用 @PathVariable("isbn") 将其链接到映射模板中的 {isbn} 占位符。

作为一般的经验法则,@RequestMapping 注释的功能与在控制器中使用时完全相同,除了配置与传出有关请求而不是入站请求。在配置注解的consumes属性时可能会特别混乱,即consumes = " application/json",因为它表明它是一个远程端,期望 JSON 作为负载的 content-type

Service discovery using Spring Cloud Netflix – Eureka


我们已经了解了如何使用 HashiCorp Consul 进行服务发现并将其与我们的应用程序集成。这个秘籍将介绍 Netflix-Eureka 中一个非常流行的服务发现框架的替代方案。 Eureka 由 Netflix 开发,旨在帮助解决其在 AWS 中的 RESTful 服务的服务发现、健康检查和负载平衡问题。

与 Consul 不同,Eureka 只专注于 service 发现,并且不提供许多附加功能,例如键/值存储服务或事件传递。然而,它非常擅长它所做的事情,应该被认为是服务发现解决方案的可行候选者。

How to do it...

在我们开始将 Eureka 添加到我们的应用程序之前,我们需要让 Eureka 服务本身启动并运行。值得庆幸的是,Spring Cloud 的开发人员提供了一个示例项目,让创建 Eureka 服务器实例和运行它变得轻而易举。让我们看一下以下步骤:

  1. To get things up and running just go to https://github.com/spring-cloud-samples/eureka and git clone the [email protected]:spring-cloud-samples/eureka.git repository to your machine.
  2. After that's done, run ./gradlew clean bootRun to start the server:
  3. Once the server is up and running, we need to add the following dependencies to the build.gradle file located at the root of our project:
//compile("org.springframework.cloud:spring-cloud-starter-consul-all") 
compile("org.springframework.cloud:spring-cloud-starter-feign") 
compile("org.springframework.cloud:spring-cloud-starter-eureka-client") 
  1. Ironically, that’s all we had to do, at this point, we just restart our application by executing the./gradlew clean bootRuncommand.

Note

确保 Eureka 服务器在后台运行,否则,虽然应用程序将启动,但 BookPubClient 调用将失败。

  1. Once the application is up and running, let's open http://localhost:8080/client/book/978-1-78528-415-1 in the browser and we should see exactly the same response as in our previous recipe. 
  1. Just to see that our application did indeed register with Eureka, we can open the browser at the http://localhost:8761 URL and we should see our service listed under instances list:
读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》春云

How it works...

通过看似毫不费力的更改,我们将一个服务发现提供者 Consul 换成了另一个 Eureka。表面上看起来没有太大变化的东西实际上在引擎盖下做了很多工作。我们能够如此轻松地做到这一点的原因是 spring-cloud-commonsspring-cloud-context 基础库。通过 spring.factores 描述符的自动模块加载支持允许在不同服务发现提供者的初始化中进行透明替换。只要我们在 BookPubApplication 上保留 @EnableDiscoveryClient 注释类,Spring Cloud 完成了繁重的工作,负责加载适当的自动配置文件并设置所有正确的 bean 以使我们的应用程序与 Eureka 一起工作。

我们必须在配方的第一步从类路径中删除 Consul 依赖项,这是为了消除 DiscoveryClient 的歧义执行。如果不这样做,我们的应用程序上下文将最终得到 DiscoveryClient 接口的两种不同实现,这本身不会很糟糕,除了Spring Cloud 必须消除歧义并选择一个,并且可能不会选择我们想要的那个。

如果我们在 build.gradle< 中保留 spring-cloud-starter-consul-all 依赖项/span> 文件,并尝试运行应用程序,它会在启动过程中失败,在日志中我们将看到以下条目:

WARN 5592 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmxMBeanExporter' defined in class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration$ServiceRegistryEndpointConfiguration': Unsatisfied dependency expressed through field 'registration'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.client.serviceregistry.Registration' available: expected single matching bean but found 2: eurekaRegistration,consulRegistration 

从异常中可以看出,Spring 自动装配无法决定应该使用哪一个服务注册表。这是因为 Eureka 和 Consul 都自动创建了 Registration 的实例,而自动装配只需要一个。

由于只有一个注册表是硬性要求,因此最好不要配置多个发现客户端依赖库以避免错误。如果由于某种原因,多个库必须驻留在类路径中,则应该使用配置属性来显式启用/禁用特定的客户端实现。例如,Consul 和 Eureka 都提供了切换状态的配置。我们可以在 spring.cloud.consul.discovery.enabled=trueeureka.client.enabled=false ="literal">application.properties 如果我们更喜欢使用 Consul 来提供服务发现功能。

Using Spring Cloud Netflix – Hystrix


在本章中我们研究了在云环境中成功运行微服务应用程序的所有方面。我们已经了解了如何更好地集成到动态变化的生态系统中、使用远程配置属性、注册服务以及发现和调用其他服务。在这个秘籍中,我们将了解在分布式、高度易变的云环境中操作断路器的另一个非常重要的方面。

我们将要研究的断路器功能的特定实现是 Netflix Hystrix。它提供了一种非常强大和方便的方式来注释我们的服务调用和处理远程服务故障、队列备份、过载、超时等事情。通过在应用程序中设置断路器,如果特定服务端点因请求过载或遇到任何类型的中断,开发人员可以确保整体应用程序的稳定性。

How to do it...

  1. To get started with Hystrix we need to add the spring-cloud-starter-hystrix library to our project. Let's modify our build.gradle file located in the root of our project with the following content:
dependencies { 
    ... 
    compile("org.springframework.cloud:
     spring-cloud-starter-consul-all") 
    compile("org.springframework.cloud:
     spring-cloud-starter-openfeign") 
    compile("org.springframework.cloud:
     spring-cloud-starter-eureka-client") 
    compile("org.springframework.cloud:
     spring-cloud-starter-netflix-hystrix") 
    runtime("com.h2database:h2") 
    runtime("mysql:mysql-connector-java") 
    ... 
} 

 

 

 

 

 

 

  1. After adding the Hystrix dependency, we need to enable Hystrix for our application. Similar to how we enabled service discovery, we will do that by making a change to the BookPubApplication.java file located under the src/main/java/com/example/bookpub directory from the root of our project with the following content:
... 
@EnableDiscoveryClient 
@EnableFeignClients 
@EnableCircuitBreaker 
public class BookPubApplication {...}
  1. Now, let's make a few changes to BookController.java, located under the src/main/java/com/example/bookpub/controllers directory from the root of our project, with the following content:
@RequestMapping(value = "", method = RequestMethod.GET) 
@HystrixCommand(fallbackMethod = "getEmptyBooksList") 
public Iterable<Book> getAllBooks() { 
    //return bookRepository.findAll(); 
    throw new RuntimeException("Books Service Not Available"); 
} 
 
public Iterable<Book> getEmptyBooksList() { 
    return Collections.emptyList(); 
} 
... 
  1. Due to Hystrix internal functionality, we also need to modify our entity models to have them eager-load the relational associations. In the Author.java, Book.java, and Publisher.java files located under the src/main/java/com/example/bookpub/entity directory from the root of our project, let's make the following changes:
  • In Author.java, make the following change:
@OneToMany(mappedBy = "author", fetch = FetchType.EAGER) 
private List<Book> books; 
  • In Book.java, make the following change:
@ManyToOne(fetch = FetchType.EAGER) 
private Author author; 
 
@ManyToOne(fetch = FetchType.EAGER) 
private Publisher publisher; 
 
@ManyToMany(fetch = FetchType.EAGER) 
private List<Reviewer> reviewers; 
  • In Publisher.java, make the following change:
@OneToMany(mappedBy = "publisher", fetch = FetchType.EAGER) 
private List<Book> books; 
  1. Finally, we are ready to restart our application by executing the ./gradlew clean bootRun command.
  1. When the application has started, let's open http://localhost:8080/books in the browser and we should see an empty JSON list as a result:
读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》春云

How it works...

在这个秘籍中,我们在将Hystrix依赖库添加到我们的项目后做了三件事。因此,让我们详细了解每个步骤,以了解究竟发生了什么:

  • The @EnableCircuitBreaker annotation, similar to @EnableDiscoveryClient, or @EnableFeignClients, which explicitly indicates that we want Spring Cloud to load appropriate configurations from spring.factories from all the libraries which have the org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker key defined.

 

  • In the case of Hystrix, it will load HystrixCircuitBreakerConfiguration, which provides the necessary configuration to enable the Hystrix functionality within the application. One of the beans it creates, is the HystrixCommandAspect class. It's purpose is to detect all the methods which are annotated with the @HystrixCommand annotation and wrap them with a handler to detect errors, timeouts, and other ill-behaviors, and deal with them appropriately, based on configuration.
  • This @HystrixCommand annotation, provided by the Hystrix library, is designed to mark methods which represent Hystrix-guarded commands, that is, methods which we want to protect using Hystrix against cascading failures and overloads. This annotation has a number of attributes and can be configured in a variety of different ways, depending on the desired behavior.
  • In our example we have used the most typical attribute—fallbackMethod, which allows us to configure an alternative method, with matching signature, which can be automatically called if the real method fails the invocation for whatever reason. This is the prime use-case, and it provides the ability to specify graceful degradation of service, using sensible defaults, if possible, instead of blowing up exceptions up the stack.
  • We used it to direct failed calls to the getEmptyBooksList() method, which returns a static empty list. This way, when the real getAllBooks() method fails, we gracefully degrade and return an empty collection, which renders nicely as a response JSON. In the situations when we do indeed desire a particular type of exception to be propagated up the stack, we can configure those explicitly using the ignoreExceptions attribute and set it to the desired exception classes.
  • To configure the circuit breaker behavior of a particular command, we can set a number of different options using the commandProperties or threadPoolProperties attributes. There we can set things like execution timeouts, size of backup queues, and many others.

 

 

最后要讨论的一件事是我们对实体模型进行的修改,以设置关系关联注释以使用fetch = FetchType.EAGER。我们必须这样做的原因是由于 Hibernate 处理关联加载的方式。默认情况下,这些是使用 FetchType.LAZY 设置加载的,这意味着 Hibernate 只会建立关系,但数据的加载会在调用 getter 方法之前不会发生。使用 Hystrix,默认情况下,这可能会导致如下所示的错误:

failed to lazily initialize a collection of role: com.example.bookpub.entity.Book.reviewers, could not initialize proxy - no Session (through reference chain: com.example.bookpub.entity.Publisher["books"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.bookpub.entity.Book["reviewers"])

这是由于 Hystrix 默认使用 ThreadPool 来执行方法调用,并且由于延迟加载的数据在调用时需要访问数据存储区,因此 Hibernate 需要一个为了处理请求而存在的活动会话。由于 Hibernate 将会话存储在 ThreadLocal 中,因此它显然不存在于 Hystrix 在调用期间使用的池执行器线程中。

一旦我们将获取更改为急切,所有数据都会在原始 Hibernate 线程中的存储库交互期间加载。或者,我们可以通过以下配置配置我们的 @HystrixCommand 注释以使用相同的执行线程:

commandProperties = { 
  @HystrixProperty(name="execution.isolation.strategy", 
                   value="SEMAPHORE") 
} 

虽然 Hystrix 强烈建议使用默认的 THREAD 策略,但在我们绝对需要驻留在同一个调用者线程中的情况下,SEMAPHORE 可以帮助我们。

或者,我们可以使用 hystrix.command.default 在我们的 application.properties 文件中设置相同的配置.execution.isolation.strategy=SEMAPHORE,或者,如果我们想具体只配置特定的 @HystrixCommand ,我们可以使用 commandKey属性的值,默认为注解方法的名称,而不是default< /span> 部分的属性名称。对于来自 BookController 检测方法的具体示例,配置键看起来像 hystrix.command。 getAllBooks.execution.isolation.strategy=SEMAPHORE.

 

 

这要归功于 Spring Cloud-Netflix Archaius 桥,它使所有 Spring 环境属性对 Archaius 配置管理器可见,因此所有 Netflix 组件都可以访问。

Spring Cloud Hystrix 集成还提供了一个 /hystrix.stream 执行器端点,Hystrix 仪表板可以使用该端点来可视化所有断路器的状态在一个应用程序中。

为了让仪表板快速运行,Spring Cloud 提供了一个示例应用程序,可以在 https://github.com/spring-cloud-samples/hystrix-dashboard:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》春云

相同的流也可以输入 Netflix Turbine Stream Aggregator,可在 https://github.com/Netflix/Turbine,用于跨多个实例的数据聚合,稍后可以使用相同的实例进行可视化仪表板。

Note

还可以使用 spring-cloud-starter-turbine 依赖库和 @EnableTurbine基本 Spring Boot 应用程序上的 注释,类似于 Hystrix 仪表板示例。