Eureka心跳检测

前言

注册中心的心跳机制有两种形式:客户端主动上报和客户端被动响应。Eureka属于是主动上报类型的,Client通过renew机制频繁的向Server发送消息,通知Server它还活着,不要将其从服务列表中剔除,但是我们renew仅仅是监控Client是否存活,并不会去检测Client依赖的服务是否存活

image-20200410233537737

从图中我们发现Client123和Client456两个客户端均依赖了第三方组件,并且MySQL同时宕机了。

  • Client123使用了Eureka自带的renew机制,renew最基础的就是调一下Server的/apps/{appName}/{instanceId}?status=&lastDirtyTimestamp=接口,正常情况下Client启动后的status为UP,所以只要Client自身服务不出问题,永远都是UP,默认的指示器是CompositeHealthIndicator,默认的管理器为EurekaHealthCheckHandler
  • Client456通过扩展HealthIndicator接口和HealthCheckHandler接口,然后来自定义需要监控的内容

默认健康监控组件

在类DiscoveryClient#getHealthCheckHandler方法中选择需要使用的健康管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public HealthCheckHandler getHealthCheckHandler() {
HealthCheckHandler healthCheckHandler = this.healthCheckHandlerRef.get();
if (healthCheckHandler == null) {
if (null != healthCheckHandlerProvider) {
healthCheckHandler = healthCheckHandlerProvider.get();
} else if (null != healthCheckCallbackProvider) {
healthCheckHandler = new HealthCheckCallbackToHandlerBridge(healthCheckCallbackProvider.get());
}

if (null == healthCheckHandler) {
healthCheckHandler = new HealthCheckCallbackToHandlerBridge(null);
}
this.healthCheckHandlerRef.compareAndSet(null, healthCheckHandler);
}

return this.healthCheckHandlerRef.get();
}

方法调用流程图

image-20200411021953543

自定义健康监控

  1. 自定义监控组件

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    @Component
    public class HealthPolicyBean implements InitializingBean {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    /**
    * 调度线程池
    */
    private ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
    /**
    * 数据库健康情况
    */
    public static boolean dbHealth = true;
    /**
    * Redis健康情况
    */
    public static boolean redisHealth = true;
    /**
    * MongoDB健康情况
    */
    public static boolean mongoHealth = true;

    @Override
    public void afterPropertiesSet() throws Exception {
    // 创建调度器
    ThreadPoolExecutor heartbeatExecutor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
    new SynchronousQueue<>(),
    new ThreadFactoryBuilder().setNameFormat("redis-HeartbeatExecutor-%d").setDaemon(true).build());

    TimedSupervisorTask task = new TimedSupervisorTask("redis-heartbeat", scheduled, heartbeatExecutor, 10,
    TimeUnit.SECONDS, 100, new RedisTimer());

    scheduled.schedule(task, 10, TimeUnit.SECONDS);
    }

    /**
    * 监控Redis状态
    */
    protected class RedisTimer implements Runnable {

    @Override
    public void run() {
    try {
    List<RedisClientInfo> clientList = redisTemplate.getClientList();
    if (clientList == null || clientList.isEmpty()) {
    HealthPolicyBean.redisHealth = false;
    } else {
    HealthPolicyBean.redisHealth = true;
    }
    } catch (Exception e) {
    HealthPolicyBean.redisHealth = false;
    }
    }
    }

    }
  2. 自定义HealthIndicator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * Cc健康指示器
    */
    @Component
    public class CcHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
    if (HealthPolicyBean.dbHealth && HealthPolicyBean.redisHealth && HealthPolicyBean.mongoHealth) {
    // 当所有组件都正常时才返回UP
    return new Health.Builder(Status.UP).build();
    } else {
    return new Health.Builder(Status.DOWN).build();
    }
    }
    }
  3. 自定义HealthCheckHandler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * Cc健康管理器
    */
    @Component
    public class CcHealthCheckHandler implements HealthCheckHandler {

    @Autowired
    private CcHealthIndicator ccHealthIndicator;

    @Override
    public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus) {
    if (ccHealthIndicator.health().getStatus().equals(Status.UP)) {
    return InstanceInfo.InstanceStatus.UP;
    }
    return InstanceInfo.InstanceStatus.DOWN;
    }
    }

    方法调用流程图

    image-20200411021933621

区别

我们打开Redis服务,启动Eureka Server、Client123和Client456。

  • Redis运行中

    Redis正常运行时,两个服务都处于正常情况

    image-20200411022923590

  • Redis停止

    将Redis服务停掉,等待一个renew周期后,服务状态发生变化,使用默认HealthCheckHandler的CUSER-SERVICE的status仍然为UP,而我们自定义HealthCheckHandler的EUREKA-HEALTH服务的status已经变成了DOWN,符合正常要求。

    image-20200411023355326

总结

在实际的生产工作中,尽量不要使用默认的HealthCheckHandler,不然就算是我们项目的MySQL、Redis、MongoDB、MQ都挂掉了,只要项目的进程还存活,那么status就很大的可能是UP,但实际上项目已经无法正常提供服务了,会给我们的项目带来很大的麻烦。