Alibaba:Nacos服务心跳和健康检查源码介绍,Java开发者

    instance.setClusterName(clusterName);
    instance.setServiceName(serviceName);
    instance.setInstanceId(instance.getInstanceId());
    instance.setEphemeral(clientBeat.isEphemeral());
    //注册实例
    serviceManager.registerInstance(namespaceId, serviceName, instance);
}
//获取服务的信息
Service service = serviceManager.getService(namespaceId, serviceName);

if (service == null) {
    throw new NacosException(NacosException.SERVER_ERROR,
            "service not found: " + serviceName + "@" + namespaceId);
}
//不存在的话,要创建一个进行处理
if (clientBeat == null) {
    clientBeat = new RsInfo();
    clientBeat.setIp(ip);
    clientBeat.setPort(port);
    clientBeat.setCluster(clusterName);
}
//开启心跳检查任务
service.processClientBeat(clientBeat);

result.put(CommonParams.CODE, NamingResponseCode.OK);
//5秒间隔
if (instance.containsMetadata(PreservedMetadataKeys.HEART_BEAT_INTERVAL)) {
    result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, instance.getInstanceHeartBeatInterval());
}
//告诉客户端不需要带上心跳信息了,变成轻量级心跳了
result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());
return result;

}


接下来我们看一下processClientBeat方法,该方法将ClientBeatProcessor放入到线程池中,接下来我们看下重点看下run方法,

![](https://upload-images.jianshu.io/upload_images/13465705-dd50cf4f160fe598.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

该方法内部主要就是更新对应实例下心跳时间,整体上如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-eefaa7258c1921a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

至此完成了从客户端到服务端更新实例的心跳时间,下图是整体的时序图:

![](https://upload-images.jianshu.io/upload_images/13465705-d5457fd0ddeaa54f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

### 服务的健康检查

Nacos Server会开启一个定时任务来检查注册服务的健康情况,对于超过15秒没收到客户端的心跳实例会将它的 healthy属性置为false,此时当客户端不会将该实例的信息发现,如果某个服务的实例超过30秒没收到心跳,则剔除该实例,如果剔除的实例恢复,发送心跳则会恢复。
当有实例注册的时候,我们会看到有个service.init()的方法,该方法的实现主要是将ClientBeatCheckTask加入到线程池当中,如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-d99c119d6a481d8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

ClientBeatCheckTask中的run方法主要做两件事心跳时间超过15秒则设置该实例信息为不健康状况和心跳时间超过30秒则删除该实例信息,如下代码:

public void run() {
try {
if (!getDistroMapper().responsible(service.getName())) {
return;
}

    if (!getSwitchDomain().isHealthCheckEnabled()) {
        return;
    }
    //获取服务所有实例信息
    List<Instance> instances = service.allIPs(true);

    // first set health status of instances:
    for (Instance instance : instances) {
        //如果心跳时间超过15秒则设置该实例信息为不健康状况
        if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
            if (!instance.isMarked()) {
                if (instance.isHealthy()) {
                    instance.setHealthy(false);
                    Loggers.EVT_LOG
                            .info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",
                                    instance.getIp(), instance.getPort(), instance.getClusterName(),
                                    service.getName(), UtilsAndCommons.LOCALHOST_SITE,
                                    instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());
                    getPushService().serviceChanged(service);
                    ApplicationUtils.publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));
                }
            }
        }
    }

    if (!getGlobalConfig().isExpireInstance()) {
        return;
    }

    // then remove obsolete instances:
    for (Instance instance : instances) {

        if (instance.isMarked()) {
            continue;
        }
        //如果心跳时间超过30秒则删除该实例信息
        if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
            // delete instance
            Loggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(),
                    JacksonUtils.toJson(instance));
            deleteIp(instance);
        }
    }

} catch (Exception e) {
    Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
}

}

首先我们来看一下deleteIp方法,该方法内部主要通过构建删除请求,发送删除请求,如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-842f6877eebce225.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

删除实例的接口如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-8e37d690c27561e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

内部通过调用ServiceManager的removeInstance方法,如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-4cd6ceb780a11d5e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

重点看下substractIpAddresses内部通过调用updateIpAddresses,该方法内部主要就是移除到超过30秒的实例信息,如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-d19a5b120a97471e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

到此完成删除实例的过程,整体的时序图如下:

![](https://upload-images.jianshu.io/upload_images/13465705-aa44e5bbf196fe5e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

接下来我们看标记不健康时候的代码,这部分代码在客户端注册的时候也出现相同的代码,只是我们略过了,这部分也是观察者模式的重要体现,从这里我们可以学习到的东西在于结合Spring的事件机制,轻松实现观察者模式,当然这个里面也有部分我感觉写的不太好,哈哈,大佬们看到勿喷。

![](https://upload-images.jianshu.io/upload_images/13465705-e8524d762fff0404.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

首先我们看serviceChanged方法,该方法主要是发布一个服务不健康的事件,如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-eaf3cea3b8070fe9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

接下来我们看下如何处理这个事件,这个时候涉及PushService这个类,整体的继承结构如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-c9418614cb3156ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们看到该类的继承ApplicationListener接口,该接口是一个支持泛型的接口,传入了ServiceChangeEvent的类,此处就是对事件的处理,如下图:

![](https://upload-images.jianshu.io/upload_images/13465705-f36b27dcf8176ba2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

接下来看一下onApplicationEvent方法,这个方法主要完成了准备数据,发送数据这几件事情:

public void onApplicationEvent(ServiceChangeEvent event) {
Service service = event.getService();
String serviceName = service.getName();
String namespaceId = service.getNamespaceId();

Future future = GlobalExecutor.scheduleUdpSender(() -> {
    try {
        Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");
        //获取所有需要推送的客户端
        ConcurrentMap<String, PushClient> clients = clientMap
                .get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
        if (MapUtils.isEmpty(clients)) {
            return;
        }

        Map<String, Object> cache = new HashMap<>(16);
        long lastRefTime = System.nanoTime();
        for (PushClient client : clients.values()) {
            //超时的不删除跳过处理
            if (client.zombie()) {
                Loggers.PUSH.debug("client is zombie: " + client.toString());
                clients.remove(client.toString());
                Loggers.PUSH.debug("client is zombie: " + client.toString());
                continue;
            }

            Receiver.AckEntry ackEntry;
            Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());
            String key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());
            byte[] compressData = null;
            Map<String, Object> data = null;

            if (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {
                org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);
                compressData = (byte[]) (pair.getValue0());
                data = (Map<String, Object>) pair.getValue1();
                Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());
            }
            //准备UDP数据
            if (compressData != null) {
                ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);
            } else {
                ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);
                if (ackEntry != null) {
                    cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));
                }
            }

            Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",
                    client.getServiceName(), client.getAddrStr(), client.getAgent(),
                    (ackEntry == null ? null : ackEntry.key));
            //发送数据
            udpPush(ackEntry);
        }
    } catch (Exception e) {
        Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);

    } finally {
        //发送完成删除
        futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
    }

总结

一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。

这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。

最后再说几句关于招聘平台的,总之,简历投递给公司之前,请确认下这家公司到底咋样,先去百度了解下,别被坑了,每个平台都有一些居心不良的广告党等着你上钩,千万别上当!!!

提供【免费】的Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

Java全套进阶资料点这里免费领取

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
g,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

Java全套进阶资料点这里免费领取

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值