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项目application.yml文件,把leaseRenewalIntervalInSeconds的时间改成5s,为了能够看到项目发送的心跳讯息,我们把Eureka的log日志打开,在控制台看输出结果。application.yml配置如下:

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项目的application.yml文件中添加leaseExpirationDurationInSeconds,跟leaseRenewalIntervalInSeconds并排,时间设置为10s,前面的介绍中说明了这个时间是要比leaseRenewalIntervalInSeconds大的。修改后的application.yml文件为:

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项目的application.yml新增配置,将服务器的自我保护机制关闭,并把自动清理的时间设置短一些。

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的application.yml中开启日志,在控制台查看输出信息(为了方便查看,可以先把eureka-provider项目application.yml文件中的日志注释掉)。eureka-consumer的application.yml配置如下:

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 spirit612@sina.cn
 */
@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 spirit612@sina.cn
 */
@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的application.yml文件中添加上面的配置,把时间更改到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提供的服务了:

源码点击这里

最后,大家有什么不懂的或者其他需要交流的内容,也可以进入我的QQ讨论群一起讨论:654331206

Spring Cloud系列:

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

Spring Boot的简单使用(二)

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

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

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

Netflix之第一个Ribbon程序(六)

Ribbon负载均衡器详细介绍(七)

Spring Cloud中使用Ribbon(八)

具有负载均衡功能的RestTemplate底层原理(九)

OpenFeign之第一个Feign程序(十)

OpenFeign之feign使用简介(十一)

Spring Cloud中使用Feign(十二)

Netflix之第一个Hystrix程序(十三)

Netflix之Hystrix详细分析(十四)

Spring Cloud中使用Hystrix(十五)

Netflix之第一个Zuul程序(十六)

Spring Cloud集群中使用Zuul(十七)

Netflix之Zuul的进阶应用(十八)

消息驱动之背景概述(十九)

消息中间件之RabbitMQ入门讲解(二十)

消息中间件之Kafka入门讲解(二十一)

Spring Cloud整合RabbitMQ或Kafka消息驱动(二十二)

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值