美文网首页Spring Cloud Alibaba
Spring Cloud Alibaba Nacos 服务注册-

Spring Cloud Alibaba Nacos 服务注册-

作者: 文艺小程序员 | 来源:发表于2020-07-08 17:24 被阅读0次

    好久不见,最近工作一直被一堆事所捆绑,也没有大块的时间去看技术相关的东西,以至于部分伙伴的留言都没有及时的回复,先和大家分享一句话突然想起来的一句吧,不变的可能是这个世界,不断变化的可能是我们自己。

    微服务架构在近几年变的越来越流行,这可能会成为我们的一个必备技能(本人的建议是如果现有的系统没有遇到什么运维、业务瓶颈的话不要为了单纯的使用微服务技术而去进行改造)。

    在微服务的早期随着Spring Cloud的支撑,好多都使用了Spring默认的Netflix全家桶,但是现在Netflix也不再进行维护且社区活跃度也比较低,所以在注册中心部分很多团队都会在Nacos、Zookeeper、Kubernetes中进行选择,本文不对其中的优缺点进行比较和分析,直接来干货Spring Cloud Alibaba Nacos 服务如何注册。

    一、服务注册初始化阶段
    其实对于服务的注册基本上所有开源项目的实现思路基本一致,无非就是服务的注册、心跳维持,看下图直接找到服务注册的源码,一般这种代码我都是从AutoConfig开始看了,大家可以先大体看一下,这几个AutoConfig功能其实就是初始化红框内的其他的的非AutoConfig类。

    源码结构图
    项目启动以后首先会通过NacosDiscoveryAutoConfiguration根据配置文件初始化NacosDiscoveryProperties,NacosDiscoveryProperties为配置文件中配置的服务注册相关参数(如serverAddr、group等),然后紧接着初始化NacosServiceDiscovery,该类主要是通过的NacosDiscoveryProperties的namingServiceInstance属性实现获取注册中心的相关信息。最后初始化NacosDiscoveryClient对NacosServiceDiscovery进行封装。

    在服务注册方面,会通过NacosServiceRegistryAutoConfiguration依次初始化NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration,在初始化NacosServiceRegistry时会对之前的NacosDiscoveryProperties的namingServiceInstance属性进行实例化,其中服务的注册的核心代码(com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register)也在该类中。在初始化AbstractAutoServiceRegistration时候,大家会发现它的父类实现了ApplicationListener,当发布事件时候会执行onApplicationEvent中的代码(可以参考我伙伴的文章),这也是服务注册的入口。

    二、服务注册阶段
    在AbstractAutoServiceRegistration的onApplicationEvent方法中执行了start方法,start方法中会执行register方法,最终调用的方法就是之前说到的com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register,完成对服务进行注册。

    public void register(Registration registration) {
            if (StringUtils.isEmpty(registration.getServiceId())) {
                log.warn("No service to register for nacos client...");
            } else {
                String serviceId = registration.getServiceId();
                String group = this.nacosDiscoveryProperties.getGroup();
                Instance instance = this.getNacosInstanceFromRegistration(registration);
    
                try {
                    this.namingService.registerInstance(serviceId, group, instance);
                    log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
                } catch (Exception var6) {
                    log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var6});
                    ReflectionUtils.rethrowRuntimeException(var6);
                }
    
            }
        }
    

    在服务注册的代码中,最核心的是this.namingService.registerInstance(serviceId, group, instance);这一行,最终实现注册的方法是com.alibaba.nacos.client.naming.net.NamingProxy#registerService,在这个方法中通过调用nacas的api包中的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()));
            this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");
        }
    
    
    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);
            }
        }
    

    在reqAPI的最后部分的代码com.alibaba.nacos.client.naming.net.NamingProxy#callServer(java.lang.String, java.util.Map<java.lang.String,java.lang.String>, java.lang.String, java.lang.String, java.lang.String)中,与服务端进行交互的过程中,会将自身的信息发送给服务端,服务端响应OK后完成注册,请求服务端的信息如下。

    地址:http://192.168.***.***:8848/nacos/v1/ns/instance
    内容:{app=nacos-provider, metadata={"preserved.register.source":"SPRING_CLOUD"}, ip=192.168.***.***, weight=1.0, ephemeral=true, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8, groupName=DEFAULT_GROUP, namespaceId=public, port=8081, enable=true, healthy=true, clusterName=DEFAULT}
    

    三、心跳的维持
    服务注册到注册中心服务端后,服务端与客户端会一直保持一个心跳,来对服务的可用性进行维护,在之前的部分我们有说过NacosServiceRegistry类中会对namingService进行实例化,同样也是利用nacos的api包通过反射来进行初始化。

      public static NamingService createNamingService(Properties properties) throws NacosException {
            try {
                Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
                Constructor constructor = driverImplClass.getConstructor(Properties.class);
                NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
                return vendorImpl;
            } catch (Throwable var4) {
                throw new NacosException(-400, var4);
            }
        }
    

    在对NacosNamingService进行初始化的过程中,我们会在构造函数中发现他还会初始化com.alibaba.nacos.client.naming.NacosNamingService#beatReactor,而BeatReactor中存在一个线程池,我们在服务注册之前(com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)方法)就会向这个线程池中发布Task。

    public BeatReactor(NamingProxy serverProxy, int threadCount) {
            this.lightBeatEnabled = false;
            this.dom2Beat = new ConcurrentHashMap();
            this.serverProxy = serverProxy;
            this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    thread.setName("com.alibaba.nacos.naming.beat.sender");
                    return thread;
                }
            });
        }
    
    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);
            this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
            MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
        }
    

    下面我们在看一下这个定时任务中的内容,其实与服务的注册原理相似,都是与服务端进行一个交互,如果服务端响应该服务不可用,则重新进行注册,如果依旧可用(或已经重新注册)则继续将新的Task(BeatTask)放入到线程池中,从而不断的与服务端维持心跳。

    地址:http://192.168.***.***:8848/nacos/v1/ns/instance/beat
    内容:{app=nacos-provider, namespaceId=public, port=8081, clusterName=DEFAULT, ip=192.168.***.***, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8}
    
        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);
                }
            }
        }
    

    小结
    上文介绍了Spring Cloud Alibaba Nacos 服务注册的实现原理与源码解析,相信大家应该大体了解了服务是如何注册到Nacos中,但是可能大家接下来还有疑惑,我们在服务调用的时候,是从哪里获取的所要调用的服务可用列表,以及这个列表是如何与服务端共同维护的,请听下回分解。

    相关文章

      网友评论

        本文标题:Spring Cloud Alibaba Nacos 服务注册-

        本文链接:https://www.haomeiwen.com/subject/iibqcktx.html