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

com.alibaba.nacos.naming.core.DomainsManager 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.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.naming.misc.*;
import com.alibaba.nacos.naming.push.PushService;
import com.alibaba.nacos.naming.raft.Datum;
import com.alibaba.nacos.naming.raft.RaftCore;
import com.alibaba.nacos.naming.raft.RaftListener;
import com.alibaba.nacos.naming.raft.RaftPeer;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author nkorange
 */
@Component
public class DomainsManager {

    /**
     * Map>
     */
    private Map> serviceMap = new ConcurrentHashMap<>();

    private LinkedBlockingDeque toBeUpdatedDomsQueue = new LinkedBlockingDeque<>(1024 * 1024);

    private Synchronizer synchronizer = new DomainStatusSynchronizer();

    /**
     * thread pool core size
     */
    private final static int DOMAIN_UPDATE_EXECUTOR_NUM = 2;

    private final Lock lock = new ReentrantLock();

    private Map dom2ConditionMap = new ConcurrentHashMap<>();

    private Map dom2LockMap = new ConcurrentHashMap<>();

    public Map getDom2LockMap() {
        return dom2LockMap;
    }

    /**
     * thread pool that processes getting domain detail from other server asynchronously
     */
    private ExecutorService domainUpdateExecutor
        = Executors.newFixedThreadPool(DOMAIN_UPDATE_EXECUTOR_NUM, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("com.alibaba.nacos.naming.domain.update.http.handler");
            t.setDaemon(true);
            return t;
        }
    });

    public Map chooseDomMap(String namespaceId) {
        return serviceMap.get(namespaceId);
    }

    private void initConfig() {

        RaftPeer leader;
        while (true) {

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                Loggers.SRV_LOG.error("[AUTO-INIT] failed to auto init", e);
            }

            try {
                leader = RaftCore.getPeerSet().getLeader();
                if (leader != null) {
                    Loggers.SRV_LOG.info("[AUTO-INIT] leader is: {}", leader.ip);
                    break;
                }

            } catch (Throwable throwable) {
                Loggers.SRV_LOG.error("[AUTO-INIT] failed to auto init", throwable);
            }

        }
    }


    public void addUpdatedDom2Queue(String namespaceId, String domName, String serverIP, String checksum) {
        lock.lock();
        try {
            toBeUpdatedDomsQueue.offer(new DomainKey(namespaceId, domName, serverIP, checksum), 5, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            toBeUpdatedDomsQueue.poll();
            toBeUpdatedDomsQueue.add(new DomainKey(namespaceId, domName, serverIP, checksum));
            Loggers.SRV_LOG.error("[DOMAIN-STATUS] Failed to add domain to be updatd to queue.", e);
        } finally {
            lock.unlock();
        }
    }

    private class UpdatedDomainProcessor implements Runnable {
        //get changed domain  from other server asynchronously

        @Override
        public void run() {
            String domName = null;
            String serverIP = null;

            try {
                while (true) {
                    DomainKey domainKey = null;

                    try {
                        domainKey = toBeUpdatedDomsQueue.take();
                    } catch (Exception e) {
                        Loggers.EVT_LOG.error("[UPDATE-DOMAIN] Exception while taking item from LinkedBlockingDeque.");
                    }

                    if (domainKey == null) {
                        continue;
                    }

                    domName = domainKey.getDomName();
                    serverIP = domainKey.getServerIP();

                    domainUpdateExecutor.execute(new DomUpdater(domainKey.getNamespaceId(), domName, serverIP));
                }
            } catch (Exception e) {
                Loggers.EVT_LOG.error("[UPDATE-DOMAIN] Exception while update dom: {} from {}, error: {}", domName, serverIP, e);
            }
        }
    }

    private class DomUpdater implements Runnable {

        String namespaceId;
        String domName;
        String serverIP;

        public DomUpdater(String namespaceId, String domName, String serverIP) {
            this.namespaceId = namespaceId;
            this.domName = domName;
            this.serverIP = serverIP;
        }

        @Override
        public void run() {
            try {
                updatedDom2(namespaceId, domName, serverIP);
            } catch (Exception e) {
                Loggers.SRV_LOG.warn("[DOMAIN-UPDATER] Exception while update dom: {} from {}, error: {}",
                    domName, serverIP, e);
            }
        }
    }

    public void updatedDom2(String namespaceId, String domName, String serverIP) {
        Message msg = synchronizer.get(serverIP, UtilsAndCommons.assembleFullServiceName(namespaceId, domName));
        JSONObject dom = JSON.parseObject(msg.getData());

        JSONArray ipList = dom.getJSONArray("ips");
        Map ipsMap = new HashMap<>(ipList.size());
        for (int i = 0; i < ipList.size(); i++) {

            String ip = ipList.getString(i);
            String[] strings = ip.split("_");
            ipsMap.put(strings[0], strings[1]);
        }

        VirtualClusterDomain raftVirtualClusterDomain = (VirtualClusterDomain) getDomain(namespaceId, domName);

        if (raftVirtualClusterDomain == null) {
            return;
        }

        List ipAddresses = raftVirtualClusterDomain.allIPs();
        for (IpAddress ipAddress : ipAddresses) {

            Boolean valid = Boolean.parseBoolean(ipsMap.get(ipAddress.toIPAddr()));
            if (valid != ipAddress.isValid()) {
                ipAddress.setValid(valid);
                Loggers.EVT_LOG.info("{} {SYNC} IP-{} : {}@{}",
                    domName, (ipAddress.isValid() ? "ENABLED" : "DISABLED"),
                    ipAddress.getIp(), ipAddress.getPort(), ipAddress.getClusterName());
            }
        }

        PushService.domChanged(raftVirtualClusterDomain.getNamespaceId(), raftVirtualClusterDomain.getName());
        StringBuilder stringBuilder = new StringBuilder();
        List allIps = raftVirtualClusterDomain.allIPs();
        for (IpAddress ipAddress : allIps) {
            stringBuilder.append(ipAddress.toIPAddr()).append("_").append(ipAddress.isValid()).append(",");
        }

        Loggers.EVT_LOG.info("[IP-UPDATED] dom: {}, ips: {}", raftVirtualClusterDomain.getName(), stringBuilder.toString());

    }

    public Set getAllDomNames(String namespaceId) {
        return serviceMap.get(namespaceId).keySet();
    }

    public Map> getAllDomNames() {

        Map> namesMap = new HashMap<>(16);
        for (String namespaceId : serviceMap.keySet()) {
            namesMap.put(namespaceId, serviceMap.get(namespaceId).keySet());
        }
        return namesMap;
    }

    public List getAllDomNamesList(String namespaceId) {
        if (chooseDomMap(namespaceId) == null) {
            return new ArrayList<>();
        }
        return new ArrayList<>(chooseDomMap(namespaceId).keySet());
    }

    public Map> getResponsibleDoms() {
        Map> result = new HashMap<>(16);
        for (String namespaceId : serviceMap.keySet()) {
            result.put(namespaceId, new HashSet<>());
            for (Map.Entry entry : serviceMap.get(namespaceId).entrySet()) {
                Domain domain = entry.getValue();
                if (DistroMapper.responsible(entry.getKey())) {
                    result.get(namespaceId).add(domain);
                }
            }
        }
        return result;
    }

    public int getResponsibleDomCount() {
        int domCount = 0;
        for (String namespaceId : serviceMap.keySet()) {
            for (Map.Entry entry : serviceMap.get(namespaceId).entrySet()) {
                if (DistroMapper.responsible(entry.getKey())) {
                    domCount++;
                }
            }
        }
        return domCount;
    }

    public int getResponsibleIPCount() {
        Map> responsibleDoms = getResponsibleDoms();
        int count = 0;
        for (String namespaceId : responsibleDoms.keySet()) {
            for (Domain domain : responsibleDoms.get(namespaceId)) {
                count += domain.allIPs().size();
            }
        }

        return count;
    }

    public void easyRemoveDom(String namespaceId, String serviceName) throws Exception {

        Domain dom = getDomain(namespaceId, serviceName);

        if (dom != null) {
            RaftCore.signalDelete(UtilsAndCommons.getDomStoreKey(dom));
        }
    }

    public void easyAddOrReplaceDom(Domain newDom) throws Exception {
        VirtualClusterDomain virtualClusterDomain;
        if (newDom instanceof VirtualClusterDomain) {
            virtualClusterDomain = (VirtualClusterDomain) newDom;
            newDom = virtualClusterDomain;
        }
        RaftCore.doSignalPublish(UtilsAndCommons.getDomStoreKey(newDom), JSON.toJSONString(newDom), true);
    }

    public void easyAddIP4Dom(String namespaceId, String domName, List ips, long term) throws Exception {
        easyUpdateIP4Dom(namespaceId, domName, ips, term, "add");
    }

    public void easyRemvIP4Dom(String namespaceId, String domName, List ips, long term) throws Exception {
        easyUpdateIP4Dom(namespaceId, domName, ips, term, "remove");
    }

    public void easyUpdateIP4Dom(String namespaceId, String domName, List ips, long term, String action) throws Exception {

        VirtualClusterDomain dom = (VirtualClusterDomain) chooseDomMap(namespaceId).get(domName);
        if (dom == null) {
            throw new IllegalArgumentException("dom doesn't exist: " + domName);
        }

        try {

            if (!dom.getEnableClientBeat()) {
                getDom2LockMap().get(UtilsAndCommons.assembleFullServiceName(namespaceId, domName)).lock();
            }

            Datum datum1 = RaftCore.getDatum(UtilsAndCommons.getIPListStoreKey(dom));
            String oldJson = StringUtils.EMPTY;

            if (datum1 != null) {
                oldJson = datum1.value;
            }

            List ipAddresses;
            List currentIPs = dom.allIPs();
            Map map = new ConcurrentHashMap(currentIPs.size());

            for (IpAddress ipAddress : currentIPs) {
                map.put(ipAddress.toIPAddr(), ipAddress);
            }

            ipAddresses = setValid(oldJson, map);

            Map ipAddressMap = new HashMap(ipAddresses.size());

            for (IpAddress ipAddress : ipAddresses) {
                ipAddressMap.put(ipAddress.getDatumKey(), ipAddress);
            }

            for (IpAddress ipAddress : ips) {
                if (!dom.getClusterMap().containsKey(ipAddress.getClusterName())) {
                    Cluster cluster = new Cluster(ipAddress.getClusterName());
                    cluster.setDom(dom);
                    dom.getClusterMap().put(ipAddress.getClusterName(), cluster);
                    Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
                        ipAddress.getClusterName(), ipAddress.toJSON());
                }

                if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
                    ipAddressMap.remove(ipAddress.getDatumKey());
                } else {
                    ipAddressMap.put(ipAddress.getDatumKey(), ipAddress);
                }

            }

            if (ipAddressMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
                throw new IllegalArgumentException("ip list can not be empty, dom: " + dom.getName() + ", ip list: "
                    + JSON.toJSONString(ipAddressMap.values()));
            }

            Loggers.EVT_LOG.info("{} {POS} {IP-UPDATE} {}, action: {}", dom, ips, action);

            String key = UtilsAndCommons.getIPListStoreKey(dom);
            String value = JSON.toJSONString(ipAddressMap.values());

            Datum datum = new Datum();
            datum.key = key;
            datum.value = value;

            datum.timestamp.set(datum1 == null ? 1 : datum1.timestamp.get() + 1);

            Loggers.RAFT.info("datum " + key + " updated:" + datum.timestamp.get());

            RaftPeer peer = new RaftPeer();
            peer.ip = RaftCore.getLeader().ip;
            peer.term.set(term);
            peer.voteFor = RaftCore.getLeader().voteFor;
            peer.heartbeatDueMs = RaftCore.getLeader().heartbeatDueMs;
            peer.leaderDueMs = RaftCore.getLeader().leaderDueMs;
            peer.state = RaftCore.getLeader().state;

            boolean increaseTerm = !((VirtualClusterDomain) getDomain(namespaceId, domName)).getEnableClientBeat();

            RaftCore.onPublish(datum, peer, increaseTerm);
        } finally {
            if (!dom.getEnableClientBeat()) {
                getDom2LockMap().get(UtilsAndCommons.assembleFullServiceName(namespaceId, domName)).unlock();
            }
        }

    }

    private List setValid(String oldJson, Map map) {
        List ipAddresses = new ArrayList<>();
        if (StringUtils.isNotEmpty(oldJson)) {
            try {
                ipAddresses = JSON.parseObject(oldJson, new TypeReference>() {
                });
                for (IpAddress ipAddress : ipAddresses) {
                    IpAddress ipAddress1 = map.get(ipAddress.toIPAddr());
                    if (ipAddress1 != null) {
                        ipAddress.setValid(ipAddress1.isValid());
                        ipAddress.setLastBeat(ipAddress1.getLastBeat());
                    }
                }
            } catch (Throwable throwable) {
                Loggers.RAFT.error("error while processing json: " + oldJson, throwable);
            } finally {
                if (ipAddresses == null) {
                    ipAddresses = new ArrayList<>();
                }
            }
        }

        return ipAddresses;
    }

    public Domain getDomain(String namespaceId, String domName) {
        if (serviceMap.get(namespaceId) == null) {
            return null;
        }
        return chooseDomMap(namespaceId).get(domName);
    }

    public void putDomain(VirtualClusterDomain domain) {
        if (!serviceMap.containsKey(domain.getNamespaceId())) {
            serviceMap.put(domain.getNamespaceId(), new ConcurrentHashMap<>(16));
        }
        serviceMap.get(domain.getNamespaceId()).put(domain.getName(), domain);
    }


    public List searchDomains(String namespaceId, String regex) {
        List result = new ArrayList();
        for (Map.Entry entry : chooseDomMap(namespaceId).entrySet()) {
            Domain dom = entry.getValue();

            String key = dom.getName() + ":" + ArrayUtils.toString(dom.getOwners());
            if (key.matches(regex)) {
                result.add(dom);
            }
        }

        return result;
    }

    public int getDomCount() {
        int domCount = 0;
        for (String namespaceId : serviceMap.keySet()) {
            domCount += serviceMap.get(namespaceId).size();
        }
        return domCount;
    }

    public int getInstanceCount() {
        int total = 0;
        for (String namespaceId : serviceMap.keySet()) {
            for (Domain domain : serviceMap.get(namespaceId).values()) {
                total += domain.allIPs().size();
            }
        }
        return total;
    }

    public Map getDomMap(String namespaceId) {
        return serviceMap.get(namespaceId);
    }

    public int getPagedDom(String namespaceId, int startPage, int pageSize, String keyword, List domainList) {

        List matchList;

        if (chooseDomMap(namespaceId) == null) {
            return 0;
        }

        if (StringUtils.isNotBlank(keyword)) {
            matchList = searchDomains(namespaceId, ".*" + keyword + ".*");
        } else {
            matchList = new ArrayList(chooseDomMap(namespaceId).values());
        }

        if (pageSize >= matchList.size()) {
            domainList.addAll(matchList);
            return matchList.size();
        }

        for (int i = 0; i < matchList.size(); i++) {
            if (i < startPage * pageSize) {
                continue;
            }

            domainList.add(matchList.get(i));

            if (domainList.size() >= pageSize) {
                break;
            }
        }

        return matchList.size();
    }

    public static class DomainChecksum {

        public String namespaceId;
        public Map domName2Checksum = new HashMap();

        public DomainChecksum() {
            this.namespaceId = Constants.REQUEST_PARAM_DEFAULT_NAMESPACE_ID;
        }

        public DomainChecksum(String namespaceId) {
            this.namespaceId = namespaceId;
        }

        public void addItem(String domName, String checksum) {
            if (StringUtils.isEmpty(domName) || StringUtils.isEmpty(checksum)) {
                Loggers.SRV_LOG.warn("[DOMAIN-CHECKSUM] domName or checksum is empty,domName: {}, checksum: {}",
                    domName, checksum);
                return;
            }

            domName2Checksum.put(domName, checksum);
        }
    }

    private class DomainReporter implements Runnable {

        @Override
        public void run() {
            try {

                Map> allDomainNames = getAllDomNames();

                if (allDomainNames.size() <= 0) {
                    //ignore
                    return;
                }

                for (String namespaceId : allDomainNames.keySet()) {

                    DomainChecksum checksum = new DomainChecksum(namespaceId);

                    for (String domName : allDomainNames.get(namespaceId)) {
                        if (!DistroMapper.responsible(domName)) {
                            continue;
                        }

                        Domain domain = getDomain(namespaceId, domName);

                        if (domain == null || domain instanceof SwitchDomain) {
                            continue;
                        }

                        domain.recalculateChecksum();

                        checksum.addItem(domName, domain.getChecksum());
                    }

                    Message msg = new Message();

                    msg.setData(JSON.toJSONString(checksum));

                    List sameSiteServers = NamingProxy.getSameSiteServers().get("sameSite");

                    if (sameSiteServers == null || sameSiteServers.size() <= 0 || !NamingProxy.getServers().contains(NetUtils.localServer())) {
                        return;
                    }

                    for (String server : sameSiteServers) {
                        if (server.equals(NetUtils.localServer())) {
                            continue;
                        }
                        synchronizer.send(server, msg);
                    }
                }
            } catch (Exception e) {
                Loggers.SRV_LOG.error("[DOMAIN-STATUS] Exception while sending domain status", e);
            } finally {
                UtilsAndCommons.DOMAIN_SYNCHRONIZATION_EXECUTOR.schedule(this, Switch.getDomStatusSynchronizationPeriodMillis(), TimeUnit.MILLISECONDS);
            }
        }
    }

    public DomainsManager() {
        // wait until distro-mapper ready because domain distribution check depends on it
        while (DistroMapper.getLiveSites().size() == 0) {
            try {
                Thread.sleep(TimeUnit.SECONDS.toMillis(1L));
            } catch (InterruptedException ignore) {
            }
        }

        UtilsAndCommons.DOMAIN_SYNCHRONIZATION_EXECUTOR.schedule(new DomainReporter(), 60000, TimeUnit.MILLISECONDS);

        UtilsAndCommons.DOMAIN_UPDATE_EXECUTOR.submit(new UpdatedDomainProcessor());

        UtilsAndCommons.INIT_CONFIG_EXECUTOR.submit(new Runnable() {
            @Override
            public void run() {
                initConfig();
            }
        });

        final RaftListener raftListener = new RaftListener() {
            @Override
            public boolean interests(String key) {
                return StringUtils.startsWith(key, UtilsAndCommons.DOMAINS_DATA_ID_PRE);
            }

            @Override
            public boolean matchUnlistenKey(String key) {
                return StringUtils.equals(key, UtilsAndCommons.DOMAINS_DATA_ID_PRE + "*");
            }

            @SuppressFBWarnings("JLM_JSR166_LOCK_MONITORENTER")
            @Override
            public void onChange(String key, String value) throws Exception {
                try {
                    if (StringUtils.isEmpty(value)) {
                        Loggers.SRV_LOG.warn("received empty push from raft, key: {}", key);
                        return;
                    }

                    VirtualClusterDomain dom = VirtualClusterDomain.fromJSON(value);
                    if (dom == null) {
                        throw new IllegalStateException("dom parsing failed, json: " + value);
                    }

                    if (StringUtils.isBlank(dom.getNamespaceId())) {
                        dom.setNamespaceId(UtilsAndCommons.getDefaultNamespaceId());
                    }

                    Loggers.RAFT.info("[RAFT-NOTIFIER] datum is changed, key: {}, value: {}", key, value);

                    Domain oldDom = getDomain(dom.getNamespaceId(), dom.getName());

                    if (oldDom != null) {
                        oldDom.update(dom);
                    } else {

                        addLockIfAbsent(UtilsAndCommons.assembleFullServiceName(dom.getNamespaceId(), dom.getName()));

                        putDomain(dom);
                        dom.init();
                        Loggers.SRV_LOG.info("[NEW-DOM-RAFT] {}", dom.toJSON());
                    }

                    wakeUp(UtilsAndCommons.assembleFullServiceName(dom.getNamespaceId(), dom.getName()));

                } catch (Throwable e) {
                    Loggers.SRV_LOG.error("[NACOS-DOM] error while processing dom update", e);
                }
            }

            @Override
            public void onDelete(String key, String value) throws Exception {
                String domKey = StringUtils.removeStart(key, UtilsAndCommons.DOMAINS_DATA_ID_PRE);
                String namespace = domKey.split(UtilsAndCommons.SERVICE_GROUP_CONNECTOR)[0];
                String name = domKey.split(UtilsAndCommons.SERVICE_GROUP_CONNECTOR)[1];
                Domain dom = chooseDomMap(namespace).remove(name);
                Loggers.RAFT.info("[RAFT-NOTIFIER] datum is deleted, key: {}, value: {}", key, value);

                if (dom != null) {
                    dom.destroy();
                    Loggers.SRV_LOG.info("[DEAD-DOM] {}", dom.toJSON());
                }
            }
        };
        RaftCore.listen(raftListener);

    }

    public void wakeUp(String key) {

        Lock lock = dom2LockMap.get(key);
        Condition condition = dom2ConditionMap.get(key);

        try {
            lock.lock();
            condition.signalAll();
        } catch (Exception ignore) {
        } finally {
            lock.unlock();
        }
    }

    public Lock addLockIfAbsent(String key) {

        if (dom2LockMap.containsKey(key)) {
            return dom2LockMap.get(key);
        }
        Lock lock = new ReentrantLock();
        dom2LockMap.put(key, lock);
        return lock;
    }

    public Condition addCondtion(String key) {
        Condition condition = dom2LockMap.get(key).newCondition();
        dom2ConditionMap.put(key, condition);
        return condition;
    }

    private static class DomainKey {
        private String namespaceId;
        private String domName;
        private String serverIP;

        public String getChecksum() {
            return checksum;
        }

        public String getServerIP() {
            return serverIP;
        }

        public String getDomName() {
            return domName;
        }

        public String getNamespaceId() {
            return namespaceId;
        }

        private String checksum;

        public DomainKey(String namespaceId, String domName, String serverIP, String checksum) {
            this.namespaceId = namespaceId;
            this.domName = domName;
            this.serverIP = serverIP;
            this.checksum = checksum;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy