《Spring Cloud微服务架构实战》-- 注册中心--Eureka

本书使用的Spring Cloud版本为Dalston.SRl,相关的技术版本如下所示。

> spring-cloud-commons:公共模块,版本为 1.2.2.RELEASE。

> spring-cloud-config:配置管理模块,版本为 1.3.1.RELEASE。

> spring-cloud-netflix: Spring Cloud的核心模块,用于提供服务管理、负载均衡等功 能,版本为 1.3.1.RELEASE。

> spring-cloud-sleuth:服务跟踪模块,版本为 1.2.1.RELEASE。

> spring-cloud-stream:用于构建消息驱动微服务的模块,版本为Chelsea.SR2。

> spring-cloud-bus:消息总线模块,对应版本为1.3.1.RELEASE。

> spring-boot: Spring Cloud 基于 Spring Boot 快速搭建,使用的 Spring Boot 版本为 1.5.3.RELEASEo

3.0 服务与发现中心: Eureka

Spring Cloud 集成了 Netflix OSS 的多个项目,形成了 spring-cloud-netflix 项目。该项目 包含多个子模块,这些子模块对集成的Netflix旗下的框架进行了封装,

本节将讲述其中一 个较为重要的服务管理框架:Eureka

3.1.1 关于 Eureka

Eureka提供基于REST的服务,在集群中主要用于服务管理。Eureka提供了基于Java 语言的客户端组件,客户端组件实现了负载均衡的功能,为业务组件的集群部署创造了条 件。

使用该框架,可以将业务组件注册到Eureka容器中,这些组件可进行集群部署,Eureka 主要维护这些服务的列表并自动检査它们的状态。

3.1.2 Eureka 架构

一个简单的Eureka集群,需要一个Eureka服务器、若干个服务提供者。我们可以将 业务组件注册到Eureka服务器中,其他客户端组件可以向服务器获取服务并且进行远程调 用。图3-1所示为Eureka的架构图。

图3-1中有两个服务器,服务器支持集群部署,每个服务器也可以作为对方服务器的 客户端进行相互注册与复制。图3-1中所示的三个Eureka客户端,两个用于发布服务,另 一个用于调用服务。不管是服务器还是客户端,都可以部署多个实例,如此一来,就很容 易构建高可用的服务集群。

3.1.3服务器端

对于注册到服务器端的服务组件,Eureka服务器并没有提供后台的存储,这些注册的 服务实例被保存在内存的注册中心,它们通过心跳来保持其最新状态,这些操作都可以在 内存中完成。客户端存在着相同的机制,同样在内存中保存了注册表信息,这样的机制提 升了 Eureka组件的性能,每次服务的请求都不必经过服务器端的注册中心。

3.1.4服务提供者

  作为Eureka客户端存在的服务提供者,主要进行以下工作:第一,向服务器注册服务; 第二,发送心跳给服务器;第三,向服务器端获取注册列表。当客户端注册到服务器时, 它将会提供一些关于自己的信息给服务器端,例如自己的主机、端口、健康检测连接等。

3.1.5服务调用者

  对于发布到Eureka服务器的服务,服务调用者可对其进行服务查找与调用,服务调用 者也是作为客户端存在的,但其职责主要是发现与调用服务。在实际情况中,有可能出现 本身既是服务提供者,又是服务调用者的情况,例如在传统的企业应用三层架构中,服务 层会调用数据访问层的接口进行数据操作,它本身也会提供服务给控制层使用。

本节对Eureka进行了介绍,读者大概了解Eureka架构及各个角色的作用即可;

3.2 第一个Eureka应用

 3.2.1构建服务器

先创建一个名称为first-ek-server的Maven项目作为服务器,在pom.xml文件中加入

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<dependencyManagement>

  <dependencies>

    <dependency>

      <groupld>org.springframework.cloud</groupld>

      <artifactld>spring-cloud-dependencies</artifactld>

      <version>Dalston.SRl</version>

      <type>pom</type>

      <scope>import</scope>

    </dependency>

  </dependencies>

</dependencyManagement>

<dependencies>

  <dependency>

    <groupld>org.springframework.cloud</groupld>

    <artifactld>spring-cloud-starter-eureka-server</artifactld><br>  </dependency>

</dependencies>

  加入的 spring-cloud-starter-eureka-server 会自动引入 spring-boot-starter-web因此只需 加入该依赖,我们的项目就具有Web容器的功能了。

接下来,编写一个最简单的启动类, 启动我们的Eureka服务器,启动类如代码清单:

1

2

3

4

5

6

@SpringBootApplication

@EnableEurekaServer

public class FirstServer {

  public static void main(String[] arg§) (

    new SpringApplicationBuilder(FirstServer.class).run(args);

  }<br>}

  

启动类几乎与前面章节中介绍的Spring Boot项目一致,只是加入了@EnableEurekaServer, 声明这是一个Eureka服务器。直接运行FirstServer即可启动Eureka服务器需要注意的是,

本例中并没有配置服务器端口,因此默认端口为8080,我们将端口配置为8761;

在 src/main/resources目录下创建application.yml配置文件,内容如下:

1

2

server:

    port: 8761

 

运行FirstServer的main方法后,可以看到控制台的 输出如下:

1

2

3

4

5

6

2017-08-04 15:35:58.900 INFO 4028 —— [  main]

s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8761 <br>2017-08-04 15:35:58.901 INFO 4028 —— [   main]

.s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761

2017-08-04 15:35:58.906 INFO 4028 —— [  main]

org.crazyit.cloud.FirstServer   : Started FirstServer in 12.361 seconds JVM running for 12.891

2017-08-04 15:35:59.488 INFO 4028 —— [nio-8761-exec-l] <br>o.a.c.c.C.[Tomcat] . [localhost].[/] : Initializing Spring FrameworkServlet * dispatcherServlet *

  

成功启动后,打开浏览器,输入http://localhost:8761,可以看到Eureka 服务器控制台,如图3-2所示。

 

   在图3-2的下方,可以看到服务的实例列表,目前我们并没有注册服务,因此列表 为空。

3.2.2服务器注册开关

在启动Eureka服务器时,会在控制台看到以下两个异常信息:

1

java.net.ConnectException: Connection refused: <br>connect com.netflix.discovery.shared.transport.TransportException: <br>Cannot execute request on any known server

  

这是由于在服务器启动时,服务器会把自己当作一个客户端,去注册Eureka服务器, 并且会到Eureka服务器抓取注册信息,它自己本身只是一个服务器,而不是服务的提供者 (客户端),

因此可以修改application.yml文件,修改以下两个配置:

1

2

3

4

eureka:

  client:

    registerWithEureka: false

    fetchRegistry: false

  

以上配置中的eureka.client.registerWithEureka属性,声明是否将自己的信息注册到 Eureka服务器,默认值为true。属性eureka.client.fetchRegistry则表示,是否到Eureka服务 器中抓取注册信息。

将这两个属性设置为false,启动时不会出现异常信息。

3.2.3编写服务提供者

在前面搭建环境章节,我们使用Spring Boot来建立一个简单的Web工程,并且在里 面编写了一个REST服务,本例中的服务提供者,与该案例类似。

建立名称为 first-ek-service-provider的项目在pom.xml中加入依赖,如下所示:

1

2

3

4

5

6

7

<dependency>

  <groupld>org.springframework.cloud</groupld><br>  <artifactld>spring-cloud-starter-config</artifactld>

</dependency>

<dependency>

  <groupld>org.springframework.cloud</groupld>

  <artifactld>spring-cloud-starter-eureka</artifactld>

</dependency>

  

在src/main/resources目录中建立application.yml配置文件,文件内容如代码清单:

1

2

3

4

5

6

7

8

9

spring:

  application:

    name: first-service-provider

eureka:

  instance:

    hostname: localhost

client:

  serviceUrl:

    defaultzone: http://localhost:8761/eureka/

  

以上配置中,将应用名称配置为first-service-provider,该服务将会被注册到端口为8761 的Eureka服务器,也就是本节前面所构建的服务器。

另外,还使用了 eureka.instance.hostname 来配置该服务实例的主机名称。编写一个Controller类,并提供一个最简单的REST服务, 如代码所示:

1

2

3

4

5

6

@RestController

public class FirstController {<br>  @RequestMapping(value = "/person/{personld}", method = RequestMethod.GET,<br>                               produces = MediaType.APPLICATION_JSON_VALUE)

  public Person findPerson(@PathVariable("personld") Integer personld) {<br>    Person person = new Person(personld, "Crazyit"30);

    return person;

  }

}

  

编写启动类:

1

2

3

4

5

6

7

@SpringBootApplication

@EnableEurekaClient

public class FirstServiceprovider {

  public static void main(String[] args) (

    new SpringApplicationBuilder(FirstServiceProvider.class).run(args);

  }

}

  

在启动类中,使用@EnableEurekaClient注解,声明该应用是一个Eureka客户端。

配置完成后,运行服务器项目first-ek-server的启动类FirstServer,再运行代码清单3-5中的 FirstServiceProvider,

在浏览器中访问 Eureka: http://localhost:8761/,可以看到服务列表如图3-3所示:

   如图3-3所示,可以看到当前注册的服务列表,只有我们编写的first-service-provider。 服务注册成功后,接下来编写服务调用者。

3.2.4编写服务调用者

服务被注册、发布到Eureka服务器后,需要有程序去发现它,并且进行调用。此处所 说的调用者,是指同样注册到Eureka的客户端,来调用其他客户端发布的服务。简单地说, 就是Eureka内部调用。

同一个服务可能会部署多个实例,调用过程可能涉及负载均衡、服务器查找等问题,Netflix的项目已经帮我们解决,并且Spring Cloud已经封装了一次,我 们仅需编写少量代码就可以实现服务调用。

新建名称为first-ek-service-invoker的项目在pom.xml文件中加入依赖,如代码清单:

1

2

3

4

5

6

7

8

9

10

<dependency>

  <groupld>org.springframework.cloud</groupld><br>  <artifactld>spring-cloud-starter-config</artifactld>

</dependency>

<dependency>

  <groupld>org.springframework.cloud</groupld><br>  <artifactld>spring-cloud-starter-eureka</artifactld>

</dependency>

<dependency>

  <groupld>org.springframework.cloud</groupld>

  <artifactId>spring-cloud-starter-ribbon</artifactId>

</dependency>

  

建立配置文件application.yml,内容如代码清单:

1

2

3

4

5

6

7

8

9

10

11

server:

  port: 9000

spring:

  application:

    name: first-service-invoker

eureka:

  instance:

    hostname: localhost

client: 

  serviceUrl:

    defaultzone: http://localhost:8761/eureka/

  

在配置文件中,配置了应用名称为first-service-invoker,这个调用者的访问端口为9000, 需要注意的是,这个调用本身也可以对外提供服务。与提供者一样,使用eureka的配置, 将调用者注册到first-ek-server上面。

下面编写一个控制器,让调用者对外提供一个测试的 服务,代码清单3-8为控制器的代码实现。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@RestController

@Configuration

public class InvokerController {<br>  @Bean

  @LoadBalanced

  public RestTemplate getRestTemplate() (

    return new RestTemplate();

  }<br>

  @RequestMapping(value = "/router", method = RequestMethod.GET,<br>                        produces = MediaType.APPLICATION_JSON_VALUE) <br>  public String router() {

    RestTemplate restTemplate = getRestTemplate();

    //根据应用名称调用服务

    String json = restTemplate.getForObject(

        "http://first-service-provider/person/l", String.class); <br>    return json;

  }

}

  

在控制器中,配置了 RestTemplate的Bean, RestTemplate本来是spring-web模块下面 的类,主要用来调用REST服务。本身并不具备调用分布式服务的能力,但是RestTemplate 的Bean被@LoadBalanced注解修饰后,

这个RestTemplate实例就具有访问分布式服务的能 力了。关于该类的一些机制,我们将放到“负载均衡”章节中讲解。

在控制器中,新建了一个router的测试方法,用来对外发布REST服务。该方法只起 路由作用,实际上是使用RestTemplate来调用first-ek-service-provider (服务提供者)的服 务。

需要注意的是,调用服务时,仅仅通过服务名称进行调用。接下来编写启动类,如代 码清单3-9所示

1

2

3

4

5

6

7

@SpringBootApplication

@EnableDiscoveryClient

public class Firstlnvoker {

  public static void main(String[] args) {

    SpringApplication.run(Firstlnvoker.class, args);

  }

}

 在启动类中,使用了@EnableDiscoveryClient注解来修改启动类,该注解使得服务调用 者有能力去Eureka中发现服务。需要注意的是,@EnableEurekaClient注解巳经包含了 @EnableDiscoveryClient的功能,

也就是说,一个Eureka客户端,本身就具有发现服务的 能力。配置完成后,依次执行以下操作:

  1. 启动服务器(first-ek-server)。
  2.  启动服务提供者(first-ek-service-provider)
  3. 启动服务调用者(first-ek-service-invoker)

使用浏览器访问Eureka,可看到注册的客户信息,如图3-4所示。

全部成功启动后,在浏览器中访问服务调用者发布的router服务:http://localhost:9000/ router,可以看到在浏览器中输出如下:

1

{"id":1,"name":"Crazyit","age"30}

  根据输出可知,实际上调用了服务提供者的/person/1服务,第一个Eureka应用到此结 束,下面对这个应用程序的结构进行简单描述;

3.2.5程序结构

本案例新建了三个项目,如果读者对程序的结构不太清晰,可以参看图

   如图3-5所示,Eureka服务为本例的first-ek-server,服务提供者为first-ek-service- provider,而调用者为first-ek-service-invoker,用户通过浏览器访问调用者的9000端口的 router服务、router服务中查找服务提供者的服务并进行调用。

在本例中,服务调用有点像 路由器的角色。

为了能演示Eureka的高可用特性,下一节将会以本案例为基础,搭建一个复杂一点的 集群

3.3  Eureka集群搭建

在运行第一个Eureka应用时,服务器实例、服务提供者实例都只启动了一个,并没有 体现高可用的特性,本节将对前面的Eureka应用进行改造,使其可以进行集群部署。

3.3.1本例集群结构图

本例将会运行两个服务器实例、两个服务提供者实例,然后服务调用者请求服务,集 群结构如图3-6所示。

第一个Eureka应用,使用的是浏览器访问Eureka的服务调用者,而改造后,为了能 看到负载均衡的效果,会编写一个HttpClient的REST客户端访问服务调用者发布的服务。

由于本书的开发环境只有一台电脑,操作系统为Windows,如果要构建集群,需要修 改hosts文件,为其添加主机名的映射。修改C:\Windows\System32\drivers\etc\hosts文件, 添加以下内容:

3.3.2改造服务器端

新建项目first-cloud-server,使用的Maven配置与3.2节中介绍的服务器一致。由于需 要对同一个应用程序启动两次,因此需要在配置文件中使用profiles (关于profiles己经在 第2章中讲述过)。服务器配置文件请见代码清单

1

2

3

4

5

6

7

8

9

10

11

12

server:

  port: 8761

spring:

  application:

    name: first-cloud-server

  profiles: slavel

eureka:

  instance:

    hostname: slavel

  client:

    serviceUrl:

      defaultzone: http://slave2:8762/eureka/

  

1

2

3

4

5

6

7

8

9

10

11

12

server:

  port: 8762

spring:

  application:

    name: first-cloud-server

  profiles: slave2

eureka:

  instance:

    hostname: slave2

  client:

    serviceUrl:

      defaultzone: http://slavel:8761/eureka/

  

代码清单3.10中配置了两个profiles,名称分别为slavel和slave2。在slavel中,配置 了应用端口为8761,主机名为slavel。当使用salve 1这个profiles来启动服务器时,将会 向 http://slave2:8762/eureka/^册自己。使用 salve2 来启动服务器,会向http://slavel:8761/ eureka/注册自己。简单点说,就是两个服务器启动后,它们会互相注册。

修改启动类,让类在启动时读取控制台的输入,决定使用哪个profiles来启动服务器, 请见代码清单

1

2

3

4

5

6

7

@SpringBootApplication

@EnableEurekaServer

public class FirstServer {

  public static void main(String[] args) ( <br>    //读取控制台输入,决定使用哪个<br>    Profiles Scanner scan = new Scanner(System.in); <br>    String profiles = scan.nextLine();

    new SpringApplicationBuilder(FirstServer.class).profiles(profiles).run(args);

  }

}

 在启动类中,先读取控制的输入,再调用profiles方法设置启动的profileso需要注意 的是,第一个启动的服务器会抛出异常,异常原因我们在前面已经经讲述过,抛出的异常不必理会。

3.3.3改造服务提供者

  服务提供者也需要启动两个实例,服务提供者的改造与服务端类似,将3.2节中的 first-ek-service-provider复制出来,并改名为first-cknid-provider。

修改配置文件,将服务提 供者注册到两个服务器中,配置文件请见代码清单

1

2

3

4

5

6

7

8

9

spring:

  application:

    name: first-cloud-provider

eureka:

  instance:

    hostname: localhost

client:

  serviceUrl:

    defaultzone : http://localhost:8761/eureka/,http://localhost:8762/eureka/

  

再修改启动类,为了避免端口冲突,启动时读取控制台输出,决定使用哪个端口来启 动,启动类如代码:

1

2

3

4

//读取控制台输入的端口,避免端口冲突

Scanner scan = new Scanner(System.in);

String port = scan.nextLine();

new SpringApplicationBuilder(FirstServiceProvider.class).properties( "server.port=" + port).run(args);

  

启动类中使用了 properties方法来设置启动端口。为了能看到效果,还需要改造控制器, 将服务调用者请求的URL保存起来并返回,修改后的控制器请见代码

1

2

3

4

5

6

7

8

9

@RestController

public class FirstController {<br>  @RequestMapping(value = "/person/(personld}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)

  public Person findPerson (@Pathvariable("personId") Integer personld, <br>                            HttpServletRequest request) {

    Person person = new Person(personld, "Crazyit"30);

    //为了査看结果,将请求的URL设置到Person实例中

    person.setMessage(request.getRequestURL().toString());

    return person;

  }

}

  控制器的findPerson方法将请求的URL保存到Person实例的message属性中,调用服务后,可以通过message属性来查看请求的URL。

3.3.4 改造服务调用者

将3.2节中的first-ek-service-invoker复制并改名为first-cloud-invoker;本例中的服务调 用者只需启动一个实例,因此修改配置文件即可使用,请见代码:

1

2

3

4

5

6

7

8

9

10

11

12

server:

  port: 9000

spring:

  application:

    name: first-cloud-invoker

eureka:

  instance:

    hostname: localhost

client:

  serviceUrl:

    defaultzone: http://slavel:8761/eureka/,http://slavel2:8762/eureka/

  修改的配置将服务调用注册到两个服务器上。

3.3.5 编写REST客户端进行测试

本例使用的是HttpClient, HttpClient是Apache提供的一个HTTP工具包。

新建名称为 first-cloud-rest-client的项目,在pom.xml中加入以下依赖:

1

2

3

4

5

<dependency>

  <groupld>org.apache.httpcomponents</groupld>

  <artifactld>httpclient</artifactld>

  <version>4.5.2</version>

</dependency>

  

新建启动类,在main方法中编写调用REST服务的代码,如代码:

1

2

3

4

5

6

7

8

9

//创建默认的HttpClient

CloseableHttpClient httpclient = HttpClients.createDefault ();

//调用6次服务并输出结果

for(int i = 0; i < 6; i++) (

//调用GET方法请求服务

HttpGet httpget = new HttpGet("http://localhost:9000/router"); //获取响应

HttpResponse response = httpclient.execute(httpget);

//根据响应解析出字符串

System.out.printin(EntityUtils.toString(response.getEntity()));

  

在main方法中,调用了 6次9000端口的router服务并输出结果。完成编写后,按以下顺序启动各个组件:

① 启动两个服务器端,在控制台分别输入slave1和slave2

② 启动两个服务提供者,在控制台分别输入8081与8082

③ 启动服务调用者。

启动了整个集群后,运行TestHttpClient,可以看到输出如下:

1

2

3

4

5

{"id":1,"name":"Crazyit","age”:30,"message":"http://localhost:8081/person/l"} <br>{"id":1,"name":"Crazyit","age":30,"message":"http://localhost:8082/person/1"}

{"id":1,"name":"Crazyit""age":30,"message":"http://localhost:8081/person/l"}

("id":1"name":"Crazyit""age" 30"message":"http://localhost: 8082/person/l"}

{"id":1"name":"Crazyit""age" 30"message":"http://localhost: 8081/person/l"}

("id":1"name":"Crazyit""age"30,"message":"http://localhost:8082/person/l"}

  根据输出结果可知,8081与8082端口分别被请求了 3次,可见已经达到负载均衡的 目的

3.4 服务实例的健康自检

在默认情况下,Eureka的客户端每隔30秒会发送一次心跳给服务器端,告知它仍然存 活。但是,在实际环境中有可能出现这种情况,客户端表面上可以正常发送心跳,

但实际 上服务是不可用的。

例如一个需要访问数据的服务提供者,表面上可以正常响应,但是数据库已经无法访问;又如,服务提供者需要访问第三方的服务,而这些服务早已失效。

对于这些情况,应当告诉服务器当前客户的状态,调用者或者其他客户端无法获取这些有问题的实例。实现 该功能,可以使用Eureka的健康检查控制器。

3.4.1 程序结构

将3.2节的服务器、服务提供者和服务调用者进行复制,命名如下。

> health-handler-server:本例的 Eureka 服务器。

> health-handler-provider:本例的服务提供者客户端。

> health-handler-invoker:本例的服务调用者客户端。

假设在实际环境中,服务提供者模块需要访问数据库,本例的health-handler-provider 模块,将是进行健康自检的模块。

3.4.2 使用 Spring Boot Actuator

Spring Boot Actuator模块主要用于系统监控,当应用程序整合了 Actuatei•后,它就会 自动提供多个服务端点,这些端点可以让外部看到应用程序的健康情况。

在本例中使用的 是/health端点。修改health-handler-provider的pom.xml文件,加入以下依赖:

1

2

3

4

5

<dependency>

  <groupld>org.springframework.boot</groupld>

  <artifactld>spring-boot-starter-actuator</artifactld>

  <version>1.5.3.RELEASE</version>

</dependency>

 

加入依赖后,先启动health-handler-server,再启动health-handler-providero这两个模块 与3.2节中介绍的基本类似,仅仅修改了部分类名。

启动完成后,在浏览器中访问 http://localhost:8080/health,可以看到输出如下:

1

"description" "Spring Cloud Eureka Discovery Client'*, "status'*: "UP" }

  该REST服务向外展示当前应用的状态为“UP”。

3.4.3实现应用健康自检

如果一个客户端本身没有问题,但是该模块所依赖的服务无法使用,那么对于服务器 以及其他客户端来说,该客户端也是不可用的,最常见的就是访问数据库的模块。我们需 要做两件事:第一,让客户端自己进行检查,是否能连接数据库;第二,将连接数据库的 结果与客户端的状态进行关联,并且将状态告诉服务器。

我们使用Spring Boot Actuator可以直接实现一个自定义的Healthindicator,根据是否能访问数据库,来决定应用自身的健康。代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

@Component

public class MyHealthlndicator implements Healthindicator {

<br>  @Override

  public Health health() {<br>    if (Healthcontroller.canVisitDb) {

      //成功连接数据库,返回UP

      return new Health.Builder(Status.UP).build();

    } else (

      //连接数据库失败,返回out of service

      return new Health.Builder(Status.DOWN).build();

    }

  }

}

  

为了简单起见,使用Healthcontroller类的canVisitDb变量来模拟是否能连接上数据库, 该变量的相关代码请见代码:

1

2

3

4

5

6

7

8

@RestController

public class Healthcontroller {

<br>  //标识当前数据库是否可以访问

  static Boolean canVisitDb = false;<br>

  @RequestMapping (value = "/db/{canVisitDb}", method = RequestMethod.GET) <br>  public String setConnectState (@Pathvariable ("canVisitDb") Boolean canVisitDb) { <br>    this.canVisitDb = canVisitDb;

    return ”当前数据库是否正常:"+ this .canVisitDb;

  }

}

控制器中的canVisitDb变量,可以通过/db/false或者/db/true两个地址来修改,配合健 康指示器一起使用。如果该值为true,健康指示器将会返回“UP”状态,反之则返回“DOWN” 状态。

修改完后,启动服务器(health-handler-server)以及服务提供者(health-handler-provider ), 访问http://localhost:8080/health可以看到服务提供者的健康状态,

默认状态为DOWN,因 此控制器中的canVisitDb默认值为false;

如果想让应用的健康状态变为UP,则访问 http://localhost:8080/db/true 即可。

接下来,如果服务提供者想把健康状态告诉服务器,还需要实现“健康检查处理器”。 处理器会将应用的健康状态保存到内存中,状态一旦发生改变,就会重新向服务器进行注 册,

其他的客户端将拿不到这些不可用的实例。代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@Component

public class MyHealthCheckHandler implements HealthCheckHandler {

<br>  @Autowired

  private MyHealthlndicator indicator;<br>

  public Instancestatus getStatus(Instancestatus currentStatus) {

    Status s = indicator.health().getStatus ();

    if(s.equals(Status.UP)) (

      System.out.printin("数据库正常连接");

      return Instancestatus.UP;

    } else {

      System.out.printin("数据库无法连接");

      return Instancestatus.DOWN;

    }

  }

}

  

在自定义的健康检查处理器中,注入了前面编写的健康指示器,根据健康指示器的结 果来返回不同的状态。Eureka中会启动一个定时器,定时刷新本地实例的信息,

并且执行 “处理器”中的getStatus方法,再将服务实例的状态“更新”到服务器中。执行以上逻辑 的定时器,默认30秒执行一次,如果想加快看到效果,

可以修改eureka.client.instancelnfo- ReplicationlntervalSeconds配置。

代码清单为服务器提供者的配置文件:

1

2

3

4

5

6

7

8

9

10

spring:

  application:

    name: health-handler-provider

eureka:

  instance:

    hostname: localhost

client:

  instancelnfoReplicationlntervalSeconds: 10

  serviceUrl:

    defaultzone: http://localhost:8761/eureka/

  

启动服务器,再启动服务提供者,在浏览器中访问http://localhost:8761,可看到服务提 供者的状态为DOWN,访问http://localhost:8080/db/true,将数据库设置为“可以连接”,

再访问8761端口,可以看到服务提供者状态已经改变为UP。

3.4.4服务查询

在前一节中,通过浏览器访问Eureka界面可査看服务状态的改变。本例通过修改服务 调用者的代码来查看应用健康自检的效果。修改服务调用者(health-handler-invoker模块),

控制器修改后如代码所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

@RestController

@Configuration

public class InvokerController {

<br>  @Autowired

  private DiscoveryClient discoveryclient;<br>

  @RequestMapping(value = "/router", method = RequestMethod.GET) <br>  public String router() {

  <br>    //查找服务列表

    List<ServiceInstance> ins = getServicelnstances();

    //输出服务信息及状态

    for (ServiceInstance service : ins) {

      EurekaServicelnstance esi = (EurekaServiceInstance) service; <br>      Instanceinfo info = esi.getInstanceInfo();

      System.out.printin(info.getAppName() + "---" + info.getInstanceId()+"---"+ info.getStatus());

    }

    return "";

  }<br>

  /**

   * 査询可用服务

   */

  private List<ServiceInstance> getServicelnstances () (

    List<String> ids = discoveryclient.getServices(); <br>    List<ServiceInstance> result = new ArrayList<ServiceInstance> (); <br>    for (String id : ids) {

      List<ServiceInstance> ins = discoveryclient.getlnstances(id); <br>      result.addAll(ins);

    }

    return result;

  }

}

  

在客户端中,如果需要查询集群中的服务,可以使用Spring Cloud的discoveryClient 类,或者Eureka的eurekaClient类,Spring Cloud对Eureka进行了封装。

本例中调用了 discoveryClient的方法来查询服务实例(如代码清单3-21中的粗体代码)。在控制器的router 方法中,仅将查询到的服务实例进行输出。

修改完后,依次进行以下操作:

  1. 运行服务器。
  2. 服务提供者。
  3. 服务调用者。
  4. 在浏览器中输入http://localhost:8080/db/true,将数据库设置为可以连接。
  5. 在浏览器中输入http://localhost:9000/router,控制台输出如下:

1

2

•HEALTH-HANDLER-PROVIDER—AY-PC:health-handler-provider—UP

•HEALTH-HANDLER-INVOKER—A Y-PC:health-handler-invoker:9000—UP

  6. 在浏览器中输入http://localhost:8080/db/false,将数据库设置为不可连接。

  7. 在浏览器中输入http://localhost:9000/router,控制台输出如下:

1

• HEALTH-HANDLER-INVOKER—A Y-PC:health-handler-invoker:9000—UP

  根据输出结果可知,将数据库设置为不可连接后,可用的服务只剩下调用者自己,服 务提供者已经不存在于服务列表中。

运行案例需要注意,默认情况下,客户端到服务器端抓取注册表会有一定的时间间隔, 因此在设置数据库是否可以连接后,访问''调用者”查看效果时需要稍等一会儿;

3.5 Eureka的常用配置

本节将讲述部分Eureka的常用配置。

3.5.1心跳检测配置

  客户端的实例会向服务器发送周期性的心跳,默认是30秒发送一次,可以通过修改客 户端的eureka.instance.leaseRenewallntervallnSeconds 属性来改变这个时间。

服务器端接收心跳请求,如果在一定期限内没有接收到服务实例的心跳,那么会将该 实例从注册表中清理掉,其他的客户端将会无法访问这个实例。

这个期限默认值为90秒, 可以通过修改客户端的eureka.instance.leaseExpirationDurationlnSeconds属性来改变这个值。

也就是说,服务器90秒没有收到客户端的心跳,就会将这个实例从列表中清理掉。但需要 注意的是,清理注册表有一个定时器在执行,默认是60秒执行一次,如果将 leaseExpirationDurationlnSeconds设置为小于60秒,

虽然符合删除实例的条件,但是还没 到60秒,这个实例将仍然存在注册表中(因为还没有执行清理)。

我们可以在服务器端配 置eureka.server.eviction-interval-timer-in-ms属性来修改注册表的清理间隔,该属性的单位是毫秒。

需要特别注意,如果开启了自我保护模式,则实例不会被剔除。在测试时,为避免受 自我保护模式的影响,建议先关闭自我保护模式,在服务器中配置:

1

eureka.server.enable-self- preservation=false

  

3.5.2注册表抓取间隔

在默认情况下,客户端每隔30秒去服务器端抓取注册表(可用的服务列表),并且将 服务器端的注册表保存到本地缓存中。

可以通过修改eureka.client.registryFetch- IntervalSeconds配置来改变注册表抓取间隔,但仍然需要考虑性能,改为哪个值比较合适,

需要在性能与实时性方面进行权衡。

3.5.3配置与使用元数据

框架自带的元数据,包括实例id、主机名称、ip地址等,如果需要自定义元数据并提 供给其他客户端使用,可以配置eureka.instance.metadata-map属性来指定。

元数据都会保存 在服务器的注册表中,并且使用简单的方式与客户端进行共享。在正常情况下,自定义元 数据不会改变客户端的行为,除非客户端知道这些元数据的含义,

以下配置片断使用了元数据。

1

2

3

4

5

eureka:

  instance:

    hostname: localhost

    metadata-map:

      company-name: crazyit

  

配置了一个名为company-name的元数据,值为crazyit,使用元数据的一方,可以调用 discoveryClient的方法获取元数据,如以下代码所示:

1

2

3

4

5

6

7

8

9

10

11

12

@Autowired

private DiscoveryClient discoveryClient;<br>

@RequestMapping(value = "/router", method = RequestMethod.GET)

public String router() {

  //查询服务实例

  List<ServiceInstance> ins = discoveryClient.getlnstances ("heart-beat-client");

  //遍历实例并输岀元数据值

  for(Serviceinstance service : ins) {

    System.out.printIn(service.getMetadata().get("company-name"));

  }

  return "";

}

  

3.5.4自我保护模式

  在开发过程中,经常可以在Eureka的主界面中看到红色字体的提醒,内容如下:

  EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE

NOT BEING EXPIRED JUST TO BE SAFE.

  出现该提示意味着Eureka进入了自我保护模式。根据前面章节的介绍可知,客户端会定时发送心跳给服务器端,如果心跳的失败率超过一定比例,服务会将这些实例保护起来,

并不会马上将其从注册表中剔除此时对于另外的客户端来说,有可能会拿到一些无法使 用的实例,这种情况可能会导致灾难的 "蔓延",这些情况可以使用容错机制予以解决,

关于集群的容错机制,将在后面的章节中讲述;

  在开发过程中,为服务器配置 eureka.server.enable-self-preservation属性,将值设置为false来关闭自我保护机制。

关闭后 再打开Eureka主界面,可以看到以下提示信息:

  THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

自我保护模式己经关闭,在出现网络或者其他问题时,将不会保护过期的实例。

本章讲述了 Eureka框架,读者学习完本章后,可以掌握Eureka的架构、如何使用Eureka 搭建集群等内容,最基本的是,能对Spring Cloud等技术有一个初步认识。

如果想更进一步,可以学习编写服务实例的自检程序。实际环境中的服务不可避免地依赖第三方环境,例如数据库、第三方服务等,因此, 如果掌握了对程序的自检,可以使得我们的应用程序更加健壮。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

time Friend

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值