Spring Cloud之Eureka客户端健康检测(五)

在前面的博客中,我们知道一个最简单的Eureka微服务架构,也要由3个项目组成,当项目增多的时候,Eureka是怎么维护服务的呢?如何确保其中一个服务实例不能使用了,将它排除出去呢?

由于整个演示过程还是Eureka的内容,我们首先从Spring Cloud服务管理框架Eureka简单示例(三)文章底部的源码链接拿到我们的示例代码。

客户端心跳推送与检测

Eureka分为服务器端和客户端,客户端每隔一段时间就会向服务器端发送一次讯息,向服务器说明自己还正常,让服务器端继续维护自己的服务,不要从服务列表里面把自己给剔除了。同时,设置一个让服务器端等待自己的时间,当自己的服务实例没有继续为服务器端发送心跳后,也就是从最后一次发送心跳开始计时,等待一段时间,依然没有收到讯息,服务器端就会把这个服务实例从服务列表里面移除,不再让流量涌入这个服务实例。

eureka.instance.lease-renewal-interval-in-seconds

表明客户端需要将心跳发送到服务器端,以表明它还活着。如果心跳停止的时间超过了服务器设置的等待时间,那么服务器端将会从它的服务列表中删除该实例,从而将流量排除在该实例之外。默认30s

eureka.instance.lease-expiration-duration-in-seconds

服务器端等待的时间,因为它收到了最后的心跳,然后才可以从它的视图中删除这个实例,并且不允许流量进入这个实例。将这个值设置得太长可能意味着,即使实例不存在,流量也可以被路由到实例。将这个值设置得太小可能意味着,由于临时网络故障,该实例可能会被排除在流量之外。这个值的设置至少要高于leaseRenewalIntervalInSeconds中指定的值。默认90s

我们通过修改这些配置,查看运行结果,去直观地理解这些配置。首先运行euraka-server项目com.init.springCloud包下面的ServerApp类main()方法,启动Eureka服务器端。然后,修改eureka-provider项目pom.xml文件,把leaseRenewalIntervalInSeconds的时间改成5s,为了能够看到项目发送的心跳讯息,我们把Eureka的log日志打开,在控制台看输出结果。pom.xml配置如下:

spring:
  application:
    name: eureka-provider

eureka:
  instance:
    leaseRenewalIntervalInSeconds: 5
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

logging:
  level:
    com.netflix: DEBUG

然后运行eureka-provider项目ProviderApp类的main()方法,项目启动成功之后,我们就能够在控制台看到打印出来的日志信息。我这里截取一段5s内的日志,为了方便观察,去掉了最前面的时间:

[{}->http://localhost:8761] total kept alive: 1, total issued: 0, total allocated: 1 out of 200
Getting free connection [{}->http://localhost:8761][null]
Released connection is reusable.
Releasing connection [{}->http://localhost:8761][null]
Pooling connection [{}->http://localhost:8761][null]; keep alive indefinitely
Notifying no-one, there are no waiting threads
Jersey HTTP PUT http://localhost:8761/eureka//apps/EUREKA-PROVIDER/DESKTOP-E3UNJK3:eureka-provider; statusCode=200
DiscoveryClient_EUREKA-PROVIDER/DESKTOP-E3UNJK3:eureka-provider - Heartbeat status: 200

浏览器访问http://localhost:8761,在Eureka的控制台也能够看见注册到服务器端的项目。


继续在eureka-provider项目的pom.xml文件中添加leaseExpirationDurationInSeconds,跟leaseRenewalIntervalInSeconds并排,时间设置为10s,前面的介绍中说明了这个时间是要比leaseRenewalIntervalInSeconds大的。修改后的pom.xml文件为:

spring:
  application:
    name: eureka-provider

eureka:
  instance:
    leaseRenewalIntervalInSeconds: 5
    leaseExpirationDurationInSeconds: 10
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

logging:
  level:
    com.netflix: DEBUG

由于服务器端会运行一个保护机制,虽然我们在客户端设置了10s之后就清除自己的实例,但是服务器端默认会在60s之后才去清理服务列表,并移除已经失效的服务实例。我们可以在eureka-server项目的pom.xml新增配置,将服务器的自我保护机制关闭,并把自动清理的时间设置短一些。

enable-self-preservation 自我保护机制,默认开启

eviction-interval-timer-in-ms 服务器清理服务列表的定时器,默认60s,注意时间是毫秒

server:
  port: 8761
  
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 10000

然后重新启动eureka-provider和eureka-server项目,访问http://localhost:8761,确定eureka-provider项目已经注册到eureka-server上面,之后,停止eureka-provider项目,等待大概10s以后,重新访问http://localhost:8761,就会看到eureka-server已经不再维护eureka-provider的服务了。

客户端服务抓取间隔

服务器端在更新了服务列表之后,客户端为了获得最新的服务列表,需要从服务器端主动抓取服务列表。

eureka.client.registry-fetch-interval-seconds 

表示从“发现”服务器获取注册表信息的频率(以秒为单位)。

在eureka-consumer项目的ConsumerController类里添加一个方法,用于展示当前自己已经缓存的服务列表的信息,同时,为了展示eureka-consumer抓取服务的过程,我们同样在eureka-consumer的pom.xml中开启日志,在控制台查看输出信息(为了方便查看,可以先把eureka-provider项目pom.xml文件中的日志注释掉)。eureka-consumer的pom.xml配置如下:

server:
  port: 8081

spring:
  application:
    name: eureka-consumer

eureka:
  client:
    registry-fetch-interval-seconds: 5
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
      
logging:
  level:
    com.netflix: DEBUG

ConsumerController类新加方法countService(),完整代码如下:

package com.init.springCloud;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

@Controller
@Configuration
public class ConsumerController {
	
	@Bean
	@LoadBalanced
	public RestTemplate getRestTemplate(){
		return new RestTemplate();
	}
	
	@GetMapping(value = "/router")
	@ResponseBody
	public String router(){
		RestTemplate temp = getRestTemplate();
		return temp.getForObject("http://eureka-provider/search/1", String.class);
	}
	
	@Autowired
	private DiscoveryClient  discoveryClient;
	
	@GetMapping(value = "/count")
	@ResponseBody
	public String countService(){
		List<String> services = discoveryClient.getServices();
		for (String string : services) {
			List<ServiceInstance> instances = discoveryClient.getInstances(string);
			System.out.println("服务名称:"+string+",服务数量:"+instances.size());
		}
		return "success";
	}
	
}

之后,我们把三个项目都启动,在eureka-consumer的控制台下,可以看到服务抓取日志,这里同样去掉时间等其他信息:

Get connection: {}->http://localhost:8761, timeout = 5000
[{}->http://localhost:8761] total kept alive: 1, total issued: 0, total allocated: 1 out of 200
Getting free connection [{}->http://localhost:8761][null]
Released connection is reusable.
Releasing connection [{}->http://localhost:8761][null]
Pooling connection [{}->http://localhost:8761][null]; keep alive indefinitely
Notifying no-one, there are no waiting threads
Jersey HTTP GET http://localhost:8761/eureka//apps/delta?; statusCode=200
Got delta update with apps hashcode UP_2_
Added instance DESKTOP-E3UNJK3:eureka-provider to the existing apps in region null
Added instance DESKTOP-E3UNJK3:eureka-consumer:8081 to the existing apps in region null
The total number of instances fetched by the delta processor : 2
The total number of all instances in the client now is 2
Completed cache refresh task for discovery. All Apps hash code is Local region apps hashcode: UP_2_, is fetching remote regions? false 

访问http://localhost:8081/count,控制台也输出了我们统计的服务信息:

服务名称:eureka-consumer,服务数量:1
服务名称:eureka-provider,服务数量:1

使用Actuator健康端点检测客户端状态

默认情况下,Eureka使用客户端心跳来决定一个服务是否是处于“UP”状态的,只要客户端注册服务成功以后,Eureka服务器端就宣布这个服务是“UP”的,所以,如果是服务整个宕掉了,还好说,Eureka服务器能够知道这个服务挂掉了,但是倘若一个服务提供者不能进行数据库连接了,这个服务实例就是不可用的,但我们的服务器可不这么认为,因为他也无从知晓这个服务是有问题的。所以,我们这里引入Actuator,并使用它的\Health端点做健康检测。

在我们示例里面,eureka-provider是作为一个服务提供者,这里将它作为测试服务,引入Actuator的包,在eureka-provider的pom.xml中添加如下依赖:

	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-actuator</artifactId>
	        <version>1.5.4.RELEASE</version>
	    </dependency>

我们在程序内部模仿数据库连接,并在控制器提供一个方法,用于修改数据库连接是否发生了故障,之后通知health端点,修改当前服务的状态。在ProviderController类添加下面的方法:

	public static Boolean isCanLinkDb = true;
	
	@RequestMapping(value = "/linkDb/{can}", method = RequestMethod.GET)
	public void LinkDb(@PathVariable Boolean can){
		isCanLinkDb = can;
	}

之后新建一个MyHealthIndicator类,实现HealthIndicator接口,重写Health方法,把数据库是否能连接这个状态传入,最后更改当前服务的健康状态:

package com.init.springCloud;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
/**
 * 自定义健康指示器(Spring Boot的内容)
 * @author spirit   
 * @date 2018年5月3日 下午2:19:58 
 * @email [email protected]
 */
@Component
public class MyHealthIndicator implements HealthIndicator {

	@Override
	public Health health() {
		if(ProviderController.isCanLinkDb){
			return new Health.Builder(Status.UP).build();
		}else{
			return new Health.Builder(Status.DOWN).build();
		}
	}

}

这里修改了服务的状态,也是针对于eureka-provider自身来说的,我们能够通过health端点知道服务是否是正常的,那怎么能把health端点返回的服务状态告诉eureka-server呢?也就是跟Eureka服务器说明,我这个服务的数据库不能连接了,服务暂时不可以使用了,你在服务列表里面把我这个服务的状态更改到“DOWN”状态,不要继续让请求涌入我这个服务。这里我们使用Netflix里面提供的HealthCheckHandler接口来做,新建一个MyHealthCheckHandler类,实现HealthCheckHandler接口,重写getStatus()方法:

package com.init.springCloud;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;

import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;

/**
 * 健康检查处理器
 * @author spirit   
 * @date 2018年5月3日 下午2:39:45 
 * @email [email protected]
 */
@Component
public class MyHealthCheckHandler implements HealthCheckHandler {

	@Autowired
	private MyHealthIndicator myHealthIndicator;
	
	@Override
	public InstanceStatus getStatus(InstanceStatus instanceStatus) {
		Status status = myHealthIndicator.health().getStatus();
		if(status.equals(Status.UP)){
			return InstanceStatus.UP;
		}else{
			return InstanceStatus.DOWN;
		}
	}

}

其实,到这里,已经完成了所有的测试代码。但是,为了能让eureka-server接受到服务变更信息后,更快速地把服务列表的信息进行更改同步(多台发现服务的服务器之间会复制服务列表,进行更新),我们缩短一下更新的时间。

eureka.client.instance-info-replication-interval-seconds 表示复制实例更改的频率(以秒为单位),以复制到发现服务的服务器。默认30s。

在eureka-server的pom.xml文件中添加上面的配置,把时间更改到10s:

server:
  port: 8761
  
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    instanceInfoReplicationIntervalSeconds: 10
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
#  server:
#    enable-self-preservation: false
#    eviction-interval-timer-in-ms: 10000

为了方便测试,不影响观察,把上面两个项目中之前所做的各种配置全部注释掉,然后把三个项目全部启动。

访问:http://localhost:8080/health,能看到eureka-provider项目当前的状态是“UP”


访问:http://localhost:8761,能看到eureka-provider项目的状态是“UP”:


再访问:http://localhost:8081/router,通过eureka-consumer项目去调用eureka-provider的方法(不清楚过程的,可以查看之前的博客内容,也可在文章末尾下载源代码),也是能够正常返回结果:


之后访问:http://localhost:8080/linkDb/false,设置eureka-provider项目的服务不可用,更改状态为“DOWN”。再次访问eureka-provider的健康端点:http://localhost:8080/health,可以看到服务状态已经更改了:


接着再次访问:http://localhost:8761,查看服务器维护的服务信息,eureka-provider的状态也被更改到“DOWN”这个不可用状态了:


此时,eureka-consumer也是没办法再调用eureka-provider提供的服务了:


源码点击这里

Spring Cloud系列:

Spring Cloud介绍与环境搭建(一)

Spring Boot的简单使用(二)

Spring Cloud服务管理框架Eureka简单示例(三)

Spring Cloud服务管理框架Eureka项目集群(四)

猜你喜欢

转载自blog.csdn.net/MrSpirit/article/details/80164315