Spring Cloud 2.2.2 源碼之五十六nacos服務端處理註冊實例請求四

Service模型大致結構

在這裏插入圖片描述

DistroConsistencyServiceImpl的put臨時實例集合一致性服務

添加集合,然後

    @Override
    public void put(String key, Record value) throws NacosException {
        onPut(key, value);
        taskDispatcher.addTask(key);
    }

onPut添加臨時實例集合

如果是臨時的就添加一個Datum,將實例集合放入。如果有監聽器監聽的,立即通知,否則就返回,怎麼通知的後面說。

    //臨時的實例
    public void onPut(String key, Record value) {
        //如果是臨時的服務實例集合
        if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
            Datum<Instances> datum = new Datum<>();//創建臨時數據
            datum.value = (Instances) value;
            datum.key = key;
            datum.timestamp.incrementAndGet();
            dataStore.put(key, datum);//放進一個map裏
        }

        if (!listeners.containsKey(key)) {//沒有監聽器就返回
            return;
        }
        //有監聽立即通知服務有改變
        notifier.addTask(key, ApplyAction.CHANGE);
    }

RaftConsistencyServiceImpl的put永久實例集合一致性服務

raft選舉算法有關。

    @Override
    public void put(String key, Record value) throws NacosException {
        try {
            raftCore.signalPublish(key, value);
        } catch (Exception e) {
            Loggers.RAFT.error("Raft put failed.", e);
            throw new NacosException(NacosException.SERVER_ERROR, "Raft put failed, key:" + key + ", value:" + value, e);
        }
    }

signalPublish

這個其實涉及到raft選舉的協議,如果本服務不是leader就要交給leader去處理,就發一個http請求給leaderleader接受到之後還是會到他的signalPublish裏處理。如果是leader的話就進行服務實例改變通知,通知本地的監聽器,並且要同步到其他結點,使用過半機制,剛好CountDownLatch可以用,只要有過半響應成功就算同步成功,具體的細節後面會講。

 public void signalPublish(String key, Record value) throws Exception {

        if (!isLeader()) {//不是leader
            JSONObject params = new JSONObject();
            params.put("key", key);
            params.put("value", value);
            Map<String, String> parameters = new HashMap<>(1);
            parameters.put("key", key);
            //交給leader去做/v1/ns/raft/datum
            raftProxy.proxyPostLarge(getLeader().ip, API_PUB, params.toJSONString(), parameters);
            return;
        }

        try {//是leader
            OPERATE_LOCK.lock();
            long start = System.currentTimeMillis();
            final Datum datum = new Datum();
            datum.key = key;
            datum.value = value;
            if (getDatum(key) == null) {
                datum.timestamp.set(1L);
            } else {
                datum.timestamp.set(getDatum(key).timestamp.incrementAndGet());
            }

            JSONObject json = new JSONObject();
            json.put("datum", datum);
            json.put("source", peers.local());
            //發佈數據改變通知
            onPublish(datum, peers.local());

            final String content = JSON.toJSONString(json);
            //只要過半的結點數
            final CountDownLatch latch = new CountDownLatch(peers.majorityCount());
            for (final String server : peers.allServersIncludeMyself()) {//遍歷所有結點
                if (isLeader(server)) {//自己算一次
                    latch.countDown();
                    continue;
                }
                final String url = buildURL(server, API_ON_PUB);///v1/ns/raft/datum/commit
                HttpClient.asyncHttpPostLarge(url, Arrays.asList("key=" + key), content, new AsyncCompletionHandler<Integer>() {
                    @Override
                    public Integer onCompleted(Response response) throws Exception {
                        if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                            Loggers.RAFT.warn("[RAFT] failed to publish data to peer, datumId={}, peer={}, http code={}",
                                datum.key, server, response.getStatusCode());
                            return 1;
                        }
                        latch.countDown();//異步完成
                        return 0;
                    }

                    @Override
                    public STATE onContentWriteCompleted() {
                        return STATE.CONTINUE;
                    }
                });

            }
            //等待半數完成
            if (!latch.await(UtilsAndCommons.RAFT_PUBLISH_TIMEOUT, TimeUnit.MILLISECONDS)) {
                // only majority servers return success can we consider this update success
                Loggers.RAFT.error("data publish failed, caused failed to notify majority, key={}", key);
                throw new IllegalStateException("data publish failed, caused failed to notify majority, key=" + key);
            }

            long end = System.currentTimeMillis();
            Loggers.RAFT.info("signalPublish cost {} ms, key: {}", (end - start), key);
        } finally {
            OPERATE_LOCK.unlock();
        }
    }

TaskDispatcher的addTask

負載均衡的獲取一個任務執行器,添加一個新任務,這個就是來做數據同步的,具體做什麼後面一起說。

    public void addTask(String key) {
        taskSchedulerList.get(UtilsAndCommons.shakeUp(key, cpuCoreCount)).addTask(key);
    }

TaskScheduler的addTask

放到一個阻塞隊列裏,做數據同步,同步到其他結點,具體的任務後面說。

private BlockingQueue<String> queue = new LinkedBlockingQueue<>(128 * 1024);


 public void addTask(String key) {
            queue.offer(key);
        }

這樣,服務實例的註冊流程基本說完了,我們可以總結下。

  1. 如果服務不存在,就創建服務,並開啓心跳檢查,來檢查服務中的每個實例,並將服務註冊到監聽器集合中,因爲服務本身實現了監聽器集合,其他集羣的服務數據有改動的話會通知服務。
  2. 添加服務實例到服務集羣中,沒有集羣就創建一個,並刷新老的服務實例集合。並將服務key添加到同步任務中,同步任務根據數量和超時限制進行服務器集羣的同步。

其實中間可能還開啓了好多任務,比如心跳檢查ServiceClientBeatCheckTaskClusterHealthCheckTask,數據同步的TaskDispatcher,服務實例改變的通知器Notifier任務。

好了,今天就到這裏了,希望對學習理解有幫助,大神看見勿噴,僅爲自己的學習理解,能力有限,請多包涵。

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