栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

四 nacos注册中心源码解析--健康检测

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

四 nacos注册中心源码解析--健康检测

一 核心原理流程图

二 客户端完成对服务端的监听 2.1 实现udp监听


PushReceiver的构造方法中会触发监听器的初始化

public PushReceiver(HostReactor hostReactor) {
        try {
            this.hostReactor = hostReactor;
            this.udpSocket = new DatagramSocket();
            this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    thread.setName("com.alibaba.nacos.naming.push.receiver");
                    return thread;
                }
            });
            // 触发监听器
            this.executorService.execute(this);
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] init udp socket failed", e);
        }
    }
2.2 监听服务端实例变化

在PushReceiver的run方法中,不断循环监听服务端的push请求。然后调用 processServiceJSON 对服务端的数据进行解析

@Override
    public void run() {
        while (!closed) {
            try {
                
                // byte[] is initialized with 0 full filled by default
                byte[] buffer = new byte[UDP_MSS];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                
                // 接受服务端的数据变化
                udpSocket.receive(packet);
                
                String json = new String(IoUtils.tryDecompress(packet.getData()), UTF_8).trim();
                NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
                
                PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class);
                String ack;
                if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
                	// 监听到有变化的事件 触发本地缓存列表的变化
                    hostReactor.processServiceJson(pushPacket.data);
                    
                    // send ack to server
                    ack = "{"type": "push-ack"" + ", "lastRefTime":"" + pushPacket.lastRefTime + "", "data":"
                            + """}";
                } else if ("dump".equals(pushPacket.type)) {
                    // dump data to server
                    ack = "{"type": "dump-ack"" + ", "lastRefTime": "" + pushPacket.lastRefTime + "", "data":"
                            + """ + StringUtils.escapeJavaScript(JacksonUtils.toJson(hostReactor.getServiceInfoMap()))
                            + ""}";
                } else {
                    // do nothing send ack only
                    ack = "{"type": "unknown-ack"" + ", "lastRefTime":"" + pushPacket.lastRefTime
                            + "", "data":" + """}";
                }
                // 
                udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.getBytes(UTF_8).length,
                        packet.getSocketAddress()));
            } catch (Exception e) {
                if (closed) {
                    return;
                }
                NAMING_LOGGER.error("[NA] error while receiving push data", e);
            }
        }
    }
三 服务端建立健康监控

还记得在服务提供者发起服务注册时。在 createEmptyService 方法中,会创建一个空的服务

并且在这个创建过程中,调用了一个 putServiceAndInit ,这个方法中除了创建空的服务并且初始
化,还会调用 service.init 方法进行服务的初始化


这个init方法,会和当前服务提供者建立一个心跳检测机制,这个心跳检测会每5s执行一次

public void init() {
        HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
        for (Map.Entry entry : clusterMap.entrySet()) {
            entry.getValue().setService(this);
            entry.getValue().init();
        }
    }

那我们来看看ClientBeatCheckTask里面是怎样实现的

@Override
    public void run() {
        try {
            // If upgrade to 2.0.X stop health check with v1
            if (ApplicationUtils.getBean(UpgradeJudgement.class).isUseGrpcFeatures()) {
                return;
            }
            if (!getDistroMapper().responsible(service.getName())) {
                return;
            }
            
            if (!getSwitchDomain().isHealthCheckEnabled()) {
                return;
            }
            
            List instances = service.allIPs(true);
            
            // first set health status of instances:
            for (Instance instance : instances) {//遍历服务节点进行心跳检测
            	//如果服务实例的最后一次心跳时间大于设置的超时时间,则认为这个服务已经下线。
                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);
                        }
                    }
                }
            }
            
            if (!getGlobalConfig().isExpireInstance()) {
                return;
            }
            
            // then remove obsolete instances:
            for (Instance instance : instances) {
                
                if (instance.isMarked()) {
                    continue;
                }
                
                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);
        }
        
    }

监听服务状态变更事件,然后遍历所有的客户端,通过udp协议进行消息的广播通知
UdpPushService.onApplicationEvent

@Override
    public void onApplicationEvent(ServiceChangeEvent event) {
        // ......
                    
                    udpPush(ackEntry);
         }     

那么服务消费者此时应该是建立了一个udp服务的监听,否则服务端无法进行数据的推送。这个监听是
在HostReactor的构造方法中初始化的

三 总结
  • nacos在注册实例时调用createEmptyService()->createServiceIfAbsent()->putServiceAndInit()->init()时会触发健康检测,没个五秒执行一次(ClientBeatCheckTask中的run())
  • ClientBeatCheckTask中的run()会遍历注册中心的所有实例,根据当最后一次心跳时间和默认心跳超时时间的差值判断实例是否健在,判断实例不存在时会发布一个ServiceChangeEvent事件,并将当前实例从注册中心删除
  • UdpPushService监听到ServiceChangeEvent通过upd协议推送消息个服务端
  • 客户端在初始化HostReactor会通过PushReceiver触发upd消息的监听器
  • 客户端监听到来自服务端的服务实例变化消息时,会更新当前本地缓存
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1000298.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号