All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.nacos.naming.core.DistroMapper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.nacos.naming.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.naming.boot.RunningConfig;
import com.alibaba.nacos.naming.misc.*;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.collections.CollectionUtils;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author nkorange
 */
public class DistroMapper {

    public static final int STABLE_PERIOD = 60 * 1000;

    public static List getHealthyList() {
        return healthyList;
    }

    private static List healthyList = new ArrayList();

    private static Map> distroConfig = new ConcurrentHashMap>();

    private static Set liveSites = new HashSet();

    private static String localhostIP;

    public static final String LOCALHOST_SITE = UtilsAndCommons.UNKNOWN_SITE;

    private static long LAST_HEALTH_SERVER_MILLIS = 0L;

    private static boolean AUTO_DISABLED_HEALTH_CHECK = false;

    private static Synchronizer synchronizer = new ServerStatusSynchronizer();

    static {
        localhostIP = NetUtils.localServer();

        init();

        UtilsAndCommons.SERVER_STATUS_EXECUTOR.schedule(new ServerStatusReporter(),
            60000, TimeUnit.MILLISECONDS);
    }

    /**
     * init server list
     */
    private static void init() {
        List servers = NamingProxy.getServers();

        while (servers == null || servers.size() == 0) {
            Loggers.SRV_LOG.warn("[DISTRO-MAPPER] Server list is empty, sleep 3 seconds and try again.");
            try {
                TimeUnit.SECONDS.sleep(3);
                servers = NamingProxy.getServers();
            } catch (InterruptedException e) {
                Loggers.SRV_LOG.warn("[DISTRO-MAPPER] Sleeping thread is interupted, try again.");
            }
        }

        StringBuilder sb = new StringBuilder();

        for (String serverIP : servers) {
            String serverSite;
            String serverConfig;

            serverSite = UtilsAndCommons.UNKNOWN_SITE;

            serverConfig = serverSite + "#" + serverIP + "#" + System.currentTimeMillis() + "#" + 1 + "\r\n";
            sb.append(serverConfig);

        }

        onServerStatusUpdate(sb.toString(), false);
    }

    private static void onServerStatusUpdate(String configInfo, boolean isFromDiamond) {

        String[] configs = configInfo.split("\r\n");
        if (configs.length == 0) {
            return;
        }

        distroConfig.clear();
        List newHealthyList = new ArrayList();

        for (String config : configs) {
            // site:ip:lastReportTime:weight
            String[] params = config.split("#");
            if (params.length <= 3) {
                Loggers.SRV_LOG.warn("received malformed distro map data: {}", config);
                continue;
            }

            Server server = new Server();

            server.site = params[0];
            server.ip = params[1];
            server.lastRefTime = Long.parseLong(params[2]);

            Date date = new Date(Long.parseLong(params[2]));
            server.lastRefTimeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);

            server.weight = params.length == 4 ? Integer.parseInt(params[3]) : 1;
            server.alive = System.currentTimeMillis() - server.lastRefTime < Switch.getdistroServerExpiredMillis();

            List list = distroConfig.get(server.site);
            if (list == null) {
                list = new ArrayList();
                distroConfig.put(server.site, list);
            }

            list.add(server);
        }

        liveSites.addAll(distroConfig.keySet());

        List servers = distroConfig.get(LOCALHOST_SITE);
        if (CollectionUtils.isEmpty(servers)) {
            return;
        }

        List allSiteSrvs = new ArrayList();
        for (Server server : servers) {
            server.adWeight = Switch.getAdWeight(server.ip) == null ? 0 : Switch.getAdWeight(server.ip);

            for (int i = 0; i < server.weight + server.adWeight; i++) {
                allSiteSrvs.add(server.ip);

                if (server.alive) {
                    newHealthyList.add(server.ip);
                }
            }
        }

        Collections.sort(newHealthyList);
        float curRatio = (float) newHealthyList.size() / allSiteSrvs.size();

        if (AUTO_DISABLED_HEALTH_CHECK
            && curRatio > Switch.getDistroThreshold()
            && System.currentTimeMillis() - LAST_HEALTH_SERVER_MILLIS > STABLE_PERIOD) {
            Loggers.SRV_LOG.info("[VIPSRV-DISTRO] distro threshold restored and " +
                "stable now, enable health check. current ratio: {}", curRatio);

            Switch.setHeathCheckEnabled(true);

            // we must set this variable, otherwise it will conflict with user's action
            AUTO_DISABLED_HEALTH_CHECK = false;
        }

        if (!CollectionUtils.isEqualCollection(healthyList, newHealthyList)) {
            // for every change disable healthy check for some while
            if (Switch.isHealthCheckEnabled()) {
                Loggers.SRV_LOG.info("[VIPSRV-DISTRO] healthy server list changed, " +
                        "disable health check for {} ms from now on, healthList: {}, newHealthyList {}",
                    STABLE_PERIOD, healthyList, newHealthyList);

                Switch.setHeathCheckEnabled(false);
                AUTO_DISABLED_HEALTH_CHECK = true;

                LAST_HEALTH_SERVER_MILLIS = System.currentTimeMillis();
            }

            healthyList = newHealthyList;
        }
    }

    public static synchronized void onReceiveServerStatus(String configInfo) {
        String[] configs = configInfo.split("\r\n");
        if (configs.length == 0) {
            return;
        }

        List newHealthyList = new ArrayList();
        List tmpServerList = new ArrayList();

        for (String config : configs) {
            tmpServerList.clear();
            // site:ip:lastReportTime:weight
            String[] params = config.split("#");
            if (params.length <= 3) {
                Loggers.SRV_LOG.warn("received malformed distro map data: {}", config);
                continue;
            }

            Server server = new Server();

            server.site = params[0];
            server.ip = params[1];
            server.lastRefTime = Long.parseLong(params[2]);

            if (!NamingProxy.getServers().contains(server.ip)) {
                throw new IllegalArgumentException("ip: " + server.ip + " is not in serverlist");
            }

            Date date = new Date(Long.parseLong(params[2]));
            server.lastRefTimeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);

            server.weight = params.length == 4 ? Integer.parseInt(params[3]) : 1;
            server.alive = System.currentTimeMillis() - server.lastRefTime < Switch.getdistroServerExpiredMillis();
            List list = distroConfig.get(server.site);
            if (list == null || list.size() <= 0) {
                list = new ArrayList();
                list.add(server);
                distroConfig.put(server.site, list);
            }

            for (Server s : list) {
                String serverId = s.ip + "_" + s.site;
                String newServerId = server.ip + "_" + server.site;

                if (serverId.equals(newServerId)) {
                    if (s.alive != server.alive || s.weight != server.weight) {
                        Loggers.SRV_LOG.warn("server beat out of date, current: {}, last: {}",
                            JSON.toJSONString(server), JSON.toJSONString(s));
                    }
                    tmpServerList.add(server);
                    continue;
                }
                tmpServerList.add(s);
            }

            if (!tmpServerList.contains(server)) {
                tmpServerList.add(server);
            }

            distroConfig.put(server.site, tmpServerList);

        }
        liveSites.addAll(distroConfig.keySet());

        List servers = distroConfig.get(LOCALHOST_SITE);
        if (CollectionUtils.isEmpty(servers)) {
            return;
        }

        //local site servers
        List allLocalSiteSrvs = new ArrayList();
        for (Server server : servers) {

            if (server.ip.endsWith(":0")) {
                continue;
            }

            server.adWeight = Switch.getAdWeight(server.ip) == null ? 0 : Switch.getAdWeight(server.ip);

            for (int i = 0; i < server.weight + server.adWeight; i++) {

                if (!allLocalSiteSrvs.contains(server.ip)) {
                    allLocalSiteSrvs.add(server.ip);
                }

                if (server.alive && !newHealthyList.contains(server.ip)) {
                    newHealthyList.add(server.ip);
                }
            }
        }

        Collections.sort(newHealthyList);
        float curRatio = (float) newHealthyList.size() / allLocalSiteSrvs.size();

        if (AUTO_DISABLED_HEALTH_CHECK
            && curRatio > Switch.getDistroThreshold()
            && System.currentTimeMillis() - LAST_HEALTH_SERVER_MILLIS > STABLE_PERIOD) {
            Loggers.SRV_LOG.info("[VIPSRV-DISTRO] distro threshold restored and " +
                "stable now, enable health check. current ratio: {}", curRatio);

            Switch.setHeathCheckEnabled(true);

            // we must set this variable, otherwise it will conflict with user's action
            AUTO_DISABLED_HEALTH_CHECK = false;
        }

        if (!CollectionUtils.isEqualCollection(healthyList, newHealthyList)) {
            // for every change disable healthy check for some while
            if (Switch.isHealthCheckEnabled()) {
                Loggers.SRV_LOG.info("[VIPSRV-DISTRO] healthy server list changed, " +
                    "disable health check for {} ms from now on", STABLE_PERIOD);

                Switch.setHeathCheckEnabled(false);
                AUTO_DISABLED_HEALTH_CHECK = true;

                LAST_HEALTH_SERVER_MILLIS = System.currentTimeMillis();
            }

            healthyList = newHealthyList;
        }
    }

    public static boolean responsible(String dom) {
        if (!Switch.isDistroEnabled()) {
            return true;
        }

        if (CollectionUtils.isEmpty(healthyList)) {
            // means distro config is not ready yet
            return false;
        }

        int index = healthyList.indexOf(localhostIP);
        int lastIndex = healthyList.lastIndexOf(localhostIP);
        if (lastIndex < 0 || index < 0) {
            return true;
        }

        int target = distroHash(dom) % healthyList.size();
        return target >= index && target <= lastIndex;
    }

    public static String mapSrv(String dom) {
        if (CollectionUtils.isEmpty(healthyList) || !Switch.isDistroEnabled()) {
            return localhostIP;
        }

        try {
            return healthyList.get(distroHash(dom) % healthyList.size());
        } catch (Exception e) {
            Loggers.SRV_LOG.warn("distro mapper failed, return localhost: " + localhostIP, e);

            return localhostIP;
        }
    }

    public static int distroHash(String dom) {
        return Math.abs(dom.hashCode() % Integer.MAX_VALUE);
    }

    public static String mapSrvName(String dom) {
        return UtilsAndCommons.UNKNOWN_HOST;
    }

    public static Set getLiveSites() {
        return liveSites;
    }

    public static void clean() {
        cleanInvalidServers();

        for (Map.Entry> entry : distroConfig.entrySet()) {
            for (Server server : entry.getValue()) {
                //request other server to clean invalid servers
                if (!server.ip.equals(localhostIP)) {
                    requestOtherServerCleanInvalidServers(server.ip);
                }
            }

        }
    }

    private static void cleanInvalidServers() {

        for (Map.Entry> entry : distroConfig.entrySet()) {
            List tmpServers = null;
            List currentServerList = entry.getValue();

            for (Server server : entry.getValue()) {
                if (!server.alive) {

                    tmpServers = new ArrayList();

                    for (Server server1 : currentServerList) {
                        String serverKey1 = server1.ip + "_" + server1.site;
                        String serverKey = server.ip + "_" + server.site;

                        if (!serverKey.equals(serverKey1) && !tmpServers.contains(server1)) {
                            tmpServers.add(server1);
                        }
                    }
                }
            }
            if (tmpServers != null) {
                distroConfig.put(entry.getKey(), tmpServers);
            }
        }
    }

    private static void requestOtherServerCleanInvalidServers(String serverIP) {
        Map params = new HashMap(1);

        params.put("action", "without-diamond-clean");
        try {
            NamingProxy.reqAPI("distroStatus", params, serverIP, false);
        } catch (Exception e) {
            Loggers.SRV_LOG.warn("[DISTRO-STATUS-CLEAN] Failed to request to clean server status to " + serverIP, e);
        }
    }

    public static String getLocalhostIP() {
        return localhostIP;
    }

    @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    public static class Server {
        public String site = UtilsAndCommons.UNKNOWN_SITE;
        public String ip;
        public int weight = 1;
        /**
         * additional weight, used to adjust manually
         */
        public int adWeight;

        public boolean alive = false;

        public long lastRefTime = 0L;
        public String lastRefTimeStr;

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            Server server = (Server) o;

            return ip.equals(server.ip);

        }

        @Override
        public int hashCode() {
            return ip.hashCode();
        }
    }

    private static class ServerStatusReporter implements Runnable {

        @Override
        public void run() {
            try {

                if (RunningConfig.getServerPort() <= 0) {
                    return;
                }

                for (String key : distroConfig.keySet()) {
                    for (Server server : distroConfig.get(key)) {
                        server.alive = System.currentTimeMillis() - server.lastRefTime < Switch.getdistroServerExpiredMillis();
                    }
                }

                int weight = Runtime.getRuntime().availableProcessors() / 2;
                if (weight <= 0) {
                    weight = 1;
                }

                localhostIP = NetUtils.localServer();

                long curTime = System.currentTimeMillis();
                String status = LOCALHOST_SITE + "#" + localhostIP + "#" + curTime + "#" + weight + "\r\n";

                //send status to itself
                onReceiveServerStatus(status);

                List allServers = NamingProxy.getServers();

                if (!allServers.contains(localhostIP)) {
                    return;
                }

                if (allServers.size() > 0 && !localhostIP.contains(UtilsAndCommons.LOCAL_HOST_IP)) {
                    for (String server : allServers) {
                        if (server.equals(localhostIP)) {
                            continue;
                        }

                        if (!allServers.contains(localhostIP)) {
                            Loggers.SRV_LOG.error("local ip is not in serverlist, ip: {}, serverlist: {}", localhostIP, allServers);
                            return;
                        }

                        Message msg = new Message();
                        msg.setData(status);

                        synchronizer.send(server, msg);

                    }
                }
            } catch (Exception e) {
                Loggers.SRV_LOG.error("[SERVER-STATUS] Exception while sending server status", e);
            } finally {
                UtilsAndCommons.SERVER_STATUS_EXECUTOR.schedule(this, Switch.getServerStatusSynchronizationPeriodMillis(), TimeUnit.MILLISECONDS);
            }

        }
    }

    public static Map> getDistroConfig() {
        return distroConfig;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy