Spring Cloud 2.2.2 源碼之六十nacos數據一致性原理之臨時結點數據同步一

Spring Cloud 2.2.2 源碼之六十nacos數據一致性原理之臨時結點數據同步一

nacos數據一致性服務執行流程

在這裏插入圖片描述

數據一致性

其實nacos內部提供兩種數據同步方案APCP,而且是混用的,只要你的實例是臨時的默認用CP,如果是永久的要就用AP。兩個數據一致性服務的處理器類結構:
在這裏插入圖片描述
可以看到,左邊的RaftConsistencyServiceImpl就是CP的實現類,右邊的DistroConsistencyServiceImpl就是AP的實現類,我們註冊實例的時候通常是DelegateConsistencyServiceImpl來幫助我們判斷該用臨時的還是永久的服務,其實他內部就是代理這兩個:
在這裏插入圖片描述
具體怎麼同步的,我們先來講APTaskScheduler任務同步。

TaskScheduler的run臨時數據同步

只要有服務實例的改變,就會讓最終一致性服務去做數據同步,其實就是放進一個隊列裏,然後同步任務從隊列中獲取要同步的服務key,然後判斷數量是不是超過1000或者超時2秒了,是的話就創建SyncTask同步任務並提交,給每一個服務器集羣去同步,其實這個屬於最終一致性的同步方案。

 @Override
        public void run() {

            List<String> keys = new ArrayList<>();
            while (true) {

                try {
                    //阻塞2秒獲取key
                    String key = queue.poll(partitionConfig.getTaskDispatchPeriod(),
                        TimeUnit.MILLISECONDS);
                    //不爲空,表示獲取到了
                    if (Loggers.DISTRO.isDebugEnabled() && StringUtils.isNotBlank(key)) {
                        Loggers.DISTRO.debug("got key: {}", key);
                    }
                    //沒有服務器繼續
                    if (dataSyncer.getServers() == null || dataSyncer.getServers().isEmpty()) {
                        continue;
                    }
                    //爲空繼續
                    if (StringUtils.isBlank(key)) {
                        continue;
                    }
                    //還沒數據
                    if (dataSize == 0) {
                        keys = new ArrayList<>();
                    }

                    keys.add(key);
                    dataSize++;//數據+1
                    //達到分區批量同步數,默認1000個或者同步時間超時,默認2秒
                    if (dataSize == partitionConfig.getBatchSyncKeyCount() ||
                        (System.currentTimeMillis() - lastDispatchTime) > partitionConfig.getTaskDispatchPeriod()) {
                        //獲取所有集羣服務器
                        for (Server member : dataSyncer.getServers()) {
                            if (NetUtils.localServer().equals(member.getKey())) {//本機不同步
                                continue;
                            }
                            SyncTask syncTask = new SyncTask();
                           	syncTask.setKeys(keys);//發送的服務key
                            syncTask.setTargetServer(member.getKey());//發送的目標,IP:port

                            if (Loggers.DISTRO.isDebugEnabled() && StringUtils.isNotBlank(key)) {
                                Loggers.DISTRO.debug("add sync task: {}", JSON.toJSONString(syncTask));
                            }
                            //提交同步任務
                            dataSyncer.submit(syncTask, 0);
                        }
                        lastDispatchTime = System.currentTimeMillis();//重置
                        dataSize = 0;
                    }

                } catch (Exception e) {
                    Loggers.DISTRO.error("dispatch sync task failed.", e);
                }
            }
        }

DataSyncer的submit

提交一個同步任務,如果是新任務的話要去重,刪除新的key,因爲有可能會有相同的key的任務在重試或者等待重試。然後進行相關key的服務實例集合獲取,序列化後同步到目標服務器,如果失敗的話還要重試,成功就刪除任務返回。

public void submit(SyncTask task, long delay) {

        // If it's a new task:
        if (task.getRetryCount() == 0) {//新的任務
            Iterator<String> iterator = task.getKeys().iterator();
            while (iterator.hasNext()) {//去重
                String key = iterator.next();
                if (StringUtils.isNotBlank(taskMap.putIfAbsent(buildKey(key, task.getTargetServer()), key))) {//已經存在的
                    // associated key already exist:
                    if (Loggers.DISTRO.isDebugEnabled()) {
                        Loggers.DISTRO.debug("sync already in process, key: {}", key);
                    }
                    iterator.remove();//刪除新的任務裏的key,理論上應該是刪除舊的,但是此時舊的可能已經在執行了,所以刪除新的
                }
            }
        }

        if (task.getKeys().isEmpty()) {
            // all keys are removed:
            return;
        }
        //全局執行器執行數據同步任務
        GlobalExecutor.submitDataSync(() -> {
            // 1. check the server
            if (getServers() == null || getServers().isEmpty()) {
                Loggers.SRV_LOG.warn("try to sync data but server list is empty.");
                return;
            }

            List<String> keys = task.getKeys();

            if (Loggers.SRV_LOG.isDebugEnabled()) {
                Loggers.SRV_LOG.debug("try to sync data for this keys {}.", keys);
            }
            // 這裏又會去重一次,應該keys裏可能有重複的,但是裏面獲取的時候去重了
            Map<String, Datum> datumMap = dataStore.batchGet(keys);
            if (datumMap == null || datumMap.isEmpty()) {//服務實例集合空了就刪除相應任務
                // clear all flags of this task:
                for (String key : keys) {
                    taskMap.remove(buildKey(key, task.getTargetServer()));
                }
                return;
            }

            byte[] data = serializer.serialize(datumMap);//服務實例集合序列化

            long timestamp = System.currentTimeMillis();
            boolean success = NamingProxy.syncData(data, task.getTargetServer());//發送
            if (!success) {//失敗就重試
                SyncTask syncTask = new SyncTask();
                syncTask.setKeys(task.getKeys());
                syncTask.setRetryCount(task.getRetryCount() + 1);
                syncTask.setLastExecuteTime(timestamp);
                syncTask.setTargetServer(task.getTargetServer());
                retrySync(syncTask);
            } else {//成功了就刪除任務
                // clear all flags of this task:
                for (String key : task.getKeys()) {
                    taskMap.remove(buildKey(key, task.getTargetServer()));
                }
            }
        }, delay);
    }

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

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