菜鳥學源碼之Nacos v1.1.3源碼學習-Client模塊(1):NacosNamingService初始化

摘要:
本文是Nacos源碼學習的第一篇,基於Nacos v1.1.3版本對Nacos源碼進行學習,本片主要從exmaple的App示例入手,切入Nacos客戶端NacosNamingService的初始化過程,並分析初始化相關的類。
一.源碼下載
1.首先Fork一份Nacos源碼到自己的github賬號下面。
2.用git clone自己賬號下的github項目到本地。
3.本地創建一個用於學習的分支,方便自己對源碼進行一些標註,同時不會污染到主幹分支的源碼。
4.另外還可以創建一個開發的分支,以便進行一些源碼的修改。
5.在本地搭建一套Nacos的運行環境,以便進行運行測試和調試。

二.從何入手
1.Nacos的源碼採用的是Maven父子項目結構,其源碼結構如下圖所示,目前我們還不知道每個模塊是幹什麼用的,但是其中一個模塊我認爲是值得下手研究的,那就是example,我們可以從這裏入手開始調試和閱讀源碼。
在這裏插入圖片描述
在這裏插入圖片描述
2.com.alibaba.nacos.example.App
我們首先來看下App這個程序,如圖所示,這裏程序主要演示的是進行服務註冊和服務獲取,首先通過Properties文件將Nacos服務器信息以及命名空間進行設置,然後通過NamingFactory創建NamingService服務,最後通過NamingService服務註冊了兩個服務,服務名稱都是nacos.test.3,但是集羣名稱不同。最後通過getAllInstnaces獲取這個服務命對應的所有服務。
在這裏插入圖片描述
3. com.alibaba.nacos.api.naming.NamingFactory
很顯然,我們需要看下NamingFactroy是幹什麼的,如下圖所示,NamingFactory是一個工廠類,用於創建NamingService。
在這裏插入圖片描述
NamingFactory可以通過兩種方式獲取NamingService,我們先來看看通過Properties來創建,可以看到這裏它通過反射的方式創建了NamingService的一個實現類:com.alibaba.nacos.client.naming.NacosNamingService,這個類存在於client模塊中,這樣客戶端就能夠通過NacosNamingService來管理和註冊服務了。
在這裏插入圖片描述
4.NacosNamingService初始化
接下來看下NacosNamingService的構造方法,如下圖所示,主要看init方法,其中進行了一些init方法:
initNamespaceForNaming:用於初始命名空間,在Nacos中命名空間用於租戶粗粒度隔離,同時還可以進行環境的區別,如開發環境和測試環境等等。
initServerAddr:初始化服務器地址,其中涉及到的endpoint 等,我們後面進行討論
initWebRootContext:初始化web上下文,其支持通過阿里雲EDAS進行部署
initCacheDir:初始化緩存目錄
initLogName:從配置中獲取日誌文件
EventDispatcher:監聽事件分發,當客戶端訂閱了某個服務信息後,會以Listener的方式註冊到EventDispatcher的隊列中,當有服務變化的時候,會通知訂閱者。
NamingProxy:服務端的代理,用於客戶端與服務端的通信
BeatReactor:用於維持與服務器之間的心跳通信,上報客戶端註冊到服務端的服務信息。
HostReactor:用於客戶端服務的訂閱,以及從服務端更新服務信息
在這裏插入圖片描述
6.方法具體分析和對象的構建-1
a.initNamespaceForNaming
通過備註方式進行解釋:

//初始化獲取Namespace

    public static String initNamespaceForNaming(Properties properties) {
        String tmpNamespace = null;
    
        //是否使用阿里雲上環境進行解析,默認爲true,如果沒有進行配置,默認使用DEFAULT_USE_CLOUD_NAMESPACE_PARSING
        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.valueOf(isUseCloudNamespaceParsing)) {
    
            tmpNamespace = TenantUtil.getUserTenantForAns();
            //從系統變量獲取ans.namespace
            tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
                @Override
                public String call() {
                    String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
                    LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
                    return namespace;
                }
            });
    
            //從環境變量獲取ALIBABA_ALIWARE_NAMESPACE,這一步和前一步應該是跟雲上環境有關
            tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
                @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;
                }
            });
        }
    
        //如果不是上雲環境,那麼從系統變量獲取namespace
        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
            @Override
            public String call() {
                String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
                LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
                return namespace;
            }
        });
    
        //若上面沒獲取到,從properties中獲取namespace
        if (StringUtils.isEmpty(tmpNamespace) && properties != null) {
            tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
        }
    
        //若還沒有渠道,獲取系統默認的namespace
        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
            @Override
            public String call() {
                return UtilAndComs.DEFAULT_NAMESPACE_ID;
            }
        });
        return tmpNamespace;
    }

b.initServerAddr
這裏讓人疑惑的是,有個初始化Endpoint,參考官網給出的解釋:Endpoint是提供一種能力,讓客戶端能夠感知到Nacos服務端的擴縮容,說直白一點就是配置一個URL,通過URL可以獲取Nacos服務器信息,也就是再也不用通過在客戶端配置死服務器端地址(這點設計還是很不錯),具體看下Endpoint是怎麼初始化的

//初始化服務器地址
private void initServerAddr(Properties properties) {
    //從properties中獲取服務器地址
    serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
    //初始化endpoint,如果有endpoint,則廢棄serverList
    endpoint = InitUtils.initEndpoint(properties);
    if (StringUtils.isNotEmpty(endpoint)) {
        serverList = "";
    }
}
public static String initEndpoint(final Properties properties) {
    if (properties == null) {

        return "";
    }
    // Whether to enable domain name resolution rules
    //是否使用endpoint解析,默認爲true,也就是:USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE
    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.valueOf(isUseEndpointRuleParsing);
    String endpointUrl;
    //使用endpoint解析功能
    if (isUseEndpointParsingRule) {
        // Get the set domain name information
        endpointUrl = ParamUtil.parsingEndpointRule(properties.getProperty(PropertyKeyConst.ENDPOINT));
        if (StringUtils.isBlank(endpointUrl)) {
            return "";
        }
    } else {
        //不使用的化,直接通過properties文件來獲取
        endpointUrl = properties.getProperty(PropertyKeyConst.ENDPOINT);
    }

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

    //獲取endpoint的端口
    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 = TemplateUtils.stringEmptyAndThenExecute(endpointPort, new Callable<String>() {
        @Override
        public String call() {
            return "8080";
        }
    });

    return endpointUrl + ":" + endpointPort;
}

c.initWebRootContext
這裏不用多說了,初始化基本的web上下文,同樣會涉及到阿里雲上雲的環境

public static void initWebRootContext() {
    // support the web context with ali-yun if the app deploy by EDAS
    //支持阿里雲上的webContext
    final String webContext = System.getProperty(SystemPropertyKeyConst.NAMING_WEB_CONTEXT);
    //生成url_base和url_instance拼接
    TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
        @Override
        public void run() {
            UtilAndComs.WEB_CONTEXT = webContext.indexOf("/") > -1 ? webContext
                : "/" + webContext;

            UtilAndComs.NACOS_URL_BASE = UtilAndComs.WEB_CONTEXT + "/v1/ns";
            UtilAndComs.NACOS_URL_INSTANCE = UtilAndComs.NACOS_URL_BASE + "/instance";
        }
    });
}

d.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;
}
}

e.initLogName
這裏也不多說,這是初始化日誌存放路徑

 //初始化日誌目錄
    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";
            }
        }
    }

f.EventDispatcher
EventDispatcher 是一個事件分發器,其維護了一個發生了變化服務的隊列,一個對於某個服務的監聽者隊列映射,實時的將服務變化信息同步給監聽者,這樣客戶端就可以通過註冊監聽者實現在服務變化後動態進行操作。

public class EventDispatcher {

    private ExecutorService executor = null;

    //發生了變化的服務隊列
    private BlockingQueue<ServiceInfo> changedServices = new LinkedBlockingQueue<ServiceInfo>();

    //監聽者維護映射
    private ConcurrentMap<String, List<EventListener>> observerMap
        = new ConcurrentHashMap<String, List<EventListener>>();

    public EventDispatcher() {

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

        executor.execute(new Notifier());
    }

    public void addListener(ServiceInfo serviceInfo, String clusters, EventListener listener) {

        NAMING_LOGGER.info("[LISTENER] adding " + serviceInfo.getName() + " with " + clusters + " to listener map");
        List<EventListener> observers = Collections.synchronizedList(new ArrayList<EventListener>());
        observers.add(listener);

        observers = observerMap.putIfAbsent(ServiceInfo.getKey(serviceInfo.getName(), clusters), observers);
        if (observers != null) {
            observers.add(listener);
        }

        serviceChanged(serviceInfo);
    }

    public void removeListener(String serviceName, String clusters, EventListener listener) {

        NAMING_LOGGER.info("[LISTENER] removing " + serviceName + " with " + clusters + " from listener map");

        List<EventListener> observers = observerMap.get(ServiceInfo.getKey(serviceName, clusters));
        if (observers != null) {
            Iterator<EventListener> iter = observers.iterator();
            while (iter.hasNext()) {
                EventListener oldListener = iter.next();
                if (oldListener.equals(listener)) {
                    iter.remove();
                }
            }
            if (observers.isEmpty()) {
                observerMap.remove(ServiceInfo.getKey(serviceName, clusters));
            }
        }
    }

    public List<ServiceInfo> getSubscribeServices() {
        List<ServiceInfo> serviceInfos = new ArrayList<ServiceInfo>();
        for (String key : observerMap.keySet()) {
            serviceInfos.add(ServiceInfo.fromKey(key));
        }
        return serviceInfos;
    }

    public void serviceChanged(ServiceInfo serviceInfo) {
        if (serviceInfo == null) {
            return;
        }

        changedServices.add(serviceInfo);
    }

    //服務變化通知線程
    private class Notifier implements Runnable {
        @Override
        public void run() {
            while (true) {
                ServiceInfo serviceInfo = null;
                try {
                    //從隊列取出變化消息
                    serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
                } catch (Exception ignore) {
                }

                if (serviceInfo == null) {
                    continue;
                }

                try {
                    //獲取監聽者隊列
                    List<EventListener> listeners = observerMap.get(serviceInfo.getKey());

                    //遍歷監聽者隊列,調用其onEvent方法
                    if (!CollectionUtils.isEmpty(listeners)) {
                        for (EventListener listener : listeners) {
                            List<Instance> 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);
                }
            }
        }
    }

    public void setExecutor(ExecutorService executor) {
        ExecutorService oldExecutor = this.executor;
        this.executor = executor;

        oldExecutor.shutdown();
    }
}

g.NamingProxy
serverProxy是客戶端與服務器端的代理,其封裝了與服務端的操作,這裏代碼太多,不具體列出,遇到具體場景再進行分析
h.BeatReactor
beatReactor是負責與服務端建立上報機制的類,對於ephemeral爲true的服務,客戶端需要通過BeatReactor週期性的進行服務的上報,告訴服務端該服務處於正常狀態,若一段時間內未進行該服務的上報,服務端會移除該服務的註冊。這裏所說的ephemeral服務是指服務信息不會在服務端持久化的服務,對於ephemeral爲false的服務,服務信息會持久化到服務端。下面是BeatReactor類的代碼.

//對於臨時服務ephemeral(在服務器段不進行持久化的服務),需要BeatReactor進行週期性的狀態上報
public class BeatReactor {

    private ScheduledExecutorService executorService;

    private NamingProxy serverProxy;

    public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();

    public BeatReactor(NamingProxy serverProxy) {
        this(serverProxy, UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
    }

    public BeatReactor(NamingProxy serverProxy, int threadCount) {
        this.serverProxy = serverProxy;

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

    //當在註冊服務的時候,會添加上報線程任務
    public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
        executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
    }

    //當移除服務的時候,會移除上報線程任務
    public void removeBeatInfo(String serviceName, String ip, int port) {
        NAMING_LOGGER.info("[BEAT] removing beat: {}:{}:{} from beat map.", serviceName, ip, port);
        BeatInfo beatInfo = dom2Beat.remove(buildKey(serviceName, ip, port));
        if (beatInfo == null) {
            return;
        }
        beatInfo.setStopped(true);
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
    }

    private String buildKey(String serviceName, String ip, int port) {
        return serviceName + Constants.NAMING_INSTANCE_ID_SPLITTER
            + ip + Constants.NAMING_INSTANCE_ID_SPLITTER + port;
    }

    //對於ephemeral服務,需要按週期上報服務信息
    class BeatTask implements Runnable {

        BeatInfo beatInfo;

        public BeatTask(BeatInfo beatInfo) {
            this.beatInfo = beatInfo;
        }

        @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }
            long result = serverProxy.sendBeat(beatInfo);
            long nextTime = result > 0 ? result : beatInfo.getPeriod();
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }
    }
}

i.HostReactor
當客戶端去獲取服務端上註冊的服務的時候,HostReactor就排上用場了,其維護了serviceInfoMap,當客戶端調用NacosNamingServiced的獲取服務信息方法的時候,HostReactor就把服務信息維護到serviceInfoMap當中,並通過UpdateTask能夠週期性的從服務端獲取訂閱服務的最新信息,同時HostReactor還持有pushReceiver對象,用於通過UDP協議從服務器獲取推送的信息,並更新到serviceInfoMap當中。HostReactor還持有failoverReactor對象,當服務端不可用的時候,切換到本地緩存模式,從緩存中獲取服務信息。

public class HostReactor {

    private static final long DEFAULT_DELAY = 1000L;

    private static final long UPDATE_HOLD_INTERVAL = 5000L;

    private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<String, ScheduledFuture<?>>();

    private Map<String, ServiceInfo> serviceInfoMap;

    private Map<String, Object> updatingMap;

    private PushReceiver pushReceiver;

    private EventDispatcher eventDispatcher;

    private NamingProxy serverProxy;

    private FailoverReactor failoverReactor;

    private String cacheDir;

    private ScheduledExecutorService executor;

    public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir) {
        this(eventDispatcher, serverProxy, cacheDir, false, UtilAndComs.DEFAULT_POLLING_THREAD_COUNT);
    }

    //構造方法
    public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir,
                       boolean loadCacheAtStart, int pollingThreadCount) {

        //設置updater線程
        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.serverProxy = serverProxy;
        this.cacheDir = cacheDir;
        //如果loadCacheAtStart就從本地緩存文件加載服務
        if (loadCacheAtStart) {
            this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
        } else {
            this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
        }

        //初始化更新映射
        this.updatingMap = new ConcurrentHashMap<String, Object>();
        //初始化容錯服務
        this.failoverReactor = new FailoverReactor(this, cacheDir);
        //初始化pushReceiver用於接收服務端推送的UDP數據
        this.pushReceiver = new PushReceiver(this);
    }

    public Map<String, ServiceInfo> getServiceInfoMap() {
        return serviceInfoMap;
    }

    public synchronized ScheduledFuture<?> addTask(UpdateTask task) {
        return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
    }

    //處理從服務端接收到的數據
    public ServiceInfo processServiceJSON(String json) {
        ServiceInfo serviceInfo = JSON.parseObject(json, ServiceInfo.class);
        ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
        if (serviceInfo.getHosts() == null || !serviceInfo.validate()) {
            //empty or error push, just ignore
            return oldService;
        }

        boolean changed = false;

        if (oldService != null) {

            //如果本地舊服務的獲取時間比服務器端獲取的時間新,則保留本地舊服務的時間
            if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {
                NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime()
                    + ", new-t: " + serviceInfo.getLastRefTime());
            }

            //用新服務信息替換serviceInfoMap
            serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);

            Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
            for (Instance host : oldService.getHosts()) {
                oldHostMap.put(host.toInetAddr(), host);
            }

            Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());
            for (Instance host : serviceInfo.getHosts()) {
                newHostMap.put(host.toInetAddr(), host);
            }

            Set<Instance> modHosts = new HashSet<Instance>();
            Set<Instance> newHosts = new HashSet<Instance>();
            Set<Instance> remvHosts = new HashSet<Instance>();

            List<Map.Entry<String, Instance>> newServiceHosts = new ArrayList<Map.Entry<String, Instance>>(
                newHostMap.entrySet());
            for (Map.Entry<String, Instance> entry : newServiceHosts) {
                Instance host = entry.getValue();
                String key = entry.getKey();
                if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(),
                    oldHostMap.get(key).toString())) {
                    modHosts.add(host);
                    continue;
                }

                if (!oldHostMap.containsKey(key)) {
                    newHosts.add(host);
                }
            }

            for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
                Instance host = entry.getValue();
                String key = entry.getKey();
                if (newHostMap.containsKey(key)) {
                    continue;
                }

                if (!newHostMap.containsKey(key)) {
                    remvHosts.add(host);
                }

            }

            if (newHosts.size() > 0) {
                changed = true;
                NAMING_LOGGER.info("new ips(" + newHosts.size() + ") service: "
                    + serviceInfo.getKey() + " -> " + JSON.toJSONString(newHosts));
            }

            if (remvHosts.size() > 0) {
                changed = true;
                NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: "
                    + serviceInfo.getKey() + " -> " + JSON.toJSONString(remvHosts));
            }

            if (modHosts.size() > 0) {
                changed = true;
                NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: "
                    + serviceInfo.getKey() + " -> " + JSON.toJSONString(modHosts));
            }

            serviceInfo.setJsonFromServer(json);

            if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
                eventDispatcher.serviceChanged(serviceInfo);
                DiskCache.write(serviceInfo, cacheDir);
            }

        } else {
            changed = true;
            NAMING_LOGGER.info("init new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> " + JSON
                .toJSONString(serviceInfo.getHosts()));
            serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
            eventDispatcher.serviceChanged(serviceInfo);
            serviceInfo.setJsonFromServer(json);
            DiskCache.write(serviceInfo, cacheDir);
        }

        MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());

        if (changed) {
            NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() +
                " -> " + JSON.toJSONString(serviceInfo.getHosts()));
        }

        return serviceInfo;
    }

    //通過key獲取服務對象
    private ServiceInfo getServiceInfo0(String serviceName, String clusters) {

        //得到ServiceInfo的key
        String key = ServiceInfo.getKey(serviceName, clusters);

        //從serviceInfoMap中獲取service
        return serviceInfoMap.get(key);
    }

    //直接從服務器端獲取Service信息,並解析爲ServiceInfo對象
    public ServiceInfo getServiceInfoDirectlyFromServer(final String serviceName, final String clusters) throws NacosException {
        String result = serverProxy.queryList(serviceName, clusters, 0, false);
        if (StringUtils.isNotEmpty(result)) {
            return JSON.parseObject(result, ServiceInfo.class);
        }
        return null;
    }


    public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {

        NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
        //serviceName是分組@@服務名稱,再和clusters合併,得到key
        String key = ServiceInfo.getKey(serviceName, clusters);
        if (failoverReactor.isFailoverSwitch()) {
            return failoverReactor.getService(key);
        }

        ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);

        //從serviceInfoMap獲取serviceObj,如果沒有serviceObj,則新生成一個
        if (null == serviceObj) {
            serviceObj = new ServiceInfo(serviceName, clusters);

            serviceInfoMap.put(serviceObj.getKey(), serviceObj);

            updatingMap.put(serviceName, new Object());
            updateServiceNow(serviceName, clusters);
            updatingMap.remove(serviceName);

            //如果更新列表中包含服務,則等待更新結束
        } else if (updatingMap.containsKey(serviceName)) {

            if (UPDATE_HOLD_INTERVAL > 0) {
                // hold a moment waiting for update finish
                synchronized (serviceObj) {
                    try {
                        serviceObj.wait(UPDATE_HOLD_INTERVAL);
                    } catch (InterruptedException e) {
                        NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                    }
                }
            }
        }

        //加入到更新調度當中
        scheduleUpdateIfAbsent(serviceName, clusters);

        return serviceInfoMap.get(serviceObj.getKey());
    }

    public void scheduleUpdateIfAbsent(String serviceName, String clusters) {
        if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
            return;
        }

        //添加更新調度任務
        synchronized (futureMap) {
            if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
                return;
            }

            ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));
            futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
        }
    }

    //直接從服務端更新服務
    public void updateServiceNow(String serviceName, String clusters) {
        ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
        try {

            String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);

            if (StringUtils.isNotEmpty(result)) {
                processServiceJSON(result);
            }
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
        } finally {
            if (oldService != null) {
                synchronized (oldService) {
                    oldService.notifyAll();
                }
            }
        }
    }

    public void refreshOnly(String serviceName, String clusters) {
        try {
            serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
        }
    }

    //更新服務
    public class UpdateTask implements Runnable {
        long lastRefTime = Long.MAX_VALUE;
        private String clusters;
        private String serviceName;

        public UpdateTask(String serviceName, String clusters) {
            this.serviceName = serviceName;
            this.clusters = clusters;
        }

        @Override
        public void run() {
            try {
                ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));

                if (serviceObj == null) {
                    updateServiceNow(serviceName, clusters);
                    executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
                    return;
                }

                if (serviceObj.getLastRefTime() <= lastRefTime) {
                    updateServiceNow(serviceName, clusters);
                    serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
                } else {
                    // if serviceName already updated by push, we should not override it
                    // since the push data may be different from pull through force push
                    refreshOnly(serviceName, clusters);
                }

                executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);

                lastRefTime = serviceObj.getLastRefTime();
            } catch (Throwable e) {
                NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
            }

        }
    }
}

三.小結
本文從exmaple中的App入手,粗略分析了下NacosNamingService的初始化過程,初始化包含的類,同時分析下每個類的作用,從而瞭解了Nacos客戶端中關於服務註冊中相關的機制。接下來將進一步繼續分析其它exmaple中的程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章