spring cloud eureka server 集羣同步之peernodes管理

在PeerAwareInstanceRegistryImpl這個註冊實現類中會看到replicateToPeers這個方法:

@Override
public boolean cancel(final String appName, final String id,
                          final boolean isReplication) {
        if (super.cancel(appName, id, isReplication)) {
            replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
            ...
            return true;
        }
        return false;
    }

@Override
public void register(final InstanceInfo info, final boolean isReplication) {
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        super.register(info, leaseDuration, isReplication);
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }

瞭解eureka的朋友可能都知道replicateToPeers這個方法是用於eureka server之間同步eureka client信息的方法。其中的代碼是這樣的:

private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info /* optional */,
                                  InstanceStatus newStatus /* optional */, boolean isReplication) {
        Stopwatch tracer = action.getTimer().start();
        try {
            if (isReplication) {
                numberOfReplicationsLastMin.increment();
            }
            // If it is a replication already, do not replicate again as this will create a poison replication
            if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
                return;
            }

            for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
                // If the url represents this host, do not replicate to yourself.
                if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                    continue;
                }
                replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
            }
        } finally {
            tracer.stop();
        }
    }

其中有兩個關鍵代碼:peerEurekaNodes.getPeerEurekaNodes() 和 peerEurekaNodes.isThisMyUrl(node.getServiceUrl()))

peerEurekaNodes.isThisMyUrl() 這個方法是判斷node是否自己(當前節點),如果是自己,就跳過,否則發送複製信息給這個eureka server node。

peerEurekaNodes.getPeerEurekaNodes() 是eureka server node的列表,eureka server會維護集羣中跟自己有直接關係的eureka server信息,其實就是將eureka.client.serviceUrl.{zone}這個url列表中生成,默認是eureka.client.serviceUrl.defaultZone,接下來看下維護這個peerEurekaNodes的邏輯。

peerEurekaNodes管理
其實邏輯很簡單,代碼的執行序列是這樣的:

springDefaultEurekaPeerEurekaNodesScheduledExeinitialize在EurekaServerAutoConfiguration中發佈了DefaultEurekaServerContext和PeerEurekaNodesstartDefaultEurekaServerContext.initialize()方法標註了@PostConstruct,Spring會在加載完該類後執行這個方法。scheduleWithFixedDelayPeerEurekaNodes創建一個定時任務,定時執行updatePeerEurekaNodes()方法,更新peerEurekaNodesupdatePeerEurekaNodesspringDefaultEurekaPeerEurekaNodesScheduledExe

這裏將PeerEurekaNodes的部分代碼貼出來,我認爲設計得挺巧妙的

	public void start() {
        ...
        try {
            updatePeerEurekaNodes(resolvePeerUrls());
            Runnable peersUpdateTask = new Runnable() {
                @Override
                public void run() {
                    try {
                        updatePeerEurekaNodes(resolvePeerUrls());
                    } catch (Throwable e) {
                        logger.error("Cannot update the replica Nodes", e);
                    }

                }
            };
            taskExecutor.scheduleWithFixedDelay(
                    peersUpdateTask,
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    TimeUnit.MILLISECONDS
            );
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        ...
        }
    }

	protected List<String> resolvePeerUrls() {
        InstanceInfo myInfo = applicationInfoManager.getInfo();
        String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo);
        List<String> replicaUrls = EndpointUtils
                .getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));

        int idx = 0;
        while (idx < replicaUrls.size()) {
            if (isThisMyUrl(replicaUrls.get(idx))) {
                replicaUrls.remove(idx);
            } else {
                idx++;
            }
        }
        return replicaUrls;
    }

	protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
        if (newPeerUrls.isEmpty()) {
            logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
            return;
        }

        Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
        toShutdown.removeAll(newPeerUrls);
        Set<String> toAdd = new HashSet<>(newPeerUrls);
        toAdd.removeAll(peerEurekaNodeUrls);

        if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
            return;
        }

        // Remove peers no long available
        List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);

        if (!toShutdown.isEmpty()) {
            logger.info("Removing no longer available peer nodes {}", toShutdown);
            int i = 0;
            while (i < newNodeList.size()) {
                PeerEurekaNode eurekaNode = newNodeList.get(i);
                if (toShutdown.contains(eurekaNode.getServiceUrl())) {
                    newNodeList.remove(i);
                    eurekaNode.shutDown();
                } else {
                    i++;
                }
            }
        }

        // Add new peers
        if (!toAdd.isEmpty()) {
            logger.info("Adding new peer nodes {}", toAdd);
            for (String peerUrl : toAdd) {
                newNodeList.add(createPeerEurekaNode(peerUrl));
            }
        }

        this.peerEurekaNodes = newNodeList;
        this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
    }

1、在創建執行updatePeerEurekaNodes的定時任務之前,先執行一遍updatePeerEurekaNodes()方法,更新peer eureka nodes列表,之前要先執行這個方法,是因爲定時任務執行的週期默認是10分鐘,如果等定時調度來執行的話,太慢了。

2、resolvePeerUrls()獲取peer node url時,調用了一個內部判斷方法isThisMyUrl(),判斷peer url是否是當前節點的url。如果是,就剔除掉。

3、updatePeerEurekaNodes()方法中,通過以前的peerurls減去當前的peerurls判斷是否要更新peerEurekaNodes列表,以減少PeerEurekaNode的創建工作。

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