在默认情况下,Eureka 的客户端每隔30秒会发送一次心跳给服务器端,告知它仍然存活。但是,在实际环境中有可能出现:客户端表面上可以正常发送心跳,但实际上服务是不可用的。
例如一个需要访问数据的服务提供者,表面上可以正常响应,但是数据库已经无法访问;又如,服务提供者需要访问第三者的访问,而这些服务早已失效。对于这些情况,应当告诉服务器当前客户的状态,调用者或其他客户端无法获取这些有问题的服务实例。实现此功能可以使用 Eureka 的健康检查控制器。
如果一个客户端本身没有问题,但该模块所依赖的服务无法使用,那么对于服务器以及其他客户端来说,该客户端也是不可用的,最常见的就是数据库访问模块;这样我们就需要做两件事:① 让客户的自己进行检查,是否能连接数据库;② 将连接数据库的结果与客户端的状态进行关联,并且将状态告诉服务器。
要使用 Eureka 的健康检查控制器就需要导入 spring-boot-starter-actuator 依赖;实现其中的 HealthIndicator (健康指示器) 接口,根据是否能访问数据库来决定应用自身的健康。
案例:
1. 分别创建项目:crazyspringcloud-healthhandler-server 和 crazyspringcloud-healthhandler-provider 作为案例的服务注册中心和服务提供者;
2. 为 crazyspringcloud-healthhandler-provider (服务提供者) 添加 Eureka 的健康检查控制依赖:
org.springframework.boot
spring-boot-starter-actuator
按spring cloud常规要求完成配置后,启动注册中心和服务提供者,浏览器访问 http://localhost:8761/health 浏览器输出该 REST 服务向外展示当前应用的状态为“UP”:
3. 创建 HealthController 类,模拟数据连接模块,提供数据库连接状态:
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* 该Controller用来模拟数据库连接模块;
* 使用变量:canVisitDb 模拟是否能连接数据库;
*/
@RestController
public class HealthController {
// 标识当前数据库是否可以访问的变量
public static Boolean canVisitDb = false;
@RequestMapping(value = "/db/{canVisitDb}", method = RequestMethod.GET)
public String setConnectState(@PathVariable("canVisitDb") Boolean canVisitDb) {
this.canVisitDb = canVisitDb;
return "当前数据库状态:" + this.canVisitDb;
}
}
说明:控制器中的变量:canVisitDb 可以通过 /db/false 或者 /db/true 两个地址来进行修改,配合健康指示器一起使用;
若该变量的值为 true,健康指示器将返回“UP”状态;反之则返回“DOWN”状态;
4. 定义健康指示器,实现 HealthIndicator (健康指示器) 接口:
通过使用 Spring Boot Actuator 提供的 HealthIndicator 接口自定义健康指示器,代码如下:
import com.sztxtech.creazyspringcloud.controller.HealthController;
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;
/**
* 实现服务健康指示器
*/
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (HealthController.canVisitDb) {
return new Health.Builder(Status.UP).build();
} else {
return new Health.Builder(Status.DOWN).build();
}
}
}
根据是否可以访问数据库的变量来决定应用自身的健康状态。
5. 依次启动服务注册中心(服务器):crazyspringcloud-healthhandler-server 和服务提供者:crazyspringcloud-healthhandler-provider;确保正常启动之后,访问 http://localhost:8761/health 可以看到服务提供者的健康状态,默认状态为 DOWN:
因为此控制器中的 canVisitDb 默认值是 false;如果想让应用的健康状态变为 UP,则访问 http://localhost:9001/db/true 即可进行进行修改:
6. 若服务提供者要将此健康状态告知服务器,还需要实现“健康检查处理器”: HealthCheckHandler;
处理器会将应用的健康状态保存到内存中,状态一旦发生改变,就会重新向服务器进行注册,其他的客户端将拿不到这些不可用的实例。
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
/**
* 实现健康检查处理器
*/
@Component
public class MyHealthCheckHandler implements HealthCheckHandler {
@Autowired
private MyHealthIndicator healthIndicator;
@Override
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus) {
Status status = healthIndicator.health().getStatus();
if (status.equals(Status.UP)){
System.out.println("数据库连接正常...");
return InstanceInfo.InstanceStatus.UP;
} else {
System.out.println("数据库无法连接...");
return InstanceInfo.InstanceStatus.DOWN;
}
}
}
在自定义的健康检查处理器中,注入了前面实现的健康指示器,根据健康指示器的结果来返回不同的状态。Eureka 中会启动一个定时器,定时刷新本地实例的信息,并且执行“处理器”中的 getStatus() 方法,再将服务实例的状态“更新”到服务器中。执行以上逻辑的定时器,默认30秒执行一次,如果想加快看到效果,可以修改 eureka.client.initial-instance-info-replication-interval-seconds 配置。以下是服务提供者的配置文件:
server:
port: 9001
spring:
application:
name: healthhandler-provider
eureka:
instance:
hostname: localhost
client:
initial-instance-info-replication-interval-seconds: 10
service-url:
defaultZone: http://localhost:8761/eureka/
依次启动服务器、服务提供者,在浏览器中访问 http://localhost:8761/ 结果如下,可看到服务提供者的状态为 DOWN;
访问 http://localhost:9001/db/true 将数据库设置为“数据库连接正常...”
再次访问 http://localhost:8761/ 结果如下,可看到服务提供者的状态已经变更为 UP;
7. 服务自检结果查询:
在前面,通过浏览器访问 Eureka 界面可查看服务状态的改变。下面将通过服务消费者方的代码来查询应用健康自检的结果;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@Configuration
public class HealthCheckResultController {
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping(value = "/router", method = RequestMethod.GET)
public String router() {
// 查找服务列表
List siList = getServiceInstances();
// 遍历服务信息及状态
for (ServiceInstance service : siList) {
EurekaDiscoveryClient.EurekaServiceInstance esi = (EurekaDiscoveryClient.EurekaServiceInstance) service;
InstanceInfo info = esi.getInstanceInfo();
System.out.println(info.getAppName() + "---" + info.getInstanceId() + "---" + info.getStatus());
}
return "";
}
/**
* 查询可用服务
* @return
*/
private List getServiceInstances() {
List idList = discoveryClient.getServices();
List result = new ArrayList<>();
for (String id : idList) {
List siList = discoveryClient.getInstances(id);
result.addAll(siList);
}
return result;
}
}
在客户端中,若需要查询集群中的服务,可用使用Spring Cloud 的 DiscoveryClient类,或者 Eureka 的 EurekaClient 类,Spring Cloud 对 Eureka 进行了封装;上面的代码中调用了 DiscoveryClient 的方法进行查询;
8. 依次启动:服务器、服务提供者、服务消费者,在确保正常启动的前提下:
① 访问 http://localhost:9001/db/true 将数据库检查设置为 “数据库连接正常...” ;
② 访问 http://localhost:9050/router ,查看IDE 服务提供者的控制台,输出内容如下:
③ 访问 http://localhost:9001/db/false 将数据库检查设置为 “数据库无法连接...” ;
④ 访问 http://localhost:9050/router ,查看IDE 服务提供者的控制台,输出内容如下:
从以上分别将模拟的数据库连接模块设置为是否可以连接来进行测试的两组结果可以看出,将数据库设置为不可连接后,可用的访问就只有消费者自己了,服务提供者已经不存在于服务列表中了。
另外需要注意的是,默认情况下,客户端到服务器端获取注册表会有一定的时间间隔,因此在设置数据库是否可以连接后,访问服务消费者查看效果需要稍等几秒,否则将看不到预期的效果哟~~~