Nacos注册中心源码解析-NamingService

Nacos注册中心架构图

Nacos注册中心源码解析

NacosServiceManager 的创建

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosServiceAutoConfiguration {

	@Bean
	public NacosServiceManager nacosServiceManager() {
		return new NacosServiceManager();
	}

}

NacosWatch 的创建

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(NacosDiscoveryAutoConfiguration.class)
public class NacosDiscoveryClientConfiguration {

	@Bean
	public DiscoveryClient nacosDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) {
		return new NacosDiscoveryClient(nacosServiceDiscovery);
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)
	public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) {
		return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
	}

}

ps:其中NacosServiceAutoConfiguration和NacosDiscoveryClientConfiguration使用一种类似与Java SPI的加载机制。它在 META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。

NacosWatch实现了SmartLifecycle的start()创建NacosNamingService

NamingService namingService = nacosServiceManager.getNamingService(properties.getNacosProperties());
public NamingService getNamingService(Properties properties) {
		if (Objects.isNull(this.namingService)) {
			buildNamingService(properties);
		}
		return namingService;
	}

buildNamingService()

private NamingService buildNamingService(Properties properties) {
		if (Objects.isNull(namingService)) {
			synchronized (NacosServiceManager.class) {
				if (Objects.isNull(namingService)) {
					namingService = createNewNamingService(properties);
				}
			}
		}
		return namingService;
	}

createNamingService()

private NamingService createNewNamingService(Properties properties) {
    try {
        return createNamingService(properties);
    }
    catch (NacosException e) {
        throw new RuntimeException(e);
    }
}
	
public static NamingService createNamingService(Properties properties) throws NacosException {
    return NamingFactory.createNamingService(properties);
}

//通过反射创建NamingService实例
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 e) {
        throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
    }
}

NacosNamingService 的构造方法

public NacosNamingService(Properties properties) throws NacosException {
    init(properties);
}

init()

private void init(Properties properties) throws NacosException {
    ValidatorUtils.checkInitParam(properties);
    // 初始化命名空间
    this.namespace = InitUtils.initNamespaceForNaming(properties);
    InitUtils.initSerialization();
    // 初始化Nacos注册中心服务地址
    initServerAddr(properties);
    // 初始化web上下文
    InitUtils.initWebRootContext(properties);
    // 初始化缓存目录
    initCacheDir();
    // 初始化日志文件
    initLogName(properties);

    //服务端的代理,用于客户端与服务端之间的通信
    this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
    //用于客户端与服务器之间的心跳通信
    this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
     //用于客户端服务的订阅,从服务端更新服务信息
    this.hostReactor = new HostReactor(this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties),
                                       isPushEmptyProtect(properties), initPollingThreadCount(properties));
}

initNamespaceForNaming()

public static String initNamespaceForNaming(Properties properties) {
    String tmpNamespace = null;

    //获取环境变量 isUseCloudNamespaceParsing、nacos.use.cloud.namespace.parsing、 默认值true
    String isUseCloudNamespaceParsing = properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
                       System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, 
                       String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));
 
    if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {

        //这里是ans,据说是注册中心,未设置tenant.id和ans.namespace 返回为空
        tmpNamespace = TenantUtil.getUserTenantForAns();
        
        //这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法
        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
            @Override
            public String call() {
                
                //获取环境变量 ans.namespace
                String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
                LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
                return namespace;
            }
        });

       
        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
            @Override
            public String call() {
                
                //获取环境变量 ALIBABA_ALIWARE_NAMESPACE
                String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
                LogUtils.NAMING_LOGGER.info("initializer namespace from System Environment :" + namespace);
                return namespace;
            }
        });
    }

    tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
        @Override
        public String call() {
            
            //获取环境变量 namespace
            String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
            LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
            return namespace;
        }
    });

   
    if (StringUtils.isEmpty(tmpNamespace) && properties != null) {
        //这里拿到我们外面设置的namespace
        tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
    }
    
    //这里如果前面tmpNamespace都是null,则返回默认的NAMESPACE:public
    tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
        @Override
        public String call() {
            //获取默认值 public
            return UtilAndComs.DEFAULT_NAMESPACE_ID;
        }
    });
    return tmpNamespace;
}

这个方法里面先去会判断是否使用isUseCloudNamespaceParsing,默认是true,然后回去检查是否用ans,ALIBABA_ALIWARE。同时会拿到我们最开始设置的namespace,如果为设置,则用默认的public。

initServerAddr()

private void initServerAddr(Properties properties) {
    //这里拿到我们前面填写的nacos服务端地址
    serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
    endpoint = InitUtils.initEndpoint(properties);
     //endpoint存在时,serverList 设置为空
    if (StringUtils.isNotEmpty(endpoint)) {
        serverList = "";
    }
}

initEndpoint()

public static String initEndpoint(final Properties properties) {
    if (properties == null) {

        return "";
    }
    // Whether to enable domain name resolution rules
    /**
    * 这里是去取end point的解析规则,即对传入的endpoint参数规则解析的能力可以是一个具体的值,也可以是一个占位符的形式
    * 1.endpoint.options 是一个具体的变量。支持从系统属性,系统环境变量中读取。
    * 2.defaultValue 是给出的一个默认值。当从具体的变量中没有被正确初始化时,会使用给出的默认值来初始化。
    *
    */
    String isUseEndpointRuleParsing = properties.getProperty(PropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,                     System.getProperty(SystemPropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
String.valueOf(ParamUtil.USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE)));

    boolean isUseEndpointParsingRule = Boolean.parseBoolean(isUseEndpointRuleParsing);
    String endpointUrl;
    if (isUseEndpointParsingRule) {
        // Get the set domain name information
        //获取配置 endpoint
        endpointUrl = ParamUtil.parsingEndpointRule(properties.getProperty(PropertyKeyConst.ENDPOINT));
        if (StringUtils.isBlank(endpointUrl)) {
            return "";
        }
    } else {
        endpointUrl = properties.getProperty(PropertyKeyConst.ENDPOINT);
    }

    if (StringUtils.isBlank(endpointUrl)) {
        return "";
    }

    String endpointPort = TemplateUtils
        .stringEmptyAndThenExecute(System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_PORT),
                                   new Callable<String>() {
                                       @Override
                                       public String call() {

                                           return properties.getProperty(PropertyKeyConst.ENDPOINT_PORT);
                                       }
                                   });

    //如果endpointPort为空,则返回8080
    endpointPort = TemplateUtils.stringEmptyAndThenExecute(endpointPort, new Callable<String>() {
        @Override
        public String call() {
            return "8080";
        }
    });

    return endpointUrl + ":" + endpointPort;
}

第一部分是设置serverList为我们最开始设置的服务端地址
第二部分设置我们的endpoint规则

InitUtils.initWebRootContext()

public static void initWebRootContext(Properties properties) {
    final String webContext = properties.getProperty(PropertyKeyConst.CONTEXT_PATH);
    TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
        @Override
        public void run() {
            UtilAndComs.webContext = ContextPathUtil.normalizeContextPath(webContext);
            UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
            UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
        }
    });
    initWebRootContext();
}

public static void initWebRootContext() {
    // support the web context with ali-yun if the app deploy by EDAS
    
    final String webContext = System.getProperty(SystemPropertyKeyConst.NAMING_WEB_CONTEXT);
    TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
        @Override
        public void run() {
            UtilAndComs.webContext = ContextPathUtil.normalizeContextPath(webContext);
            UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
            UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
        }
    });
}

这里如果应用由EDAS部署,则支持阿里云的web上下文

initCacheDir()

private void initCacheDir() {
    String jmSnapshotPath = System.getProperty("JM.SNAPSHOT.PATH");
    if (!StringUtils.isBlank(jmSnapshotPath)) {
        cacheDir =
            jmSnapshotPath + File.separator + "nacos" + File.separator + "naming" + File.separator + namespace;
    } else {
        cacheDir = System.getProperty("user.home") + File.separator + "nacos" + File.separator + "naming"
            + File.separator + namespace;
    }
}

这里初始化本地实例信息

NamingProxy

构造方法

public NamingProxy(String namespaceId, String endpoint, String serverList, Properties properties) {

    //设置了用户名和密码 同时初始化了nacosRestTemplate,nacosRestTemplate是客户端发送信息到服务端的类,里面用HttpClient实现
    this.securityProxy = new SecurityProxy(properties, nacosRestTemplate);
    this.properties = properties;
    this.setServerPort(DEFAULT_SERVER_PORT);
    this.namespaceId = namespaceId;
    this.endpoint = endpoint;
    
    //最大重试次数
    this.maxRetry = ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.NAMING_REQUEST_DOMAIN_RETRY_COUNT,
                                                              String.valueOf(UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT)));

    if (StringUtils.isNotEmpty(serverList)) {
        this.serverList = Arrays.asList(serverList.split(","));
        if (this.serverList.size() == 1) {
            this.nacosDomain = serverList;
        }
    }
    
    //初始化定时刷新任务
    this.initRefreshTask();
}

initRefreshTask()

private void initRefreshTask() {

    //初始化一个线程池
    this.executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("com.alibaba.nacos.client.naming.updater");
            t.setDaemon(true);
            return t;
        }
    });

    
    refreshSrvIfNeed();
    this.securityProxy.login(getServerList());

    this.executorService.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            //refreshSrvIfNeed去拿服务端serverList
            refreshSrvIfNeed();
        }
    }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);

    this.executorService.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            //securityProxy.login登陆到拿到的服务端列表
            securityProxy.login(getServerList());
        }
    }, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
}

refreshSrvIfNeed()

private void refreshSrvIfNeed() {
    try {

        //校验服务端列表serverList是否为空
        if (!CollectionUtils.isEmpty(serverList)) {
            NAMING_LOGGER.debug("server list provided by user: " + serverList);
            return;
        }

        //校验刷新时间间隔
        if (System.currentTimeMillis() - lastSrvRefTime < vipSrvRefInterMillis) {
            return;
        }

        //调用远程接口获取服务端列表 "http://" + endpoint + "/nacos/serverlist"
        List<String> list = getServerListFromEndpoint();

        if (CollectionUtils.isEmpty(list)) {
            throw new Exception("Can not acquire Nacos list");
        }

        if (!CollectionUtils.isEqualCollection(list, serversFromEndpoint)) {
            NAMING_LOGGER.info("[SERVER-LIST] server list is updated: " + list);
        }

        //更新服务列表serversFromEndpoint
        serversFromEndpoint = list;
        //更新最新刷新时间
        lastSrvRefTime = System.currentTimeMillis();
    } catch (Throwable e) {
        NAMING_LOGGER.warn("failed to update server list", e);
    }
}

login( )

public boolean login(List<String> servers) {

    try {
        //校验刷新时间间隔
        if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS.toMillis(tokenTtl - tokenRefreshWindow)) {
            return true;
        }

        //尝试登录nacos服务列表
        for (String server : servers) {
            if (login(server)) {
                lastRefreshTime = System.currentTimeMillis();
                return true;
            }
        }
    } catch (Throwable throwable) {
        SECURITY_LOGGER.warn("[SecurityProxy] login failed, error: ", throwable);
    }

    return false;
}

new BeatReactor(this.serverProxy,initClientBeatThreadCount(properties))

initClientBeatThreadCount()

private int initClientBeatThreadCount(Properties properties) {
    
    if (properties == null) {
        return UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT;
    }

    return ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.NAMING_CLIENT_BEAT_THREAD_COUNT),UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
}

public static final int DEFAULT_CLIENT_BEAT_THREAD_COUNT =
    Runtime.getRuntime().availableProcessors() > 1 ? Runtime.getRuntime().availableProcessors() / 2 : 1;
    

通过 Runtime.getRuntime().availableProcessors()方法拿到Java虚拟机的可用的处理器数量,下面我们看看构造方法

BeatReactor
构造方法

public BeatReactor(NamingProxy serverProxy, int threadCount) {
    this.serverProxy = serverProxy;
    this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            thread.setName("com.alibaba.nacos.naming.beat.sender");
            return thread;
        }
    });
}

这里只是初始化了线程池,本身这个BeatReactor有一个内部类BeatTask执行本地实例注册到服务端做心跳检测

class BeatTask implements Runnable {
    ....
}

BeatReactor周期性(默认5s执行一次心跳)的进行服务的上报,以便让nacos服务端能够实时感知服务的状态。如果一段时间内未进行上报,nacos服务端会移除或下线该服务的注册。

new HostReactor(this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties),isPushEmptyProtect(properties), initPollingThreadCount(properties));

isLoadCacheAtStart()

private boolean isLoadCacheAtStart(Properties properties) {
    boolean loadCacheAtStart = false;
    if (properties != null && StringUtils
        .isNotEmpty(properties.getProperty(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START))) {
        loadCacheAtStart = ConvertUtils
            .toBoolean(properties.getProperty(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START));
    }

    return loadCacheAtStart;
}

这个方法比较简单,只是设置了是否加载本地缓存,下面我们看看构造方法

public HostReactor(NamingProxy serverProxy, BeatReactor beatReactor, String cacheDir, boolean loadCacheAtStart,
                   boolean pushEmptyProtection, int pollingThreadCount) {
    // init executorService
    this.executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            thread.setName("com.alibaba.nacos.client.naming.updater");
            return thread;
        }
    });

    this.beatReactor = beatReactor;
    this.serverProxy = serverProxy;
    this.cacheDir = cacheDir;
    if (loadCacheAtStart) {
        this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
    } else {
        this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
    }
    this.pushEmptyProtection = pushEmptyProtection;
    this.updatingMap = new ConcurrentHashMap<String, Object>();
    this.failoverReactor = new FailoverReactor(this, cacheDir);
    this.pushReceiver = new PushReceiver(this);
    this.notifier = new InstancesChangeNotifier();

    NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
    NotifyCenter.registerSubscriber(notifier);
}

这里初始化了一些本地缓存的内容,我们主要看看FailoverReactor和PushReceive

FailoverReactor

public FailoverReactor(HostReactor hostReactor, String cacheDir) {
    this.hostReactor = hostReactor;
    this.failoverDir = cacheDir + "/failover";
    // init executorService
    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.failover");
            return thread;
        }
    });
    this.init();
}

public void init() {

    //故障转移开关检测
    executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);

    //将从nacos服务端获取的服务列表写入故障转移文件
    executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);

    // backup file on startup if failover directory is empty.
    executorService.schedule(new Runnable() {
        @Override
        public void run() {
            try {
                File cacheDir = new File(failoverDir);

                if (!cacheDir.exists() && !cacheDir.mkdirs()) {
                    throw new IllegalStateException("failed to create cache dir: " + failoverDir);
                }

                File[] files = cacheDir.listFiles();
                if (files == null || files.length <= 0) {
                    new DiskFileWriter().run();
                }
            } catch (Throwable e) {
                NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
            }

        }
    }, 10000L, TimeUnit.MILLISECONDS);
}

这里是操作本地实例信息的一些线程,FailoverReactor通过一个文件配置激活failover模式。该模式下,会从本地文件中读取服务列表信息。

PushReceiver

使用UDP方式用于接收Nacos服务端的推送,并更新到serviceInfoMap当中

public PushReceiver(HostReactor hostReactor) {
    try {
        this.hostReactor = hostReactor;
        String udpPort = getPushReceiverUdpPort();
        if (StringUtils.isEmpty(udpPort)) {
            this.udpSocket = new DatagramSocket();
        } else {
            this.udpSocket = new DatagramSocket(new InetSocketAddress(Integer.parseInt(udpPort)));
        }
        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;
            }
        });

        //执行当前类对象,本身实现了Runnable接口
        this.executorService.execute(this);
    } catch (Exception e) {
        NAMING_LOGGER.error("[NA] init udp socket failed", e);
    }
}

@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);

            //接收nacos服务端UDP消息
            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)) {
                
                //将接收到的服务信息更新到serviceInfoMap中
                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);
        }
    }
}

run方法使用while true循环来执行udpSocket.receive(packet),之后将接收到的数据解析为PushPacket,然后根据不同pushPacket.type做不同处理

  • 当pushPacket.type为dom或者service的时候会调用hostReactor.processServiceJSON(pushPacket.data);

  • 当pushPacket.type为dump的时候会将hostReactor.getServiceInfoMap()序列化到ack中,最后将ack返回回去

至此,初始化工作就完成了。

注册namespace

registerInstance()

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    //临时节点
    if (instance.isEphemeral()) {
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        // BeatTask加入到线程池中,线程池中线程注册实例到服务端
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

registerInstance方法第一步获取GroupedName,然后看instance是否短暂的,如果是执行beatReactor.addBeatInfo方法,注册及监控,最后也是通过serverProxy注册namespace

addBeatInfo()

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
    String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
    BeatInfo existBeat = null;
    //fix #1733
    if ((existBeat = dom2Beat.remove(key)) != null) {
        existBeat.setStopped(true);
    }
    dom2Beat.put(key, beatInfo);
    executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
    MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

先是buildKey生成key,查看beatinfo是否存在,不存在put;然后执行BeatTask,我们来看看BeatTask的run方法

@Override
public void run() {
    if (beatInfo.isStopped()) {
        return;
    }
    long nextTime = beatInfo.getPeriod();
    try {
        JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
        long interval = result.get("clientBeatInterval").asLong();
        boolean lightBeatEnabled = false;
        if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {
            lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean();
        }
        BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
        if (interval > 0) {
            nextTime = interval;
        }
        int code = NamingResponseCode.OK;
        if (result.has(CommonParams.CODE)) {
            code = result.get(CommonParams.CODE).asInt();
        }
        if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
            Instance instance = new Instance();
            instance.setPort(beatInfo.getPort());
            instance.setIp(beatInfo.getIp());
            instance.setWeight(beatInfo.getWeight());
            instance.setMetadata(beatInfo.getMetadata());
            instance.setClusterName(beatInfo.getCluster());
            instance.setServiceName(beatInfo.getServiceName());
            instance.setInstanceId(instance.getInstanceId());
            instance.setEphemeral(true);
            try {
                //注册实例到服务端 
                serverProxy.registerService(beatInfo.getServiceName(), NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
            } catch (Exception ignore) {
            }
        }
    } catch (NacosException ex) {
        NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",
                            JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());

    } catch (Exception unknownEx) {
        NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, unknown exception msg: {}",
                            JacksonUtils.toJson(beatInfo), unknownEx.getMessage(), unknownEx);
    } finally {
        executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
    }
}        

这个方法里面最重要两步就是第一步sendBeat发送心跳,第二步通过reqApi注册实例到服务端

registerService()


/**
* 配置参数,然后通过reqApi注册实例到服务端
*/
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,instance);

    final Map<String, String> params = new HashMap<String, String>(16);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, 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", JacksonUtils.toJson(instance.getMetadata()));
    
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
                     String method) throws NacosException {

    params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

    if (CollectionUtils.isEmpty(servers) && StringUtils.isBlank(nacosDomain)) {
        throw new NacosException(NacosException.INVALID_PARAM, "no server available");
    }

    NacosException exception = new NacosException();

    if (StringUtils.isNotBlank(nacosDomain)) {
        for (int i = 0; i < maxRetry; i++) {
            try {
                return callServer(api, params, body, nacosDomain, method);
            } catch (NacosException e) {
                exception = e;
                if (NAMING_LOGGER.isDebugEnabled()) {
                    NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);
                }
            }
        }
    } else {
        Random random = new Random(System.currentTimeMillis());
        int index = random.nextInt(servers.size());

        for (int i = 0; i < servers.size(); i++) {
            String server = servers.get(index);
            try {
                return callServer(api, params, body, server, method);
            } catch (NacosException e) {
                exception = e;
                if (NAMING_LOGGER.isDebugEnabled()) {
                    NAMING_LOGGER.debug("request {} failed.", server, e);
                }
            }
            index = (index + 1) % servers.size();
        }
    }

    NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
                        exception.getErrMsg());

    throw new NacosException(exception.getErrCode(),
                             "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());

}

public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer,
                         String method) throws NacosException {
    long start = System.currentTimeMillis();
    long end = 0;
    injectSecurityInfo(params);
    Header header = builderHeader();

    String url;
    if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {
        url = curServer + api;
    } else {
        if (!IPUtil.containsPort(curServer)) {
            curServer = curServer + IPUtil.IP_PORT_SPLITER + serverPort;
        }
        url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
    }

    try {
        HttpRestResult<String> restResult = nacosRestTemplate
            .exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);
        end = System.currentTimeMillis();

        MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(restResult.getCode()))
            .observe(end - start);

        if (restResult.ok()) {
            return restResult.getData();
        }
        if (HttpStatus.SC_NOT_MODIFIED == restResult.getCode()) {
            return StringUtils.EMPTY;
        }
        throw new NacosException(restResult.getCode(), restResult.getMessage());
    } catch (Exception e) {
        NAMING_LOGGER.error("[NA] failed to request", e);
        throw new NacosException(NacosException.SERVER_ERROR, e);
    }
}

通过最开始初始化的nacosRestTemplate发送的请求。

posted @ 2022-11-16 22:33  snail灬  阅读(547)  评论(0编辑  收藏  举报