深入解析Nacos源码注册中心AP模式之服务注册

Nacos有两种模式,一个是AP模式(最终一致性),一个是CP模式(强一致性),通常我们平时使用最多的就是它的AP模式,所以这里我们就来看一下对于一个注册中心来说,最基础的服务注册功能在Nacos中的实现原理是怎样的。

com.alibaba.nacos.naming.controllers.InstanceController#register

首先先说明一下Nacos的服务端其实就是nacos-naming这个模块,这个模块就是一个springboot项目,所以我们可以通过官方文档直接找到它的注册接口(Open API 指南),然后根据这个接口去源码里面去搜索就可以找到接口方法:

/**
 * 注册新实例
 *
 * @param request HttpServletRequest
 * @return 注册成功返回ok
 * @throws Exception any error during register
 */
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {

    // 获取命名空间,从请求域参数中获取key为namespaceId的目标值,如果获取不到返回的值为public
    final String namespaceId = WebUtils
            .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    // 获取服务名,从请求域参数中获取key为serviceName的目标值,如果获取不到会抛出异常
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    // 检查传进来的服务名的格式,服务名不能带有“@@”并且服务名不能为空
    NamingUtils.checkServiceNameFormat(serviceName);

    // 根据解析请求域参数得到对应参数值填充到服务实例对象中
    final Instance instance = parseInstance(request);
    // 注册服务实例
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

前面的代码都是去获取到请求的参数值,然后对参数值进行一些检验,构造出要注册的服务实例对象,关键调用registerInstance方法

com.alibaba.nacos.naming.core.ServiceManager#registerInstance

/**
 * 以AP模式将实例注册到服务
 *
 * <p>T如果服务或集群不存在,此方法会静默地创建它们
 *
 * @param namespaceId 命名空间id
 * @param serviceName 服务名称
 * @param instance    服务注册实例
 * @throws Exception any error occurred in the process
 */
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {

    // 如果服务不存在,初始化服务对象并把它放到双层map结构缓存中
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    // 获取服务对象
    Service service = getService(namespaceId, serviceName);
    // 获取不到服务对象抛异常
    if (service == null) {
        throw new NacosException(NacosException.INVALID_PARAM,
                "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }

    // 向服务对象中添加服务实例
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

这个方法主要有三步:

1.初始化服务对象

2.获取服务对象

3.向服务对象中添加要注册的新实例对象

我们先来看第一步

com.alibaba.nacos.naming.core.ServiceManager#createEmptyService

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
    createServiceIfAbsent(namespaceId, serviceName, local, null);
}
/**
 * 如果服务不存在,创建对应的服务对象并把它放到双层map结构缓存中
 *
 * @param namespaceId namespace
 * @param serviceName service name
 * @param local       whether create service by local
 * @param cluster     cluster
 * @throws NacosException nacos exception
 */
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
        throws NacosException {
    Service service = getService(namespaceId, serviceName);

    // 条件成立:1.该命名空间还未初始化
    //          2.该服务还未初始化
    if (service == null) {
        
        Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
        // 构造一个service对象
        service = new Service();
        // 设置服务名称
        service.setName(serviceName);
        // 设置所属的命名空间id
        service.setNamespaceId(namespaceId);
        // 设置所属的组名
        service.setGroupName(NamingUtils.getGroupName(serviceName));
        // now validate the service. if failed, exception will be thrown
        // 设置最近一次的修改时间
        service.setLastModifiedMillis(System.currentTimeMillis());
        service.recalculateChecksum();
        if (cluster != null) {
            cluster.setService(service);
            service.getClusterMap().put(cluster.getName(), cluster);
        }
        service.validate();
        // 该方法做了三件事
        // 1.把创建好的service对象放到双层map缓存结构中
        // 2.启动检查该服务下实例心跳的线程
        // 3.给该服务注册实例变化的监听器
        putServiceAndInit(service);
        // 条件成立:要注册的实例不是一个临时实例
        if (!local) {
            addOrReplaceService(service);
        }
    }
}

一开始就是再去内存中根据命名空间id和服务名称再去拿一遍服务对象,如果该服务是第一次注册的话那么肯定是为空,所以就会创建一个服务对象并对其进行初始化,接着就会在putServiceAndInit方法中把服务对象放进内存中了

/**
 * 该方法做了三件事
 * 1.把创建好的service对象放到双层map缓存结构中
 * 2.启动检查心跳的线程
 * 3.给该服务设置实例变动监听
 * @param service   服务对象
 * @throws NacosException
 */
private void putServiceAndInit(Service service) throws NacosException {
    // 把服务对象放到双层map中
    putService(service);
    service = getService(service.getNamespaceId(), service.getName());
    service.init();
    // 给指定的服务设置监听器,监听器的作用就是当这个服务发生了实例上的变动,比如新增或者删除,就会触发回调对应的监听方法
    // 可以看到service对象自身实现了监听器接口,所以service本身也是一个监听器,这里直接把service传进去了,当该服务实例发生变动之后会执行service中实现的监听回调方法
    // 设置临时实例变动的监听器
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
    // 设置持久化实例变动的监听器
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
    Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}
/**
 * 把服务对象放到双层map中
 *
 * @param service 服务对象
 */
public void putService(Service service) {
    if (!serviceMap.containsKey(service.getNamespaceId())) {
        synchronized (putServiceLock) {
            if (!serviceMap.containsKey(service.getNamespaceId())) {
                serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());
            }
        }
    }
    serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
}

可以看到在putService方法中,就是把服务对象放进内存中的,而这个内存结构其实就是一个双层map结构

/**
 * Map(namespace, Map(group::serviceName, Service)).
 * 利用双层map结构去存储service对象,达到可以通过namespace与group的隔离效果
 * key:命名空间id
 * value:key=>组名::服务名称
 *        value=>service对象
 */
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

第一层map通过命名空间id去对服务进行隔离,第二层map通过组名+服务名进行实例的隔离,也就是如下图的结构:

 在把服务对象放进了双层map结构后,就调用它的init方法做一些初始化的工作,这里与实例的心跳检查有关,先不看。然后注意下面有两行代码,我们关注第一行就行(AP模式),这行代码大概的意思就是注册了一个监听器,具体是用来干嘛的我们后面就会知道

com.alibaba.nacos.naming.core.ServiceManager#addInstance

/**
 * 向服务对象中添加服务实例
 *
 * @param namespaceId 命名空间id
 * @param serviceName 服务名称
 * @param ephemeral   添加的服务实例是否是临时实例
 * @param ips         需要添加的服务实例对象
 * @throws NacosException nacos exception
 */
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException {

    // 如果是临时实例,返回com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName
    // 如果是持久化实例,返回com.alibaba.nacos.naming.iplist.namespaceId##serviceName
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
    // 获取服务对象
    Service service = getService(namespaceId, serviceName);

    // 对服务对象加锁,使得同一个服务下的实例串行注册
    synchronized (service) {
        // 获取到新增实例之后该服务下最新的实例集合
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
        
        Instances instances = new Instances();
        instances.setInstanceList(instanceList);
        
        consistencyService.put(key, instances);
    }
}

在这个方法中会先去根据命名空间id+服务名称构造一个key,再获取到对应的服务对象,接着就是会去调用addIpAddresses方法

/**
 * Compare and get new instance list.
 *
 * @param service   服务对象
 * @param action    操作类型{@link UtilsAndCommons#UPDATE_INSTANCE_ACTION_REMOVE} or {@link UtilsAndCommons#UPDATE_INSTANCE_ACTION_ADD}
 * @param ephemeral 是否是临时实例
 * @param ips       要操作的服务实例集合
 * @return 操作后的服务实例列表
 * @throws NacosException nacos exception
 */
public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)
        throws NacosException {

    // 获取到该服务下面所有的服务实例
    Datum datum = consistencyService
            .get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));

    // 获取该服务下面所有的临时实例 / 持久化实例
    List<Instance> currentIPs = service.allIPs(ephemeral);
    Map<String, Instance> currentInstances = new HashMap<>(currentIPs.size());
    Set<String> currentInstanceIds = Sets.newHashSet();

    // 遍历该服务下面所有的临时实例 / 持久化实例
    for (Instance instance : currentIPs) {
        currentInstances.put(instance.toIpAddr(), instance);
        currentInstanceIds.add(instance.getInstanceId());
    }
    
    Map<String, Instance> instanceMap;
    if (datum != null && null != datum.value) {
        instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);
    } else {
        instanceMap = new HashMap<>(ips.length);
    }

    // 遍历要操作的实例集合
    for (Instance instance : ips) {
        // 该服务下面没有这个实例对应的集群
        if (!service.getClusterMap().containsKey(instance.getClusterName())) {
            // 初始化集群对象
            Cluster cluster = new Cluster(instance.getClusterName(), service);
            // TODO 服务实例心跳有关
            cluster.init();
            // 把初始化的集群放到该服务下面
            service.getClusterMap().put(instance.getClusterName(), cluster);
            Loggers.SRV_LOG
                    .warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
                            instance.getClusterName(), instance.toJson());
        }

        // 条件成立:action == UPDATE_INSTANCE_ACTION_REMOVE
        if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
            instanceMap.remove(instance.getDatumKey());
        }
        // 条件成立:action == UPDATE_INSTANCE_ACTION_ADD
        else {
            Instance oldInstance = instanceMap.get(instance.getDatumKey());
            // 条件成立:说明遍历到的这个实例已经有了,所以直接使用旧实例的id
            if (oldInstance != null) {
                instance.setInstanceId(oldInstance.getInstanceId());
            }
            // 条件成立:说明遍历到的这个实例之前并没有,属于新增实例
            else {
                // 根据指定的规则生成实例id
                instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));
            }
            // 把更新或者新增的实例放到instanceMap中
            instanceMap.put(instance.getDatumKey(), instance);
        }
        
    }
    
    if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
        throw new IllegalArgumentException(
                "ip list can not be empty, service: " + service.getName() + ", ip list: " + JacksonUtils
                        .toJson(instanceMap.values()));
    }

    // 返回操作后的实例集合
    return new ArrayList<>(instanceMap.values());
}

这个方法很长,里面做的事情大概说一下,其实就是获取到原来内存中所有的实例集合,然后在这个集合中新增或者移除实例,最后返回一个操作完之后的实例集合,而我们这里服务注册,所以该方法返回的就是原来的实例 + 要注册的实例

Instances instances = new Instances();
instances.setInstanceList(instanceList);

接着创建一个Instances对象,把原来的实例 + 要注册的实例放到这个对象中,最后会根据是否是临时节点去调用不同的持久化组件的put方法,而我们这里由于是AP模式,所以注册的是一个临时实例,具体的持久化组件就是DistroConsistencyServiceImpl

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#put

public void put(String key, Record value) throws NacosException {
    onPut(key, value);
    // 同步数据到nacos集群中的其他节点
    distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
            globalConfig.getTaskDispatchPeriod() / 2);
}

在这个方法中我们只关注onPut方法就行

public void onPut(String key, Record value) {

    // 条件成立:操作的record是临时的
    if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
        Datum<Instances> datum = new Datum<>();
        datum.value = (Instances) value;
        datum.key = key;
        datum.timestamp.incrementAndGet();
        dataStore.put(key, datum);
    }

    // listeners中没有这个key的监听器
    if (!listeners.containsKey(key)) {
        // 直接返回
        return;
    }

    // 向通知线程的阻塞队列中新增一个pair,通知线程会不断地从阻塞队列中拿到pair对象,根据pair对象的key从dataStore中获取到datum,再从datum对象中获取到record
    // 然后根据key找到对应的监听器队列,根据动作类型去回调这些监听器队列所对应的方法,并把上面获取到的record作为参数给监听回调方法
    notifier.addTask(key, DataOperation.CHANGE);
}
public void addTask(String datumKey, DataOperation action) {
    // 表示该服务的变更操作正在进行中
    if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
        return;
    }
    if (action == DataOperation.CHANGE) {
        services.put(datumKey, StringUtils.EMPTY);
    }
    tasks.offer(Pair.with(datumKey, action));
}

在onPut方法中会判断当前的操作是否是一个临时实例的操作,如果是的话就会把全量实例集合封装成Datum对象,然后通过DataStore存储数据组件存放到内存中,接着就把前面构造的key以及操作类型放到一个队列中。代码执行到这里,我们似乎并没有看到把实例集合放到内存中的操作,其实Nacos在处理这一步的时候,是采用的异步化的方式去执行的,肯定不用想就知道会有一个异步线程去从队列中获取数据然后进行真正的实例注册操作,那么这个线程在哪里启动的呢?

/**
 * 在当前bean初始化之后调用,开启一个线程池执行notifier这个任务
 */
@PostConstruct
public void init() {
    GlobalExecutor.submitDistroNotifyTask(notifier);
}

寻找了一遍,发现在DistroConsistencyServiceImpl的init方法中会通过线程池去启动该线程,而这个init方法上有@PostConstruct注解,所以在DistroConsistencyServiceImpl这个类被spring容器加载的生命周期过程中,init方法就会被调用。下面我们主要来看一下线程执行的任务是什么逻辑

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier

/**
 * 该任务会在nacos服务启动的时候被线程池所执行
 * 执行的时候会开启一个死循环不断地从阻塞队列中获取pair对象并处理,pair对象封装了key和动作类型
 * 根据key可以获取对应的监听器队列,根据动作类型可以选择回调监听器的哪个回调方法
 * 其中当执行实例注册的时候最终就会由监听器监听到并执行具体的注册操作,通过这种方法可以使得整个实例注册流程异步化,提升并发性能
 */
public class Notifier implements Runnable {

    /**
     * key: com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName
     *      com.alibaba.nacos.naming.iplist.namespaceId##serviceName
     * value: StringUtils.EMPTY
     */
    private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
    
    private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
    
    /**
     * 向队列中添加一个通知任务
     *
     * @param datumKey com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName
     *                 com.alibaba.nacos.naming.iplist.namespaceId##serviceName
     * @param action   动作类型
     */
    public void addTask(String datumKey, DataOperation action) {
        // 表示该服务的变更操作正在进行中
        if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
            return;
        }
        if (action == DataOperation.CHANGE) {
            services.put(datumKey, StringUtils.EMPTY);
        }
        tasks.offer(Pair.with(datumKey, action));
    }
    
    public int getTaskSize() {
        return tasks.size();
    }
    
    @Override
    public void run() {
        Loggers.DISTRO.info("distro notifier started");

        // 该任务开启了一个死循环,一直从tasks队列中获取pair对象
        for (; ; ) {
            try {
                Pair<String, DataOperation> pair = tasks.take();
                // 处理从tasks中获取到的pair对象
                handle(pair);
            } catch (Throwable e) {
                Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
            }
        }
    }
    
    private void handle(Pair<String, DataOperation> pair) {
        try {
            // datumKey == com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName
            // datumKey == com.alibaba.nacos.naming.iplist.namespaceId##serviceName
            String datumKey = pair.getValue0();
            // 操作类型
            DataOperation action = pair.getValue1();
            
            services.remove(datumKey);
            
            int count = 0;

            // listeners中没有这个key的监听器
            if (!listeners.containsKey(datumKey)) {
                // 直接返回
                return;
            }

            // 回调该key所对应的所有监听器
            for (RecordListener listener : listeners.get(datumKey)) {
                
                count++;
                
                try {
                    // 条件成立:操作类型是CHANGE
                    if (action == DataOperation.CHANGE) {
                        // 回调监听器的onChange方法
                        listener.onChange(datumKey, dataStore.get(datumKey).value);
                        continue;
                    }

                    // 条件成立:操作类型是DELETE
                    if (action == DataOperation.DELETE) {
                        // 回调监听器的onDelete方法
                        listener.onDelete(datumKey);
                        continue;
                    }
                } catch (Throwable e) {
                    Loggers.DISTRO.error("[NACOS-DISTRO] error while notifying listener of key: {}", datumKey, e);
                }
            }
            
            if (Loggers.DISTRO.isDebugEnabled()) {
                Loggers.DISTRO
                        .debug("[NACOS-DISTRO] datum change notified, key: {}, listener count: {}, action: {}",
                                datumKey, count, action.name());
            }
        } catch (Throwable e) {
            Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
        }
    }
}

该任务会开启一个死循环,这个死循环会不断地从队列中获取Pair对象,其实就是一个键值对,key就是命名空间id+服务名称,value是操作类型,我们根据上面的代码可以知道这个操作类型就是DataOperation.CHANGE,接着把pair对象交给handle方法去处理。在handle方法中会根据pair对象中的key从listener集合中找到对应的监听器集合,那么这个监听器集合是什么时候放进来的呢?不要忘了我们上面说的,在服务对象放进双层map结构之后,有一行代码就是注册监听器的,我们现在重新看一下这行代码

consistencyService
        .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
/**
 * 给指定的key注册一个监听器
 * @param key      com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName
 * @param listener 监听器实例
 * @throws NacosException
 */
@Override
public void listen(String key, RecordListener listener) throws NacosException {
    // 条件成立:listeners中没有key为com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName的监听器队列
    if (!listeners.containsKey(key)) {
        // 给该key初始化一个空的监听器队列
        listeners.put(key, new ConcurrentLinkedQueue<>());
    }

    // 条件成立:key为com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName的监听器队列中已经包含了传进来的监听器
    if (listeners.get(key).contains(listener)) {
        return;
    }

    // 给这个key对应的监听器队列增加一个监听器
    listeners.get(key).add(listener);
}

可以看到注册的监听器对应的key就是命名空间id+服务名称,监听器对象是this,也就是Service对象本身。那么操作类型是DataOperation.CHANGE的时候会调用监听器的onChange方法,所以我们现在看service类的onChange方法

com.alibaba.nacos.naming.core.Service#onChange

/**
 * 当该服务下的实例发生变化时,就会回调该监听方法
 * @param key   com.alibaba.nacos.naming.iplist.ephemeral.namespaceId##serviceName
 * @param value 变化之后该服务下最新的实例集合
 * @throws Exception
 */
@Override
public void onChange(String key, Instances value) throws Exception {
    
    Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);

    // 遍历最新的实例集合
    for (Instance instance : value.getInstanceList()) {

        // 实例为null,抛出异常
        if (instance == null) {
            // Reject this abnormal instance list:
            throw new RuntimeException("got null instance " + key);
        }

        // 设置的权重最大只能是10000
        if (instance.getWeight() > 10000.0D) {
            instance.setWeight(10000.0D);
        }

        // 设置的权重小于0.01并大于0就强制设置为0.01
        if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) {
            instance.setWeight(0.01D);
        }
    }

    // 更新该服务下最新的实例
    updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));
    // 重新计算服务的校验和
    recalculateChecksum();
}

关键就是updateIps方法

/**
 * 更新该服务下最新的实例,通过写时复制的方式,更新的时候先去复制出原来的一份实例集合,在复制的这个集合上面进行修改,最后替换掉原来旧的集合
 *
 * @param instances 该服务下最新的实例集合
 * @param ephemeral whether is ephemeral instance
 */
public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
    // 存放该服务下最新的实例
    Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());
    for (String clusterName : clusterMap.keySet()) {
        ipMap.put(clusterName, new ArrayList<>());
    }

    // 遍历该服务下最新的实例集合
    for (Instance instance : instances) {
        try {
            if (instance == null) {
                Loggers.SRV_LOG.error("[NACOS-DOM] received malformed ip: null");
                continue;
            }
            
            if (StringUtils.isEmpty(instance.getClusterName())) {
                instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME);
            }

            // 如果服务下面没有这个新增实例所在的集群,那么就在该服务下面初始化一个集群
            if (!clusterMap.containsKey(instance.getClusterName())) {
                Loggers.SRV_LOG
                        .warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
                                instance.getClusterName(), instance.toJson());
                Cluster cluster = new Cluster(instance.getClusterName(), this);
                cluster.init();
                getClusterMap().put(instance.getClusterName(), cluster);
            }

            // 如果服务下面没有这个新增实例所在的集群,同时也对复制出来的实例集合进行集群的初始化
            List<Instance> clusterIPs = ipMap.get(instance.getClusterName());
            if (clusterIPs == null) {
                clusterIPs = new LinkedList<>();
                ipMap.put(instance.getClusterName(), clusterIPs);
            }

            // 把新增的实例放到复制的实例集合中
            clusterIPs.add(instance);
        } catch (Exception e) {
            Loggers.SRV_LOG.error("[NACOS-DOM] failed to process ip: " + instance, e);
        }
    }


    // 遍历最新的实例集合
    for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
        //make every ip mine
        // 获取这个集群下所有的最新实例
        List<Instance> entryIPs = entry.getValue();
        // 对该服务下每一个集群更新最新的实例集合
        clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
    }

    // 更新修改时间
    setLastModifiedMillis(System.currentTimeMillis());
    // 服务变动之后推送最新实例信息给客户端
    getPushService().serviceChanged(this);
    StringBuilder stringBuilder = new StringBuilder();
    
    for (Instance instance : allIPs()) {
        stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(",");
    }
    
    Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(),
            stringBuilder.toString());
    
}

实例集合都是保存在clusterMap中的,所以核心调用Cluster的updateIps方法对集群里面实例进行更新

public void updateIps(List<Instance> ips, boolean ephemeral) {

    // 获取到该集群下旧的实例集合
    Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
    
    HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());

    // 遍历该集群下旧的实例集合
    for (Instance ip : toUpdateInstances) {
        oldIpMap.put(ip.getDatumKey(), ip);
    }

    // 找到新增的实例集合
    List<Instance> updatedIPs = updatedIps(ips, oldIpMap.values());
    // 条件成立:说明更新的实例都包含旧的实例,并且还有新增的实例
    if (updatedIPs.size() > 0) {
        // 遍历新增的实例集合
        for (Instance ip : updatedIPs) {
            Instance oldIP = oldIpMap.get(ip.getDatumKey());
            
            // do not update the ip validation status of updated ips
            // because the checker has the most precise result
            // Only when ip is not marked, don't we update the health status of IP:
            if (!ip.isMarked()) {
                ip.setHealthy(oldIP.isHealthy());
            }
            
            if (ip.isHealthy() != oldIP.isHealthy()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} IP-{} {}:{}@{}", getService().getName(),
                        (ip.isHealthy() ? "ENABLED" : "DISABLED"), ip.getIp(), ip.getPort(), getName());
            }
            
            if (ip.getWeight() != oldIP.getWeight()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} {IP-UPDATED} {}->{}", getService().getName(), oldIP.toString(),
                        ip.toString());
            }
        }
    }
    
    List<Instance> newIPs = subtract(ips, oldIpMap.values());
    if (newIPs.size() > 0) {
        Loggers.EVT_LOG
                .info("{} {SYNC} {IP-NEW} cluster: {}, new ips size: {}, content: {}", getService().getName(),
                        getName(), newIPs.size(), newIPs.toString());
        
        for (Instance ip : newIPs) {
            HealthCheckStatus.reset(ip);
        }
    }
    
    List<Instance> deadIPs = subtract(oldIpMap.values(), ips);
    
    if (deadIPs.size() > 0) {
        Loggers.EVT_LOG
                .info("{} {SYNC} {IP-DEAD} cluster: {}, dead ips size: {}, content: {}", getService().getName(),
                        getName(), deadIPs.size(), deadIPs.toString());
        
        for (Instance ip : deadIPs) {
            HealthCheckStatus.remv(ip);
        }
    }
    
    toUpdateInstances = new HashSet<>(ips);

    if (ephemeral) {
        // 更新该集合下最新的临时实例集合
        ephemeralInstances = toUpdateInstances;
    } else {
        // 更新该集合下最新的持久化实例集合
        persistentInstances = toUpdateInstances;
    }
}
/**
 * 当前集群的持久化服务实例集合
 */
@JsonIgnore
private Set<Instance> persistentInstances = new HashSet<>();

/**
 * 当前集群的临时服务实例集合
 */
@JsonIgnore
private Set<Instance> ephemeralInstances = new HashSet<>();

这个方法中其实用到的就是写时复制的一个思想,先对原来集群中的实例复制一份,然后通过要更新的新实例集合与原来的旧实例集合进行一系列的比较,最后把最新的的实例集合赋值给ephemeralInstances变量,自此整个实例注册的流程的完成了

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值