Nacos客户端namespace初始化及注册流程

很多人在学习开源的时候,无从下手,代码那么多,从哪个地方开始呢?我们学习nacos,首先去到nocas的github源码的地方链接: https://github.com/alibaba/nacos下载源码到我们的idea,打开example项目,

在这里插入图片描述

进入APP,可以看到如下代码:

   public static void main(String[] args) throws NacosException {
        Properties properties = new Properties();
        properties.setProperty("serverAddr", "21.34.53.5:8848,21.34.53.6:8848");
        properties.setProperty("namespace", "quickStart");
        NamingService naming = NamingFactory.createNamingService(properties);
        naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
        naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT");
        System.out.println(naming.getAllInstances("nacos.test.3"));
    }

这里我们可以看到,这里构建了一个NamingService实例,同时设置了我们的nacos服务端的地址和端口,设置namespace。
我们进入createNamingService方法

NamingService

    /**
     * Create a new naming service.
     *
     * @param properties naming service properties
     * @return new naming service
     * @throws NacosException nacos exception
     */
    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);
        }
    }

这里通过反射创建了一个NamingService实例,实际的实现类是在api项目里面的NacosNamingService,随后我们进入NacosNamingService看看

NacosNamingService

1.先看里面的属性
namespace 名字空间
endpoint 服务管理服务端地址管理服务器地址,获取服务管理服务端地址(当 nacos server 集群需要扩缩容时,客户端需要有一种能力能够及时感知到集群发生变化,及时感知到集群的变化是通过 endpoint 来实现的。也即客户端会定时的向endpoint发送请求来更新客户端内存中的集群列表。)
serverList 服务管理服务端地址,可直接配置,或从endpoint获取
cacheDir 调用服务信息本地文件缓存地址
logName 暂未使用
HostReactor 客户端关心的服务的实例信息,推拉模式的更新,failover服务实例信息读写管理
BeatReactor 本地实例信息心跳
EventDispatcher 服务信息变更监听回调处理
NamingProxy 服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试

 /**
     * Each Naming service should have different namespace.
     * 名字空间
     */
    private String namespace;

    /**
     * 当 nacos server 集群需要扩缩容时,客户端需要有一种能力能够及时感知到集群发生变化。
     * 及时感知到集群的变化是通过 endpoint 来实现的。也即客户端会定时的向 endpoint 发送请求来更新客户端内存中的集群列表。
     * 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
     */
    private String endpoint;

    /**
     * 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
     */
    private String serverList;

    /**
     * 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
     */
    private String cacheDir;

    private String logName;

    /**
     * 客户端关心的服务的实例信息,推拉模式的更新,failover服务实例信息读写管理
     */
    private HostReactor hostReactor;

    /**
     * 本地实例信息心跳
     */
    private BeatReactor beatReactor;

    /**
     * 服务信息变更监听回调处理
     */
    private EventDispatcher eventDispatcher;

    /**
     * 服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
     */
    private NamingProxy serverProxy;

了解了相关字段的意思我们来看看构造方法

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

这里其实就是执行init初始化方法

   private void init(Properties properties) throws NacosException {
        ValidatorUtils.checkInitParam(properties); //检查contextPath格式 可为空
        this.namespace = InitUtils.initNamespaceForNaming(properties); //初始化命名空间
        //子类实现类中的静态代码串中已经向Jackson进行了注册,但是由于classloader的原因,只有当 该子类被使用的时候,才会加载该类。
        // 这可能会导致Jackson先进性反序列化,再注册子类,从而导致 反序列化失败。
        //所以这里将NoneSelector、ExpressionSelector这两个类进行注册或者销毁
        InitUtils.initSerialization();

        //这里进行nacos服务端地址初始化
        //这里面会涉及到是否启用endpoint
        initServerAddr(properties);

        //如果应用由EDAS部署,则支持阿里云的web上下文
        InitUtils.initWebRootContext();

        //这里初始化本地缓存的路径及存放的registerInstance的内容
        initCacheDir();

        //初始化LogName,未设置用naming.log
        initLogName(properties);

        /**
         *初始化ExecutorService线程池,创建名字为com.alibaba.nacos.naming.client.listener的daemon线程Notifier
         * EventDispatcher中有一个LinkedBlockingQueue队列,放的是ServiceInfo
         * EventDispatcher中有ConcurrentMap>放入的是EventListener
         *Notifier中run方法解析
         *                  先去队列中弹出队顶元素(poll方法)
         *                  如果为空进行下一次循环
         *                  如果不为空则去ConcurrentMap取listeners
         *                  去除listener去监听NamingEvent
         *
        */
        this.eventDispatcher = new EventDispatcher();

        /**
         * 初始化服务代理,用户名密码服务地址及initRefreshTask任务的线程池,创建com.alibaba.nacos.client.naming.updater名字的daemon线程
         */
        this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);

        /**
         * initClientBeatThreadCount(properties):Runtime.getRuntime().availableProcessors()返回到Java虚拟机的可用的处理器数量
         * 创建一个此案城池com.alibaba.nacos.naming.beat.sender的daemon线程
         */
        this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
        /**
         * 同上
         */
        this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
                isLoadCacheAtStart(properties), initPollingThreadCount(properties));
    }

innit方法里面在初始化各个模块,具体的步骤是
1.检查contextPath格式
2.将NoneSelector、ExpressionSelector这两个类进行注册或者销毁
3.nacos服务端地址初始化
4.如果应用由EDAS部署,则支持阿里云的web上下文
5.这里初始化本地缓存
6.初始化LogName
7.初始化服务信息变更监听回调处理
8.初始化服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
9.初始化本地实例信息心跳
10.初始化客户端关心的服务的实例信息
说明:7-10都会初始化线程池,创建daemon线程
总的来说,init方法为我们初始化各种本地信息,下面来看具体初始化方法

ValidatorUtils.checkInitParam(properties)

public static final String CONTEXT_PATH = "contextPath";
   
    private static final Pattern CONTEXT_PATH_MATCH = Pattern.compile("(\\/)\\1+");
    
    public static void checkInitParam(Properties properties) throws NacosException {
        checkContextPath(properties.getProperty(PropertyKeyConst.CONTEXT_PATH));
    }
    
    /**
     * Check context path.
     *
     * @param contextPath context path
     */
    public static void checkContextPath(String contextPath) {
        if (contextPath == null) {
            return;
        }
        Matcher matcher = CONTEXT_PATH_MATCH.matcher(contextPath);
        if (matcher.find()) {
            throw new IllegalArgumentException("Illegal url path expression");
        }
    }
    

这里的代码比较简单,只是检查了一下contextPath

InitUtils.initNamespaceForNaming(properties)

    /**
     * Add a difference to the name naming. This method simply initializes the namespace for Naming. Config
     * initialization is not the same, so it cannot be reused directly.
     *
     * 为名称命名添加差异。此方法简单地初始化命名空间以进行命名。配置初始化不一样,所以不能直接重用。
     *
     * @param properties properties
     * @return namespace
     */
    public static String initNamespaceForNaming(Properties properties) {
        String tmpNamespace = null;

        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)));//默认是true
        System.out.println("isUseCloudNamespaceParsing:" + isUseCloudNamespaceParsing);
        if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {

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

                    return namespace;
                }
            });

            /**
             * 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
             * call()方法里面去取ALIBABA_ALIWARE_NAMESPACE环境变量
             */
            tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable() {
                @Override
                public String call() {
                    String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
                    LogUtils.NAMING_LOGGER.info("initializer namespace from System Environment :" + namespace);
                    return namespace;
                }
            });
        }

        /**
         * 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
         * call()方法里面去取NAMESPACE属性
         */
        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable() {
            @Override
            public String call() {
                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() {
            @Override
            public String call() {
                return UtilAndComs.DEFAULT_NAMESPACE_ID;
            }
        });
        return tmpNamespace;
    }

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

InitUtils.initSerialization()

 /**
     * Register subType for serialization.
     *
     * 

* Now these subType implementation class has registered in static code. But there are some problem for classloader. * The implementation class will be loaded when they are used, which will make deserialize before register. *

* *

* 子类实现类中的静态代码串中已经向Jackson进行了注册,但是由于classloader的原因,只有当 该子类被使用的时候,才会加载该类。这可能会导致Jackson先进性反序列化,再注册子类,从而导致 反序列化失败。 *

*/ public static void initSerialization() { // TODO register in implementation class or remove subType JacksonUtils.registerSubtype(NoneSelector.class, SelectorType.none.name()); JacksonUtils.registerSubtype(ExpressionSelector.class, SelectorType.label.name()); }

这里很简单,主要是为了防止反序列化失败

initServerAddr(properties)

   private void initServerAddr(Properties properties) {
        //这里拿到我们前面填写的nacos服务端地址
        serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR);

        endpoint = InitUtils.initEndpoint(properties);
        if (StringUtils.isNotEmpty(endpoint)) {
            serverList = "";
        }
    }
  /**
     * Init end point.
     *
     * @param properties properties
     * @return end point
     */
    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)));

        //isUseEndpointParsingRule的值决定是否启用endpoint解析规则
        boolean isUseEndpointParsingRule = Boolean.parseBoolean(isUseEndpointRuleParsing);
        String endpointUrl;
        if (isUseEndpointParsingRule) {//如果启用解析规则
            // Get the set domain name information
            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() {
                            @Override
                            public String call() {

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

        endpointPort = TemplateUtils.stringEmptyAndThenExecute(endpointPort, new Callable() {
            @Override
            public String call() {
                return "8080";
            }
        });

        return endpointUrl + ":" + endpointPort;
    }

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

InitUtils.initWebRootContext()

   /**
     * Init web root context.
     */
    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 = webContext.indexOf("/") > -1 ? webContext : "/" + webContext;

                UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
                UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
            }
        });
    }

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

initCacheDir()

private void initCacheDir() {
    cacheDir = System.getProperty("com.alibaba.nacos.naming.cache.dir");
    if (StringUtils.isEmpty(cacheDir)) {
        cacheDir = System.getProperty("user.home") + "/nacos/naming/" + namespace;
    }
}

这里初始化本地实例信息,在本地你会看到这样的文件
C:\Users\nacos\naming\quickStart
[图片上传失败...(image-bbdedd-1600251861321)]

initLogName(properties)

    private void initLogName(Properties properties) {
        logName = System.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME);
        if (StringUtils.isEmpty(logName)) {

            if (properties != null && StringUtils
                    .isNotEmpty(properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME))) {
                logName = properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME);
            } else {
                logName = "naming.log";
            }
        }
    }

这里设置logname,目前没看到哪里用

new EventDispatcher()

    public EventDispatcher() {
        
        this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "com.alibaba.nacos.naming.client.listener");
                thread.setDaemon(true);
                
                return thread;
            }
        });
        
        this.executor.execute(new Notifier());
    }

我们看到构造方法里面初始化了一个线程池,并且加入了Notifier这个线程,我们来看看Notifier的run方法

@Override
        public void run() {
            while (!closed) {
                
                ServiceInfo serviceInfo = null;
                try {
                    serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
                } catch (Exception ignore) {
                }
                
                if (serviceInfo == null) {
                    continue;
                }
                
                try {
                    List listeners = observerMap.get(serviceInfo.getKey());
                    
                    if (!CollectionUtils.isEmpty(listeners)) {
                        for (EventListener listener : listeners) {
                            List hosts = Collections.unmodifiableList(serviceInfo.getHosts());
                            listener.onEvent(new NamingEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
                                    serviceInfo.getClusters(), hosts));
                        }
                    }
                    
                } catch (Exception e) {
                    NAMING_LOGGER.error("[NA] notify error for service: " + serviceInfo.getName() + ", clusters: "
                            + serviceInfo.getClusters(), e);
                }
            }
        }

先去队列中弹出队顶元素(poll方法)
如果为空进行下一次循环
如果不为空则去ConcurrentMap取listeners
取出listener去监听NamingEvent

new NamingProxy(this.namespace, this.endpoint, this.serverList, properties)

    public NamingProxy(String namespaceId, String endpoint, String serverList, Properties properties) {
        
        this.securityProxy = new SecurityProxy(properties, nacosRestTemplate);
        this.properties = properties;
        this.setServerPort(DEFAULT_SERVER_PORT);
        this.namespaceId = namespaceId;
        this.endpoint = endpoint;
        if (StringUtils.isNotEmpty(serverList)) {
            this.serverList = Arrays.asList(serverList.split(","));
            if (this.serverList.size() == 1) {
                this.nacosDomain = serverList;
            }
        }
        this.initRefreshTask();
    }

这里的初始化动作很多 我们一个一个看
首先看new SecurityProxy(properties, nacosRestTemplate)

    /**
     * Construct from properties, keeping flexibility.
     *
     * @param properties a bunch of properties to read
     */
    public SecurityProxy(Properties properties, NacosRestTemplate nacosRestTemplate) {
        username = properties.getProperty(PropertyKeyConst.USERNAME, StringUtils.EMPTY);
        password = properties.getProperty(PropertyKeyConst.PASSWORD, StringUtils.EMPTY);
        contextPath = properties.getProperty(PropertyKeyConst.CONTEXT_PATH, "/nacos");
        contextPath = contextPath.startsWith("/") ? contextPath : "/" + contextPath;
        this.nacosRestTemplate = nacosRestTemplate;
    }

这里设置了用户名和密码同时初始化了nacosRestTemplate,nacosRestTemplate是客户端发送信息到服务端的类,里面用HttpClient实现,有兴趣的可以去看看
再看看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;
            }
        });
        
        this.executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                refreshSrvIfNeed();
            }
        }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
        
        this.executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                securityProxy.login(getServerList());
            }
        }, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
        
        refreshSrvIfNeed();
        this.securityProxy.login(getServerList());
    }

首先初始化一个线程池,同时refreshSrvIfNeed去拿服务端serverList,同时securityProxy.login登陆到拿到的服务端列表。

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

我们先看看initClientBeatThreadCount方法

    public static final int DEFAULT_CLIENT_BEAT_THREAD_COUNT =
            Runtime.getRuntime().availableProcessors() > 1 ? Runtime.getRuntime().availableProcessors() / 2 : 1;
   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);
    }

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

    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执行本地实例注册到服务端做心跳检测

new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(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(EventDispatcher eventDispatcher, NamingProxy serverProxy, BeatReactor beatReactor,
            String cacheDir, boolean loadCacheAtStart, 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.eventDispatcher = eventDispatcher;
        this.beatReactor = beatReactor;
        this.serverProxy = serverProxy;
        this.cacheDir = cacheDir;
        if (loadCacheAtStart) {
            this.serviceInfoMap = new ConcurrentHashMap(DiskCache.read(this.cacheDir));
        } else {
            this.serviceInfoMap = new ConcurrentHashMap(16);
        }
        
        this.updatingMap = new ConcurrentHashMap();
        this.failoverReactor = new FailoverReactor(this, cacheDir);
        this.pushReceiver = new PushReceiver(this);
    }
    

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

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

这里主要看run方法

 @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) {
                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 {
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        /**
         *ephemeral
         *短暂的
         */
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            /**
             * BeatTask加入到线程池中,线程池中线程注册实例到服务端
             *
             * 利用prometheus监控
             */
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        //这里也是注册实例到服务端,beatReactor里面也是本地实例心跳
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

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

    /**
     * Add beat information.
     *
     * @param serviceName service name
     * @param beatInfo    beat information
     */
    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方法

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 {
                        /**
                         * 注册实例到服务端  reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
                         */
                        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());

            }
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }

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

 /**
     * register a instance to service with specified instance properties.
     *
     * @param serviceName name of service
     * @param groupName   group of service
     * @param instance    instance to register
     * @throws NacosException nacos exception
     */
    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 params = new HashMap(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);
        
    }

这里主要配置参数,然后通过reqApi注册实例到服务端,注册实例到服务端代码如下

/**
     * Call server.
     *
     * @param api       api
     * @param params    parameters
     * @param body      body
     * @param curServer ?
     * @param method    http method
     * @return result
     * @throws NacosException nacos exception
     */
    public String callServer(String api, Map params, Map 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 (!curServer.contains(UtilAndComs.SERVER_ADDR_IP_SPLITER)) {
                curServer = curServer + UtilAndComs.SERVER_ADDR_IP_SPLITER + serverPort;
            }
            url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
        }
        
        try {
            HttpRestResult 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发送的。

你可能感兴趣的:(Nacos客户端namespace初始化及注册流程)