03、客户端健康检测与常用配置

102 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

03、客户端健康检测与常用配置

一、健康检测:

1.1 使用 Spring Boot Actuator

  • 在实际开发中,还有可能出现一种情况
  • 客户端的服务需要连接数据库,可是数据库已经崩溃,但是客户端仍然向服务器端发送心跳请求,这种情况下服务器端误认为客户端的服务是正常的
  • 客户端应该及时告诉服务器端自己的健康状态
  • 加入Actuator,提供了很多端点,其中一个叫做/heath端点

修改 srl-provider-server 依赖:

<!-- 健康检查依赖此模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

访问http://localhost:8888/actuator/health 即可返回当前服务提供者的健康状态

结果如下:{"status":"UP"}

1. 2 实现应用健康自检:

如果一个客户端本身没有问题,但是该模块所依赖的服务无法使用,那么对于服务器以及其他客户端来说,该客户端是不可用的 最常见 就是访问数据库的模块。我们需要做两件事:

  1. 让客户端自己进行检查,是否能连接数据库 ,
  2. 将连接数据库的结果与客户端的状态进行关联 并且将状态告诉服务器。

健康指示器:

/**java
 * 此处是健康指示器,只是服务提供者知道 自己的健康状态
 * 通过如下方式:http://localhost:8888/actuator/health      返回当前服务提供者的健康状态
 * EK服务器 还是通过心跳机制检测 该服务提供者的健康状态
 * 如果需要 EK服务器通过自定义的健康指示器来 检测健康状态, 需要 -- 健康检查处理器、eureka.client.healthcheck.enabled=true
 */
@Component
public class MyHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        if(HealthController.canVisitDb){
            //成功连接数据库 返回UP
            return new Health.Builder(Status.UP).build();
        }else{
            //连接数据库失败 返回Down
            return new Health.Builder(Status.DOWN).build();
        }
    }
}
/**
 * 此方法只用于模拟改变 当前节点的健康状态
 * @param canVisitDb
 * @return
 */
@RequestMapping(value = "db/{canVisitDb}", method = RequestMethod.GET)
public String setConnectState(@PathVariable("canVisitDb") Boolean canVisitDb) {
    this.canVisitDb = canVisitDb;
    return "当前数据库是否正常:" + this.canVisitDb;

}

健康检查处理器 : 在自定义的健康检查处理器中,注入了前面编写的健康指示器,根据健康指示器的结 果来返回不同 的状态 。Eureka 中会启动一个定时器,定时刷新本地实例的信息,并且执行 “处理器 ”中的 getStatus 方法,再将服务实例的状态“更新”到服务器中。执行以上逻辑 的定时器 默认30秒执行一次,如果想加快 到效果,可以修改eureka.client.instance-info-replication-interval-seconds 配置。

/**
 * 健康检查处理器   -- 将健康指示器里的健康状态返回给eureka server
 */
@Component
public class MyHealthCheckHandler implements HealthCheckHandler {

    @Autowired
    private MyHealthIndicator myHealthIndicator;

    @Override
    public InstanceStatus getStatus(InstanceStatus instanceStatus) {
        //通过健康指示器来获取健康状态
        Status status = myHealthIndicator.health().getStatus();
        //返回状态给EK服务器
        if (status.equals(Status.UP)) {
            System.out.println("数据库正常连接");
            return InstanceStatus.UP;
        } else {
            System.out.println("数据库无法连接");
            return InstanceStatus.DOWN;
        }
    }
}

1.3 服务查询

在前节中,通过浏览器访问 Eureka 界面可查看服务状态的改变。本例通过修改服务 调用者的代码来查看应用健康自检的效果。修改服务调用者(srl-invoker-server 模块〉, 控制器修改后如代码

 /**
     * 测试查询服务列表
     *
     * @return
     */
    @RequestMapping(value = "/select", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public String routerSelect() {
        //查找服务列表
        List<ServiceInstance> ins = this.getServiceInstances();
        //输出服务信息及状态
        for (ServiceInstance service : ins) {
            EurekaServiceInstance esi = (EurekaServiceInstance) service;
            InstanceInfo info = esi.getInstanceInfo();
            System.out.println(info.getAppName() + "---" + info.getInstanceId() + "---" + info.getStatus());
            //获取配置的元数据信息
            System.out.println(service.getMetadata().get("author-name"));
        }
        return "";
    }

    /**
     * 在客户端中,如果需要查询集群中的服务 可以使用 Spring Cloud 的discoveryClient类,或者 Eureka 的eurekaClient 类,
     * Spring Cloud 对Eureka 进行了封装。本例中调用了discoveryClient 的方法来查询服务实例。
     * 在控制器的router方法中,仅将查询到的服务实例进行输出 。
     */
    private List<ServiceInstance> getServiceInstances() {
        List<String> ids = discoveryClient.getServices();
        List<ServiceInstance> result = new ArrayList<>();
        for (String id : ids) {
            List<ServiceInstance> ins = discoveryClient.getInstances(id);
            result.addAll(ins);
        }
        return result;
    }

访问:http://localhost:9000/select 结果如下: EUREKA-SERVER---DESKTOP-29QKE42:eureka-server:8761---UP EUREKA-SERVER---DESKTOP-29QKE42:eureka-server:8762---UP INVOKER-SERVER---DESKTOP-29QKE42:invoker-server:9000---UP PROVIDER-SERVER---DESKTOP-29QKE42:provider-server:9999---UP PROVIDER-SERVER---DESKTOP-29QKE42:provider-server:8888---UP

二、Eureka 的常用配置

主要有 自我保护模式、心跳检测机制、注册表抓取策略、元数据配置

客户端配置:

#客户端的实例会向服务器发送周期性的心跳,默认是30秒,可修改客户端此配置
eureka.instance.lease-renewal-interval-in-seconds=30
#超过30s没有收到心跳,则启用停流,将这个实例从列表中清理掉,这个值必须大于:eureka.instance.lease-renewal-interval-in-seconds
eureka.instance.lease-expiration-duration-in-seconds=90
#客户端向eureka-server抓取注册表(可用的服务列表),将服务器端的注册表保存到本地缓存中,默认是30s,需要结合集群规模设置(或者服务性质)
eureka.client.registry-fetch-interval-seconds=30

服务器配置:

#剔除失效节点 -- 注册表的清理间隔
eureka.server.eviction-interval-timer-in-ms=6000
#是否启用自我保护,不启用 自我保护:如果心跳的失败率超过一定比例,服务会将这些实例保护起来,并不会马上将其从注册表中剔除。结合后面容错机制
eureka.server.enable-self-preservation=false

Eureka的元数据有两种:标准元数据和自定义元数据。 标准元数据:主机名、IP地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。 自定义元数据:可以使用eureka.instance.metadata-map配置,这些元数据可以在远程客户端中访问,但是一般不改变客户端行为,除非客户端知道该元数据的含义。

分别在每个节点定义key相同value不同的元数据:

#配置元数据信息,元数据都会保存在服务器的注册表中,并且使用简单的方式与客户端进行共享。DiscoveryClient调用
eureka.instance.metadata-map.author-name=songrongliang-provider

通过此方式获取:

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 测试查询服务列表
     * @return
     */
    @RequestMapping(value = "/select", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public String routerSelect() {
        //查找服务列表
        List<ServiceInstance> ins = this.getServiceInstances();
        //输出服务信息及状态
        for (ServiceInstance service : ins) {
            EurekaServiceInstance esi = (EurekaServiceInstance) service;
            InstanceInfo info = esi.getInstanceInfo();
            System.out.println(info.getAppName() + "---" + info.getInstanceId() + "---" + info.getStatus());
            //获取配置的元数据信息
            System.out.println(service.getMetadata().get("author-name"));
        }
        return "";
    }

    /**
     * 在客户端中,如果需要查询集群中的服务 可以使用 Spring Cloud 的discoveryClient类,或者 Eureka 的eurekaClient 类,
     * Spring Cloud 对Eureka 进行了封装。本例中调用了discoveryClient 的方法来查询服务实例。
     * 在控制器的router方法中,仅将查询到的服务实例进行输出 。
     */
    private List<ServiceInstance> getServiceInstances() {
        List<String> ids = discoveryClient.getServices();
        List<ServiceInstance> result = new ArrayList<>();
        for (String id : ids) {
            List<ServiceInstance> ins = discoveryClient.getInstances(id);
            result.addAll(ins);
        }
        return result;
    }

打印输出如下:

EUREKA-SERVER---DESKTOP-29QKE42:eureka-server:8761---UP songrongliang-registry EUREKA-SERVER---DESKTOP-29QKE42:eureka-server:8762---UP songrongliang-registry INVOKER-SERVER---DESKTOP-29QKE42:invoker-server:9000---UP songrongliang-invoker PROVIDER-SERVER---DESKTOP-29QKE42:provider-server:8888---UP songrongliang-provider PROVIDER-SERVER---DESKTOP-29QKE42:provider-server:9999---UP songrongliang-provider