nacos服务注册底层+心跳底层

服务注册和心跳检测的实现:

  1. 服务注册:其实就是调用HttpClient发送一个http请求实现的服务注册。信息中包含了命名空间,服务名,组名,集群名,ip,端口,权重,健康状况等信息。
  2. 心跳监测:通过将心跳信息封装成一个BeatTask线程任务,然后交给定时线程池执行。如果长时间服务端无法接收到客户端的心跳,此时服务端认为你的这个服务实例出问题了,这样服务端就会主动从服务注册表中剔除该服务实例,该服务实例就不存在服务端了。因此监测返回的状态码会为20404,之后会重新将该服务实例注册到服务端。并且这里的心跳检测采用了变频的方式,也就是定时任务执行的间隔是不固定的,这次任务的执行才决定下一次任务执行的时间,好处是如果有的任务频繁失败,可以将下一次执行的任务时间拉长,减少资源的浪费。

服务注册

registerInstance

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {  
    if (instance.isEphemeral()) {
	    //声明一个心跳任务
        BeatInfo beatInfo = new BeatInfo();
        //将服务名和组名根据一定规则拼成一个字符串  
        beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));  
        //设置服务的ip 端口 集群 权重等。。。
        beatInfo.setIp(instance.getIp());  
        beatInfo.setPort(instance.getPort());  
        beatInfo.setCluster(instance.getClusterName());  
        beatInfo.setWeight(instance.getWeight());  
        beatInfo.setMetadata(instance.getMetadata());  
        beatInfo.setScheduled(false); 
        //设置心跳间隔 默认是5秒
        //DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5L);
        //将心跳任务与服务的实例绑定
        beatInfo.setPeriod
        (instance.getInstanceHeartBeatInterval());  
        this.beatReactor.addBeatInfo
        (NamingUtils.getGroupedName(serviceName, groupName), beatInfo);  
    }  
	//将服务实例注册到服务端
    this.serverProxy.registerService
    (NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);  
}

registerService

//将服务的所有信息封装成一个map集合,然后调用reqAPI方法注册
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {  
    LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});  
    Map<String, String> params = new HashMap(9);  
    params.put("namespaceId", this.namespaceId);  
    params.put("serviceName", serviceName);  
    params.put("groupName", groupName);  
    params.put("clusterName", instance.getClusterName());  
    params.put("ip", instance.getIp());  
    params.put("port", String.valueOf(instance.getPort()));  
    params.put("weight", String.valueOf(instance.getWeight()));  
    params.put("enable", String.valueOf(instance.isEnabled()));  
    params.put("healthy", String.valueOf(instance.isHealthy()));  
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));  
    params.put("metadata", JSON.toJSONString(instance.getMetadata())); 
    //VERSION = "Nacos-Java-Client:v" + VersionUtils.VERSION;  
    //WEB_CONTEXT = "/nacos";
    //NACOS_URL_BASE = WEB_CONTEXT + "/v1/ns"; 
    //NACOS_URL_INSTANCE = NACOS_URL_BASE + "/instance"; 
    //NACOS_URL_SERVICE = NACOS_URL_BASE + "/service";
    this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");  
}

reqAPI

body,默认是空

servers,就是我们配置的nacos服务端所在服务的ip和端口的集合,因为我们可能配置多个。

public String reqAPI(String api, Map<String, String> params, String body, List<String> servers, String method) throws NacosException {  
    params.put("namespaceId", this.getNamespaceId());  
    //如果服务段为空则抛出异常
    if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(this.nacosDomain)) {  
        throw new NacosException(400, "no server available");  
    } else {  
	    //
        NacosException exception = new NacosException();  
        if (servers != null && !servers.isEmpty()) {  
            Random random = new Random(System.currentTimeMillis());  
            int index = random.nextInt(servers.size());  
            int i = 0;  
  
            while(i < servers.size()) {  
	            //随机选择一个服务端调用
                String server = (String)servers.get(index);  
  
                try {  
	                //调用成功则返回这个服务端
                    return this.callServer(api, params, body, server, method);  
                } catch (NacosException var13) {  
	                //调用失败则重新选择新的服务器
                    exception = var13;  
                    if(LogUtils.NAMING_LOGGER.isDebugEnabled()) 
                     {  
                        LogUtils.NAMING_LOGGER.debug("request {} failed.", server, var13);  
                    }  
  
                    index = (index + 1) % servers.size();  
                    ++i;  
                }  
            }  
        }  
  
        if (StringUtils.isNotBlank(this.nacosDomain)) {  
            int i = 0;  
  
            while(i < 3) {  
                try {  
                    return this.callServer(api, params, body, this.nacosDomain, method);  
                } catch (NacosException var12) {  
                    exception = var12;  
                    if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {  
                        LogUtils.NAMING_LOGGER.debug("request {} failed.", this.nacosDomain, var12);  
                    }  
  
                    ++i;  
                }  
            }  
        }  
  
        LogUtils.NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", new Object[]{api, servers, exception.getErrCode(), exception.getErrMsg()});  
        throw new NacosException(exception.getErrCode(), "failed to req API:/api/" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());  
    }  
}

callServer

//将地址和请求路径名( /nacos/v1/ns/instance )进行拼接,然后发送http请求进行服务注册,然后接收客户端的响应。
public String callServer(String api, Map<String, String> params, String body, String curServer, String method) throws NacosException {  
    long start = System.currentTimeMillis();  
    long end = 0L;  
    this.injectSecurityInfo(params);  
    List<String> headers = this.builderHeaders();  
    String url;  
    if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {  
        if (!curServer.contains(":")) {  
            curServer = curServer + ":" + this.serverPort;  
        }  
  
        url = HttpClient.getPrefix() + curServer + api;  
    } else {  
        url = curServer + api;  
    }  
  
    HttpResult result = HttpClient.request(url, headers, params, body, "UTF-8", method);  
    end = System.currentTimeMillis();  
    MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(result.code)).observe((double)(end - start));  
    if (200 == result.code) {  
        return result.content;  
    } else if (304 == result.code) {  
        return "";  
    } else {  
        throw new NacosException(result.code, result.content);  
    }  
}

心跳机制

addBeatInfo

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {  
    LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);  
    //通过当前服务名,ip,端口生成一个key
    String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());  
    BeatInfo existBeat = null;
    //如果当前key已经存在了心跳那么就将它从dom2Beat集合中移除并停止心跳
    if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {  
        existBeat.setStopped(true);  
    }  
	//将key与心跳信息绑定然后放到dom2Beat集合中
    this.dom2Beat.put(key, beatInfo);  
    //根据心跳信息生成一个BeatTask线程任务
    //将该任务丢到定时线程池中 等待beatInfo.getPeriod()默认为5秒后开始执行
    this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);  
    //设置一个监听器监听
    MetricsMonitor.getDom2BeatSizeMonitor()
    .set((double)this.dom2Beat.size());  
}

*定时任务的run()

首先就是通过serverProxy发送一个http请求到服务端,服务端进行响应,其实这个过程就完成了跟服务端的心跳,接下来就是解析服务端的响应的数据了。根据服务端返回的不同的状态码进行判断,进行不同的操作。
code==20404的状态码:
这段代码其实就是重新向服务端进行服务的注册。有人肯定会好奇,为什么发送心跳的时候,服务端会告诉客户端,该服务实例在服务端找不到?不是只有注册服务的时候才会去构建心跳么,按道理应该存在的?是的,这是正常的情况下,在正常情况下,客户端发送心跳,服务实例应该存在于服务端的,但是有些情况,比如说网络抖动的时候,因为网络的问题,客户端无法给服务端发送心跳,长时间服务端无法接收到客户端的心跳,此时服务端认为你的这个服务实例出问题了,这样服务端就会主动从服务注册表中剔除该服务实例,该服务实例就不存在服务端了。当客户端网络好了的话,那么此时会恢复跟服务端的心跳机制,就会出现服务找不到的现象,这种情况下只要重新注册一下就行了。
BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);验证完这次心跳之后会
重新构建一个新的任务然后丢到定时线程池中执行
。以此来实现的定时发送心跳的机制。这个定时机制实现了变频的功能,所谓的变频就是定时任务执行的间隔是不固定的,这次任务的执行才决定下一次任务执行的时间,这样有一个好处就是在一些场景中,如果有的任务频繁失败,那么是不是可以考虑让下一次执行的任务时间拉长,减少资源的浪费,nacos在定时更新本地服务实例列表的缓存也使用到了这个机制。这种机制其实在很多框架,中间件都有使用。

public void run() {  
    if (!this.beatInfo.isStopped()) {  
        long nextTime = this.beatInfo.getPeriod();  
  
        try {  
            JSONObject result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo, BeatReactor.this.lightBeatEnabled);  
            long interval = (long)result.getIntValue("clientBeatInterval");  
            boolean lightBeatEnabled = false;  
            if (result.containsKey("lightBeatEnabled")) {  
                lightBeatEnabled = result.getBooleanValue("lightBeatEnabled");  
            }  
  
            BeatReactor.this.lightBeatEnabled = lightBeatEnabled;  
            if (interval > 0L) {  
                nextTime = interval;  
            }  
  
            int code = 10200;  
            if (result.containsKey("code")) {  
                code = result.getIntValue("code");  
            }  
  
            if (code == 20404) {  
                Instance instance = new Instance();  
                instance.setPort(this.beatInfo.getPort());  
                instance.setIp(this.beatInfo.getIp());  
                instance.setWeight(this.beatInfo.getWeight());  
                instance.setMetadata(this.beatInfo.getMetadata());  
                instance.setClusterName(this.beatInfo.getCluster());  
                instance.setServiceName(this.beatInfo.getServiceName());  
                instance.setInstanceId(instance.getInstanceId());  
                instance.setEphemeral(true);  
  
                try {  
                    BeatReactor.this.serverProxy.registerService(this.beatInfo.getServiceName(), NamingUtils.getGroupName(this.beatInfo.getServiceName()), instance);  
                } catch (Exception var10) {  
                }  
            }  
        } catch (NacosException var11) {  
            LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", new Object[]{JSON.toJSONString(this.beatInfo), var11.getErrCode(), var11.getErrMsg()});  
        }  
  
        BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);  
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值