• nacos源码分析-服务注册(客户端)


    前言

    一直都想写SpringCloudAlibaba的源码分析,终于开始动手第一篇了,如果想要看懂Nacos源码至少要把《SpringBoot自动》配置看了,不然就是看天书。本篇文章呢带大家一起来看一下Nacos-Client 客户端服务注册这一部分的源码。

    基础环境

    首先需要下载一个Nacos-server,也就是注册中心了,顺便把源码也下载了, 后面我们在分析服务端的时候会用到, 下载地址 ,https://github.com/alibaba/nacos/releases/tag/1.4.3
    在这里插入图片描述
    下周好之后,把nacos-server解压启动,进入 bin目录,cmd执行 startup.cmd -m standalone ,代表单实例启动。如果嫌麻烦可以把 startup.cmd 中的 set MODE=“cluster” 改成 standalone ,以后就直接启动startup不再加参数了。在这里插入图片描述
    接下来是客户端了,我的项目结构如下:

    在这里插入图片描述
    我这里使用的SpringBoot版本是2.2.5 ; alibaba的版本是 2.2.1 ;父工程pom.xml管理如下

     <parent>
            <groupId> org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.2.5.RELEASEversion>
        parent>
    
    
        <dependencyManagement>
            <dependencies>
                
                <dependency>
                    <groupId>com.alibaba.cloudgroupId>
                    <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                    <version>2.2.1.RELEASEversion>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
                
                <dependency>
                    <groupId>org.springframework.cloudgroupId>
                    <artifactId>spring-cloud-dependenciesartifactId>
                    <version>Hoxton.SR3version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
        dependencyManagement>
    
    • 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

    nacos-client 依赖如下

        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    启动类如下

    //Ncaos客户端应用程序启动
    @SpringBootApplication
    public class NacosClientStarter {
    
        public static void main(String[] args) {
            SpringApplication.run(NacosClientStarter.class);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    配置文件中配置了一个服务名和 nacos注册中心地址

    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
      application:
        name: nacos-client
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后启动项目,nacos-client就会被注册到Nacos注册中心,如下:

    在这里插入图片描述
    OK到这里基础环境准备好了,相信对于你来说也是比较容易的事情。

    服务注册源码分析

    Nacos和Eureka都拥有服务注册,服务发现,心跳续约等功能,本篇文章主要讨论服务注册,也就是客户端启动时会主动向服务端注册自己,那么在服务端会形成一个服务注册表(其实就是一个Map)下面我们就来研究一下服务注册的流程是如何的。

    对SpringBoot有一定研究的同学都清楚SpringBoot的自动配置,SpringCloud或者说SpringCloudAlibaba的全是基于SpringBoot的自动配置来完成相关组件的装载。在External libraries 中找到我们导入的依赖 spring-cloud-starter-alibaba-nacos-discovery

    在这里插入图片描述
    然后展开找到META-INF/spring.factories 文件,内容如下:

    在这里插入图片描述
    NacosServerRegistryAutoConfiguration 是针对于服务注册的自动配置类。程序启动该类中的配置自动生效。除了该配置类,文件中还包括针对于服务发现的配置类和 config 的自动配置类。

    NacosServerRegistryAutoConfiguration 服务注册自动配置

    下面是NacosServerRegistryAutoConfiguration 的源码

    @Configuration(
        proxyBeanMethods = false
    )
    @EnableConfigurationProperties
    @ConditionalOnNacosDiscoveryEnabled
    //条件开关
    @ConditionalOnProperty(
        value = {"spring.cloud.service-registry.auto-registration.enabled"},
        matchIfMissing = true
    )
    @AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class})
    public class NacosServiceRegistryAutoConfiguration {
        public NacosServiceRegistryAutoConfiguration() {
        }
    
        @Bean
        public NacosServiceRegistry nacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
            return new NacosServiceRegistry(nacosDiscoveryProperties);
        }
    
        @Bean
        @ConditionalOnBean({AutoServiceRegistrationProperties.class})
        public NacosRegistration nacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties, ApplicationContext context) {
            return new NacosRegistration(nacosDiscoveryProperties, context);
        }
    	//开启自动注册
        @Bean
        @ConditionalOnBean({AutoServiceRegistrationProperties.class})
        public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) {
            return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);
        }
    }
    
    
    • 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
    • spring.cloud.service-registry.auto-registration.enabled:自动注册条件开关,默认是true

    • NacosServiceRegistry : Nacos服务注册器,客户端往服务端发送注册请求的整个过程是通过它来完成的,它实现于 ServiceRegistry 接口(EurekaClient也是的哦) 。复写了register服务注册方法。

    • NacosRegistration :可以看做是对要注册是服务的等级,其中包括服务的实例ID,注册地址等等

    • NacosAutoServiceRegistration :自动注册器,服务注册由它来触发的

    • NacosDiscoveryProperties :针对于Nacos的配置属性对象

    • NacosRegistration :根据nacos配置创建的注册的服务的登记对象,其中包含了注册的服务的serviceId,host,port等等

    NacosAutoServiceRegistration 自动注册

    NacosAutoServiceRegistration 继承了 AbstractAutoServiceRegistration,通过监听 WebServerInitializedEvent web服务初始化事件来调用 NacosAutoServiceRegistration#register 触发自动注册。该方法中会判断 register-enabled: true 开关来决定是否注册。如果开启自动注册,就会最终来调用 NacosServiceRegistry#register 方法触发服务注册。
    在这里插入图片描述
    start()方法内部会调用 NacosAutoServiceRegistration #register方法
    在这里插入图片描述
    而NacosAutoServiceRegistration #register 做了是否开启注册配置(spring.cloud.nacos.discovery.register-enabled=true)开关后,就调用super的注册方法注册,下面是 AbstractAutoServiceRegistration#register源码

    protected void register() {
    	this.serviceRegistry.register(getRegistration());
    }
    
    • 1
    • 2
    • 3

    这里的 serviceRegistry 就是最前面说到的 NacosServiceRegistry ,核心服务注册流程就在他里面。

    NacosServiceRegistry 服务注册流程

    下面是 NacosServiceRegistry#register 的源码

    @Override
    public void register(Registration registration) {
    	//判断是否有服务ID,也就是spring.application.name配置不能为空
    	if (StringUtils.isEmpty(registration.getServiceId())) {
    		log.warn("No service to register for nacos client...");
    		return;
    	}
    	//拿到服务ID
    	String serviceId = registration.getServiceId();
    	//拿到服务的组名
    	String group = nacosDiscoveryProperties.getGroup();
    	//封装好的服务实例
    	Instance instance = getNacosInstanceFromRegistration(registration);
    
    	try {
    		//注册实例
    		namingService.registerInstance(serviceId, group, instance);
    		log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
    				instance.getIp(), instance.getPort());
    	}
    	catch (Exception e) {
    		log.error("nacos registry, {} register failed...{},", serviceId,
    				registration.toString(), e);
    		// rethrow a RuntimeException if the registration is failed.
    		// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
    		rethrowRuntimeException(e);
    	}
    }
    
    • 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

    该方法会先判断服务 serviceId也就是服务名不能为空,然后拿到 group和Instance(服务实例对象),调用 NamingService#registerInstance来注册服务。下面是 NacosNamingService#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));
                beatInfo.setIp(instance.getIp());
                beatInfo.setPort(instance.getPort());
                beatInfo.setCluster(instance.getClusterName());
                beatInfo.setWeight(instance.getWeight());
                beatInfo.setMetadata(instance.getMetadata());
                beatInfo.setScheduled(false);
                beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
                this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
            }
    
            this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里就看的比较清楚了,这里会把服务的ip,端口,服务名等信息封装到 BeatInfo 对象中,beatReactor.addBeatInfo是把当前服务实例加入心跳机制(心跳续约),然后通过serverProxy.registerService注册。下面是NamingProxy#registerService的源码

    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()));
            this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");
        }
    	...省略...
    	public String reqAPI(String api, Map<String, String> params, String body, String method) throws NacosException {
            return this.reqAPI(api, params, body, this.getServerList(), method);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    最终把相关配置使用一个Map进行封装,然后交给reqAPI方法去发送POST请求给服务端,

    • /nacos/v1/ns/instance : 是nacos注册的接口,拼接上yaml中的nacos地址就是完成的注册地址了
    • this.getServerList() : 拿到所有的nacos服务端的地址,考虑到nacos-server集群的情况

    代码最终来到reqAPI方法

    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()) {
                	//使用随机数随机取一个nacos-server
                    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());
            }
        }
    
    • 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

    代码不难理解,reqAPI方法中会随机从多个Nacos服务器地址中取一个,然后发起注册请求,通过callServer方法完成。

    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;
            //拼接完整的URL: http://127.0.0.1:8848/nacos/v1/ns/instance
            if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {
                if (!curServer.contains(":")) {
                    curServer = curServer + ":" + this.serverPort;
                }
    
                url = HttpClient.getPrefix() + curServer + api;
            } else {
                url = curServer + api;
            }
    		//调用nacos的http客户端执行请求
            HttpClient.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);
            }
        }
    
    • 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

    方法中会对注册的地址进行拼接,如: http://127.0.0.1:8848/nacos/v1/ns/instance,然后低着参数,调用HttpClient.request执行请求;下面是:com.alibaba.nacos.client.naming.net.HttpClient#request 的源码,看得出来底层还是通过 JDK自带的HttpURLConnection来发送请求的

    public static HttpResult request(String url, List<String> headers, Map<String, String> paramValues, String body, String encoding, String method) {
            HttpURLConnection conn = null;
    
            HttpResult var8;
            try {
                String encodedContent = encodingParams(paramValues, encoding);
                url = url + (StringUtils.isEmpty(encodedContent) ? "" : "?" + encodedContent);
                conn = (HttpURLConnection)(new URL(url)).openConnection();
                setHeaders(conn, headers, encoding);
                conn.setConnectTimeout(CON_TIME_OUT_MILLIS);
                conn.setReadTimeout(TIME_OUT_MILLIS);
                conn.setRequestMethod(method);
                conn.setDoOutput(true);
                if (StringUtils.isNotBlank(body)) {
                    byte[] b = body.getBytes();
                    conn.setRequestProperty("Content-Length", String.valueOf(b.length));
                    conn.getOutputStream().write(b, 0, b.length);
                    conn.getOutputStream().flush();
                    conn.getOutputStream().close();
                }
    
                conn.connect();
                if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
                    LogUtils.NAMING_LOGGER.debug("Request from server: " + url);
                }
    
                var8 = getResult(conn);
                return var8;
            } catch (Exception var14) {
                try {
                    if (conn != null) {
                        LogUtils.NAMING_LOGGER.warn("failed to request " + conn.getURL() + " from " + InetAddress.getByName(conn.getURL().getHost()).getHostAddress());
                    }
                } catch (Exception var13) {
                    LogUtils.NAMING_LOGGER.error("[NA] failed to request ", var13);
                }
    
                LogUtils.NAMING_LOGGER.error("[NA] failed to request ", var14);
                var8 = new HttpResult(500, var14.toString(), Collections.emptyMap());
            } finally {
                IoUtils.closeQuietly(conn);
            }
    
            return var8;
        }
    
    
    • 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

    BeatReactor#addBeatInfo心跳续约

    在 NacosNamingService#registerInstance方法中把服务信息封装为一个 BeatInfo ,然后加入this.beatReactor.addBeatInfo 心跳机制。我们来看一下心跳是如何做的,下面是beatReactor.addBeatInfo的源码

     public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
            LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
            String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
            BeatInfo existBeat = null;
            if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
                existBeat.setStopped(true);
            }
    
            this.dom2Beat.put(key, beatInfo);
            //线程池,定时任务,5000毫秒发送一次心跳。beatInfo.getPeriod()是定时任务执行的频率
            this.executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
            MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
        }
    
       //心跳任务
       class BeatTask implements Runnable {
            BeatInfo beatInfo;
    
            public BeatTask(BeatInfo beatInfo) {
                this.beatInfo = beatInfo;
            }
    
           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);
                }
            }
       }
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    和Eureka一样,心跳也是通过线程池 ScheduledExecutorService 来实现的,时间频率默认是5秒一次。

    • BeatInfo : 心跳续约的对象,其中包括服务的IP,端口,服务名,权重等

    • executorService.schedule :定时任务,beatInfo.getPeriod()是定时任务执行频率,默认是5000 毫秒发送一次心跳续约请求到NacosServer

    • BeatTask :是一个Runnable线程,run方法中会调用 BeatReactor.this.serverProxy.sendBeat 发送心跳请求。

    • 通过http请求向nacos服务端发送心跳请求。接口为:/nacos/v1/ns/instance/beat。
      从返回结果中解析lightBeatEnabled和interval参数,lightBeatEnabled表示心跳是否需要携带心跳信息发送给nacos服务端。interval表示心跳间隔。

    • 如果返回码为RESOURCE_NOT_FOUND,则创建instance并且进行服务注册。
      最后触发下一次心跳请求任务。

    BeatTask作为心跳续约的线程对象,他的run方法中 通过 BeatReactor.this.serverProxy.sendBeat发送心跳,如果发现服务未注册会通过 BeatReactor.this.serverProxy.registerService 注册服务。

    下面是 com.alibaba.nacos.client.naming.net.NamingProxy#sendBeat 发送心跳的方法

     public JSONObject sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {
            if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
                LogUtils.NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", this.namespaceId, beatInfo.toString());
            }
    
            Map<String, String> params = new HashMap(8);
            String body = "";
            if (!lightBeatEnabled) {
                try {
                    body = "beat=" + URLEncoder.encode(JSON.toJSONString(beatInfo), "UTF-8");
                } catch (UnsupportedEncodingException var6) {
                    throw new NacosException(500, "encode beatInfo error", var6);
                }
            }
    
            params.put("namespaceId", this.namespaceId);
            params.put("serviceName", beatInfo.getServiceName());
            params.put("clusterName", beatInfo.getCluster());
            params.put("ip", beatInfo.getIp());
            params.put("port", String.valueOf(beatInfo.getPort()));
            String result = this.reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, body, "PUT");
            return JSON.parseObject(result);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这里也是会拼接好心跳的地址 :127.0.0.1:8848/nacos/v1/ns/instance/beat ,参数包括namespaceId命名空间ID;serviceName 服务ing;clusterName 集群名;ip 服务的IP;port 端口。然后发送一个PUT请求。底层依然是从多个NacosServer随机选择一个发起心跳请求。

    客户端就先分析到这里,为了梳理流程我这里画了一个图

    在这里插入图片描述

    总结

    1. NACOS就是使用SpringBoot自动装配完成相关组件的配置,在Nacos的自动配置类(NacosServerRegistryAutoConfiguration )中注册了服务注册器(NacosServiceRegistry )和自动注册器(NacosAutoServiceRegistration ),注册的服务登记对象(NacosRegistration)等。
    2. Nacos自动注册器通过监听应用启动时间来触发自动注册,他会判断一个自动注册开关(spring.cloud.service-registry.auto-registration.enabled)。满足自动注册就会调用NacosServiceRegistry 的register方法进行服务注册
    3. NacosServiceRegistry 把注册工作交给NacosNamingService去完成,在NacosNamingService中做了两件时间:1是把服务封装为BeatInfo加入心跳机制,2是完成服务的注册
    4. 对于心跳机制,通过BeatReactor 来完成,底层使用的是带定时任务的线程池5S一次心跳发送到服务端。
    5. 对于服务注册则是通过NamingProxy代理来完成,和心跳一样,都是使用随机选择一个NacosServer,然后拼接注册地址,交给HttpClient发送http请求。

    好吧,到这里就真的结束了,如果文章对你有帮助建议点赞收藏加好评。你的鼓励是我最大的动力

  • 相关阅读:
    C++ 异形窗口
    知乎问题:.NET AOT编译后能替代C++吗?
    【OPENVX】快速角点提取之 vxuFastCorners
    蓝眼开源云盘部署全过程(手动安装)
    kafka 第一次小整理(草稿篇)————演变[二]
    ⑩⑥ 【MySQL】详解 触发器TRIGGER,协助 确保数据的完整性,日志记录,数据校验等操作。
    微信小程序开发08 CICD:打造小程序的工程交付体系
    Shell 使用日期或计数器 命名 文件
    2024-Pop!_OS新版本,新桌面环境的消息
    高性能 MySQL 第四版(GPT 重译)(四)
  • 原文地址:https://blog.csdn.net/u014494148/article/details/127931250