Spring Cloud Alibaba Nacos 服務注冊--實現(xiàn)原理與源碼解析

好久不見,最近工作一直被一堆事所捆綁铛楣,也沒有大塊的時間去看技術(shù)相關(guān)的東西,以至于部分伙伴的留言都沒有及時的回復明肮,先和大家分享一句話突然想起來的一句吧菱农,不變的可能是這個世界,不斷變化的可能是我們自己柿估。

微服務架構(gòu)在近幾年變的越來越流行循未,這可能會成為我們的一個必備技能(本人的建議是如果現(xiàn)有的系統(tǒng)沒有遇到什么運維、業(yè)務瓶頸的話不要為了單純的使用微服務技術(shù)而去進行改造)秫舌。

在微服務的早期隨著Spring Cloud的支撐的妖,好多都使用了Spring默認的Netflix全家桶,但是現(xiàn)在Netflix也不再進行維護且社區(qū)活躍度也比較低足陨,所以在注冊中心部分很多團隊都會在Nacos嫂粟、Zookeeper、Kubernetes中進行選擇墨缘,本文不對其中的優(yōu)缺點進行比較和分析赋元,直接來干貨Spring Cloud Alibaba Nacos 服務如何注冊。

一飒房、服務注冊初始化階段
其實對于服務的注冊基本上所有開源項目的實現(xiàn)思路基本一致搁凸,無非就是服務的注冊、心跳維持狠毯,看下圖直接找到服務注冊的源碼护糖,一般這種代碼我都是從AutoConfig開始看了,大家可以先大體看一下嚼松,這幾個AutoConfig功能其實就是初始化紅框內(nèi)的其他的的非AutoConfig類嫡良。

源碼結(jié)構(gòu)圖

項目啟動以后首先會通過NacosDiscoveryAutoConfiguration根據(jù)配置文件初始化NacosDiscoveryProperties,NacosDiscoveryProperties為配置文件中配置的服務注冊相關(guān)參數(shù)(如serverAddr献酗、group等)寝受,然后緊接著初始化NacosServiceDiscovery,該類主要是通過的NacosDiscoveryProperties的namingServiceInstance屬性實現(xiàn)獲取注冊中心的相關(guān)信息罕偎。最后初始化NacosDiscoveryClient對NacosServiceDiscovery進行封裝很澄。

在服務注冊方面,會通過NacosServiceRegistryAutoConfiguration依次初始化NacosServiceRegistry颜及、NacosRegistration甩苛、NacosAutoServiceRegistration,在初始化NacosServiceRegistry時會對之前的NacosDiscoveryProperties的namingServiceInstance屬性進行實例化俏站,其中服務的注冊的核心代碼(com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register)也在該類中讯蒲。在初始化AbstractAutoServiceRegistration時候,大家會發(fā)現(xiàn)它的父類實現(xiàn)了ApplicationListener肄扎,當發(fā)布事件時候會執(zhí)行onApplicationEvent中的代碼(可以參考我伙伴的文章)墨林,這也是服務注冊的入口赁酝。

二、服務注冊階段
在AbstractAutoServiceRegistration的onApplicationEvent方法中執(zhí)行了start方法旭等,start方法中會執(zhí)行register方法赞哗,最終調(diào)用的方法就是之前說到的com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register,完成對服務進行注冊辆雾。

public void register(Registration registration) {
        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
        } else {
            String serviceId = registration.getServiceId();
            String group = this.nacosDiscoveryProperties.getGroup();
            Instance instance = this.getNacosInstanceFromRegistration(registration);

            try {
                this.namingService.registerInstance(serviceId, group, instance);
                log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
            } catch (Exception var6) {
                log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var6});
                ReflectionUtils.rethrowRuntimeException(var6);
            }

        }
    }

在服務注冊的代碼中肪笋,最核心的是this.namingService.registerInstance(serviceId, group, instance);這一行,最終實現(xiàn)注冊的方法是com.alibaba.nacos.client.naming.net.NamingProxy#registerService度迂,在這個方法中通過調(diào)用nacas的api包中的reqAPI來與注冊中心的服務端進行交互從而完成注冊藤乙。

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
        Map<String, String> params = new HashMap(9);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        params.put("clusterName", instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put("healthy", String.valueOf(instance.isHealthy()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JSON.toJSONString(instance.getMetadata()));
        this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");
    }


public String callServer(String api, Map<String, String> params, String body, String curServer, String method) throws NacosException {
        long start = System.currentTimeMillis();
        long end = 0L;
        this.injectSecurityInfo(params);
        List<String> headers = this.builderHeaders();
        String url;
        if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {
            if (!curServer.contains(":")) {
                curServer = curServer + ":" + this.serverPort;
            }

            url = HttpClient.getPrefix() + curServer + api;
        } else {
            url = curServer + api;
        }

        HttpResult result = HttpClient.request(url, headers, params, body, "UTF-8", method);
        end = System.currentTimeMillis();
        MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(result.code)).observe((double)(end - start));
        if (200 == result.code) {
            return result.content;
        } else if (304 == result.code) {
            return "";
        } else {
            throw new NacosException(result.code, result.content);
        }
    }

在reqAPI的最后部分的代碼com.alibaba.nacos.client.naming.net.NamingProxy#callServer(java.lang.String, java.util.Map<java.lang.String,java.lang.String>, java.lang.String, java.lang.String, java.lang.String)中,與服務端進行交互的過程中惭墓,會將自身的信息發(fā)送給服務端坛梁,服務端響應OK后完成注冊,請求服務端的信息如下腊凶。

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

三划咐、心跳的維持
服務注冊到注冊中心服務端后,服務端與客戶端會一直保持一個心跳钧萍,來對服務的可用性進行維護褐缠,在之前的部分我們有說過NacosServiceRegistry類中會對namingService進行實例化,同樣也是利用nacos的api包通過反射來進行初始化风瘦。

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

在對NacosNamingService進行初始化的過程中队魏,我們會在構(gòu)造函數(shù)中發(fā)現(xiàn)他還會初始化com.alibaba.nacos.client.naming.NacosNamingService#beatReactor,而BeatReactor中存在一個線程池万搔,我們在服務注冊之前(com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)方法)就會向這個線程池中發(fā)布Task胡桨。

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

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
        BeatInfo existBeat = null;
        if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
            existBeat.setStopped(true);
        }

        this.dom2Beat.put(key, beatInfo);
        this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
    }

下面我們在看一下這個定時任務中的內(nèi)容,其實與服務的注冊原理相似瞬雹,都是與服務端進行一個交互昧谊,如果服務端響應該服務不可用,則重新進行注冊酗捌,如果依舊可用(或已經(jīng)重新注冊)則繼續(xù)將新的Task(BeatTask)放入到線程池中呢诬,從而不斷的與服務端維持心跳。

地址:http://192.168.***.***:8848/nacos/v1/ns/instance/beat
內(nèi)容:{app=nacos-provider, namespaceId=public, port=8081, clusterName=DEFAULT, ip=192.168.***.***, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8}
    class BeatTask implements Runnable {
        BeatInfo beatInfo;

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

        public void run() {
            if (!this.beatInfo.isStopped()) {
                long nextTime = this.beatInfo.getPeriod();

                try {
                    JSONObject result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo, BeatReactor.this.lightBeatEnabled);
                    long interval = (long)result.getIntValue("clientBeatInterval");
                    boolean lightBeatEnabled = false;
                    if (result.containsKey("lightBeatEnabled")) {
                        lightBeatEnabled = result.getBooleanValue("lightBeatEnabled");
                    }

                    BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
                    if (interval > 0L) {
                        nextTime = interval;
                    }

                    int code = 10200;
                    if (result.containsKey("code")) {
                        code = result.getIntValue("code");
                    }

                    if (code == 20404) {
                        Instance instance = new Instance();
                        instance.setPort(this.beatInfo.getPort());
                        instance.setIp(this.beatInfo.getIp());
                        instance.setWeight(this.beatInfo.getWeight());
                        instance.setMetadata(this.beatInfo.getMetadata());
                        instance.setClusterName(this.beatInfo.getCluster());
                        instance.setServiceName(this.beatInfo.getServiceName());
                        instance.setInstanceId(instance.getInstanceId());
                        instance.setEphemeral(true);

                        try {
                            BeatReactor.this.serverProxy.registerService(this.beatInfo.getServiceName(), NamingUtils.getGroupName(this.beatInfo.getServiceName()), instance);
                        } catch (Exception var10) {
                        }
                    }
                } catch (NacosException var11) {
                    LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", new Object[]{JSON.toJSONString(this.beatInfo), var11.getErrCode(), var11.getErrMsg()});
                }

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

小結(jié)
上文介紹了Spring Cloud Alibaba Nacos 服務注冊的實現(xiàn)原理與源碼解析意敛,相信大家應該大體了解了服務是如何注冊到Nacos中馅巷,但是可能大家接下來還有疑惑,我們在服務調(diào)用的時候草姻,是從哪里獲取的所要調(diào)用的服務可用列表,以及這個列表是如何與服務端共同維護的稍刀,請聽下回分解撩独。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敞曹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子综膀,更是在濱河造成了極大的恐慌澳迫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剧劝,死亡現(xiàn)場離奇詭異橄登,居然都是意外死亡,警方通過查閱死者的電腦和手機讥此,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門拢锹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萄喳,你說我怎么就攤上這事卒稳。” “怎么了他巨?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵充坑,是天一觀的道長。 經(jīng)常有香客問我染突,道長捻爷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任份企,我火速辦了婚禮役衡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薪棒。我一直安慰自己手蝎,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布俐芯。 她就那樣靜靜地躺著棵介,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吧史。 梳的紋絲不亂的頭發(fā)上邮辽,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音贸营,去河邊找鬼吨述。 笑死,一個胖子當著我的面吹牛钞脂,可吹牛的內(nèi)容都是我干的揣云。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冰啃,長吁一口氣:“原來是場噩夢啊……” “哼邓夕!你這毒婦竟也來了刘莹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤焚刚,失蹤者是張志新(化名)和其女友劉穎点弯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矿咕,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡抢肛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碳柱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捡絮。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖士聪,靈堂內(nèi)的尸體忽然破棺而出锦援,到底是詐尸還是另有隱情,我是刑警寧澤剥悟,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布灵寺,位于F島的核電站,受9級特大地震影響区岗,放射性物質(zhì)發(fā)生泄漏略板。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一慈缔、第九天 我趴在偏房一處隱蔽的房頂上張望叮称。 院中可真熱鬧,春花似錦藐鹤、人聲如沸瓤檐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挠蛉。三九已至,卻和暖如春肄满,著一層夾襖步出監(jiān)牢的瞬間谴古,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工稠歉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掰担,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓怒炸,卻偏偏與公主長得像带饱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子横媚,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355