Java面试系列之Nacos健康检查机制

2022-09-23 18:01:23 浏览数 (1)

Nacos是阿里巴巴开源的注册中心和配置中心的技术解决方案,目前有很多公司都在使用它,并落地到自己的业务产品中,好吧你们知不知道它是支持健康检查机制的呢?下面我将带着大家一起来理解它的健康检查机制的核心原理及业务背景。

为什么Nacos要支持健康检查机制

Nacos是一个注册中心,为什么它要支持健康检查机制呢?这主要是因为注册中心不应该仅仅提供服务注册和发现功能,还应该保证对服务可用性进行监测,对不健康的服务和过期的进行标识或剔除,维护实例的生命周期,以保证客户端尽可能的查询到可用的服务列表。因此本文将详细介绍 Nacos 注册中心中的健康检查机制。

Nacos中的实例分类

在搞清楚Nacos的健康检查机制之前,一定清楚Nacos中的实例分类,Nacos 提供了两 种服务类型供用户注册实例时选择,分为临时实例和永久实例。

临时实例只是临时存在于注册中心中,会在服务下线或不可用时被注册中心剔除,临时实例会与注 册中心保持心跳,注册中心会在一段时间没有收到来自客户端的心跳后会将实例设置为不健康,然 后在一段时间后进行剔除。

永久实例在被删除之前会永久的存在于注册中心,且有可能并不知道注册中心存在,不会主动向注 册中心上报心跳,那么这个时候就需要注册中心主动进行探活。

什么是临时实例的健康检查机制?

在 Nacos 中,用户可以通过两种方式进行临时实例的注册,通过 Nacos 的 OpenAPI 进行服务注 册或通过 Nacos 提供的 SDK 进行服务注册。

OpenAPI 的注册方式实际是用户根据自身需求调用 Http 接口对服务进行注册,然后通过 Http 接 口发送心跳到注册中心。在注册服务的同时会注册一个全局的客户端心跳检测的任务。在服务一段 时间没有收到来自客户端的心跳后,该任务会将其标记为不健康,如果在间隔的时间内还未收到心 跳,那么该任务会将其剔除。

SDK 的注册方式实际是通过 RPC 与注册中心保持连接(Nacos 2.x 版本中,旧版的还是仍然通过 OpenAPI 的方式),客户端会定时的通过 RPC 连接向 Nacos 注册中心发送心跳,保持连接的存 活。如果客户端和注册中心的连接断开,那么注册中心会主动剔除该 client 所注册的服务,达到下 线的效果。同时 Nacos 注册中心还会在注册中心启动时,注册一个过期客户端清除的定时任务, 用于删除那些健康状态超过一段时间的客户端。

从上面的特点我们可以发现,对于不同类型的使用方式,Nacos 对于健康检查的特点实际都是相同 的,都是由客户端向注册中心发送心跳,注册中心会在连接断开或是心跳过期后将不健康的实例移除。

(1)我们可以看一下Nacos2.1.0中的临时实例的健康检查机制的关键代码,下面是SDK健康检查机制的入口。

代码语言:javascript复制
//代码来自于NamingHttpClientProxy类的registerService()方法
@Override
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, 
        serviceName,instance);
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        //如果是临时实例,则开启临时实例的健康检查机制
        if (instance.isEphemeral()) {
            //构造当前需要注册的服务实例的心跳信息,诸如服务名称、IP地址信息、端口号、集群名称、服务实例权重等
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            //开启一个定时任务,定时的向Nacos Server发送心跳包,具体可以查阅BeatReactor类
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        //构造服务注册的请求参数
        final Map<String, String> params = new HashMap<String, String>(32);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, groupedServiceName);
        params.put(CommonParams.GROUP_NAME, groupName);
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        params.put(IP_PARAM, instance.getIp());
        params.put(PORT_PARAM, String.valueOf(instance.getPort()));
        params.put(WEIGHT_PARAM, String.valueOf(instance.getWeight()));
        params.put(REGISTER_ENABLE_PARAM, String.valueOf(instance.isEnabled()));
        params.put(HEALTHY_PARAM, String.valueOf(instance.isHealthy()));
        params.put(EPHEMERAL_PARAM, String.valueOf(instance.isEphemeral()));
        params.put(META_PARAM, JacksonUtils.toJson(instance.getMetadata()));
        //发起服务注册的请求
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
        
    }

(2)生产健康检查心跳包的出口如下。

代码语言:javascript复制
//源码来自于NamingHttpClientProxy类的sendBeat()方法
public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {
        if (NAMING_LOGGER.isDebugEnabled()) {
            NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());
        }
        Map<String, String> params = new HashMap<String, String>(16);
        Map<String, String> bodyMap = new HashMap<String, String>(2);
        if (!lightBeatEnabled) {
            bodyMap.put("beat", JacksonUtils.toJson(beatInfo));
        }
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
        params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());
        params.put(IP_PARAM, beatInfo.getIp());
        params.put(PORT_PARAM, String.valueOf(beatInfo.getPort()));
        //发送健康检查的心跳包
        String result = reqApi(UtilAndComs.nacosUrlBase   "/instance/beat", params, bodyMap, HttpMethod.PUT);
        return JacksonUtils.toObj(result);
    }

定时器默认的周期为5s,业务服务可以在发起服务注册时,主动的调整健康检查心跳的周期,具体代码实现如下:

代码语言:javascript复制
//源码来自于Nacos的Instance类
public static final long DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5);

(3)Nacos Server用Open API去处理客户端发送的心跳包,部分代码实现如下:

代码语言:javascript复制
@CanDistro
    @PutMapping("/beat")
    @Secured(action = ActionTypes.WRITE)
    public ObjectNode beat(HttpServletRequest request) throws Exception {
        ...
        //处理客户端的健康检查的心跳请求,设置服务实例为健康的状态
        int resultCode = getInstanceOperator().handleBeat(namespaceId, serviceName,ip, port, clusterName,    
            clientBeat, builder);
        result.put(CommonParams.CODE, resultCode);
        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL,
                getInstanceOperator().getHeartBeatInterval(namespaceId, serviceName, ip, port, clusterName));
        result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());
        return result;
    }

大家如果感兴趣可以查阅InstanceController类的beat()方法。

什么是永久实例健康检查机制?

Nacos 中使用 SDK 对于永久实例的注册实际也是使用 OpenAPI 的方式进行注册,这样可以保证即使是客户端下线后也不会影响永久实例的健康检查。

对于永久实例的的监看检查,Nacos 采用的是注册中心探测机制,注册中心会在永久服务初始化时 根据客户端选择的协议类型注册探活的定时任务。Nacos 现在内置提供了三种探测的协议,即 Http、TCP 以及 MySQL 。一般而言 Http 和 TCP 已经可以涵盖绝大多数的健康检查场景。MySQL 主要用于特殊的业务场景,例如数据库的主备需要通过服务名对外提供访问,需要确定当前 访问数据库是否为主库时,那么我们此时的健康检查接口,是一个检查数据库是否为主库的 MySQL命令。

由于持久化服务的实例的在被主动删除前一直存在的特性,探活的定时任务会不断探测服务的健康状态,并且将无法探测成功的实例标记为不健康。但是有些时候会有这样的场景,有些服务不希望去校验其健康状态,Nacos 也是提供了对应的白名单配置,用户可以将服务配置到该白名单,那么 Nacos 会放弃对其进行健康检查,实例的健康状态也始终为用户传入的健康状态。

(1)Nacos Server会启动一个定时任务,定时的执行任务HealthCheckTask,并调用永久实例健康检查机制的处理类HealthCheckProcessorDelegate去处理健康检查。

代码语言:javascript复制
@Component("healthCheckDelegate")
public class HealthCheckProcessorDelegate implements HealthCheckProcessor {
    
    private Map<String, HealthCheckProcessor> healthCheckProcessorMap = new HashMap<>();
    
    public HealthCheckProcessorDelegate(HealthCheckExtendProvider provider) {
        provider.init();
    }
    
    @Autowired
    public void addProcessor(Collection<HealthCheckProcessor> processors) {
        healthCheckProcessorMap.putAll(processors.stream().filter(processor -> processor.getType() != null)
                .collect(Collectors.toMap(HealthCheckProcessor::getType, processor -> processor)));
    }
    
    @Override
    public void process(HealthCheckTask task) {
        //处理健康检查
        String type = task.getCluster().getHealthChecker().getType();
        HealthCheckProcessor processor = healthCheckProcessorMap.get(type);
        if (processor == null) {
            processor = healthCheckProcessorMap.get(NoneHealthCheckProcessor.TYPE);
        }
        
        processor.process(task);
    }
    
    @Override
    public String getType() {
        return null;
    }
}

(2)Nacos Server服端的健康检查可以分为多种类型,比如MysqlHealthCheckProcessor、HttpHealthCheckProcessor和TcpSuperSenseProcessor等。

(3)HttpHealthCheckProcessor为Nacos支持的基于HTTP的永久实例健康检查机制的处理类,TcpSuperSenseProcessor为Nacos支持的基于TCP的永久实例健康检查机制的处理类,MysqlHealthCheckProcessor为Nacos支持的基于数据库健康检查的永久实例健康检查机制的处理类。

总之Nacos Server支持HTTP、TCP和MySQL三种永久性实例的健康检查机制。

公众号初衷

知识输出是笔者的初衷,借助知识输出,能够认识更多的牛人,能够和牛人沟通,也是自己技术提升的一个机会。

0 人点赞