spring clound alibaba 之 nacos 服务端注册流程源码解析

服务注册

         在上一章,我们说完了nacos客户端的自动注册流程,知道最后是客户端向服务端发送注册请求,完成了客户端的注册流程。 之前发送的URL: curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.10&port=8080',根据这个url,我们在服务端代码中找到对应方法

    

 看上图,首先获取到服务的命名空间和服务名,再获取服务实例信息,调用serviceManger完成注册。而默认的命名空间就是

SericeManager 服务存储结构

再让我们深入到sericeManager中

我们可以看到,在manager中有一个Map对象,具体构成如下:

首先是一个 Map(namespace, Map(group::serviceName, Service)) 集合,集合的key为 namespace, value 为一个map,namespace 代表着什么呢,像一般项目中,会有dev、test、master即对应开发、测试、线上环境。这个时候,各个环境的配置都是一套,你就可以建立3个 namespace;而value 中 key代表这个分组下的服务,value就是这个服务的具体信息,在Service内部, 有一个   Map<String, Cluster>, 在 Cluster 内部是一个 Set<Instance>  集合,这个是什么意思呢,举个例子吧: 在nacos的设计中,是为了应付中大型项目的,比如阿里,他们一个服务可能在不同的地方都对应着一个集群,而在每个集群中这个服务的实例都会有多个。所以 Cluster 就是对应集群分片,而 Instance 就是具体的服务实例了。

具体注册逻辑

       再让我们回到注册方法中,在里面做了以下事情

我们进入到创建service的方法中,这里为什么叫创建了一个空的service呢,就是只创建了service对象,对里面的Instance实例并没有进行初始化

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        //首先从缓存中获取
        Service service = getService(namespaceId, serviceName);
        //如果不存在,则创建
        if (service == null) {

            Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
            service = new Service();
            service.setName(serviceName);
            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 不能为空,命名是否符合规范
            service.validate();

            //添加service
            putServiceAndInit(service);
            //持久化
            if (!local) {
                addOrReplaceService(service);
            }
        }
    }

putServiceAndInit() 方法中,做了以下几件事

 private void putServiceAndInit(Service service) throws NacosException {
        //添加service, 向map 中put一个空的service对象
        putService(service);
        //初始化service
        service.init();

        //新增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());
    }

什么是节点变化监听呢, nacos 有一个 RecordListener 类,它提供了几个接口,当插入、新增、删除时分别做什么事,比如我们一般像map中新增数据就是直接调用 Map.put(), nacos就做得比较高级,它是先添加一个对插入对象的监听器,当数据有变化时,再调用对应的事件方法。

public interface RecordListener<T extends Record> {
    
    /**
     * Determine if the listener was registered with this key.
     *
     * @param key candidate key
     * @return true if the listener was registered with this key
     */
    boolean interests(String key);
    
    /**
     * Determine if the listener is to be removed by matching the 'key'.
     *
     * @param key key to match
     * @return true if match success
     */
    boolean matchUnlistenKey(String key);
    
    /**
     * Action to do if data of target key has changed.
     *
     * @param key   target key
     * @param value data of the key
     * @throws Exception exception
     */
    void onChange(String key, T value) throws Exception;
    
    /**
     * Action to do if data of target key has been removed.
     *
     * @param key target key
     * @throws Exception exception
     */
    void onDelete(String key) throws Exception;
}

再让我们回到添加实例的方法中,主要做了 1 更新实例 2 给Service 中 clusterMap 赋值,将实例放入DataStore  。

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {

        //获取到唯一标识key
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

        //获取service
        Service service = getService(namespaceId, serviceName);

        synchronized (service) {
            //获取到更新的实例,主要对实例的健康状态进行维护
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

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

            //给Service 中clusterMap赋值,将实例放入DataStore中
            consistencyService.put(key, instances);
        }
    }

clusterMap 这个我们知道,就是上文中说的 Service内部结构,DataStore  又是什么呢? DataStore nacos 维护的另一份关于 Instance  的集合,具体结构如下,里面有一个 Map 集合, key 就是  String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral) 组成的, value 是 Instances , Instances 中是一个 List<Instance> 集合。

 

其中 Datum 的数据构成: key 是一个 Sring ,value 是一个泛型, 这个泛型继承自 Record类

而它有3个实现类,分别是以下这3个

 

我们再来看下它具体是怎么给 Service 添加实例、将实例加入 DataStore 中的。从前面可以看到,它调用了 consistencyService.put(), 具体方法实现类有以下几个

 

 DelegateConsistencyServiceImpl :  里面实现了 EphemeralConsistencyService 和  PersistentConsistencyServiceDelegateImpl ,即是一个一致性委托类,对临时和永久节点分别做委托

DistroConsistencyServiceImpl :一致性协议算法,使用 distro 算法将数据分为多块,每个 nacos 服务器负责一块,由它来负责做服务器生成、删除和同步的,同一时间内每个Nacos服务器都会接收到其他Nacos服务器的数据同步,所以每个Nacos服务器最终都会接收到有完整的数据。

PersistentConsistencyServiceDelegateImpl: 持久一致性服务委托。

PersistentServiceProcessor: 在集群模式下,持久化处理,启动Raft协议。

RaftConsistencyServiceImpl: 使用简化的Raft协议来维护Nacos集群的一致性状态。

StandalonePersistentServiceProcessor:独立模式下的持久服务操纵层。

但是在nacos 内部,一般都是调用 DistroConsistencyServiceImpl, 对集群模式下临时节点的处理,让我们进去看看

 public void put(String key, Record value) throws NacosException {
        // 添加一个 record
        onPut(key, value);
        //开始同步数据到所有远程服务器
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }
 public void onPut(String key, Record value) {

        //判断key的正确性
        if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
            Datum<Instances> datum = new Datum<>();
            datum.value = (Instances) value;
            datum.key = key;
            datum.timestamp.incrementAndGet();
            //向 dataStore 中添加
            dataStore.put(key, datum);
        }

        if (!listeners.containsKey(key)) {
            return;
        }

        // 根据操作类型创建一个 notifier 任务
        notifier.addTask(key, DataOperation.CHANGE);
    }

再让我们看看 Notifier 类具体做了什么事情

进入 onChange() 方法,最后我看可以看到这个方法

public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
        //获取到当前 clusterMap 中的 Instances
        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();
            //更新instance
            clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
        }

        setLastModifiedMillis(System.currentTimeMillis());
        //发布service改变事件,同步到集群中的其它服务器去
        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());

    }

主要看这两个方法 :   

更新instance : clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral); 
发布service改变事件,同步到集群中的其它服务器去: getPushService().serviceChanged(this);

我们看下它是怎么更新的 Instance 

//判断节点是临时还是永久节点
        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());
                }
            }
        }

        //获取到新增的 Instance
        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);
            }
        }

        //获取到死掉的 Instance
        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);
            }
        }

        //更新 Instances
        toUpdateInstances = new HashSet<>(ips);

        if (ephemeral) {
            ephemeralInstances = toUpdateInstances;
        } else {
            persistentInstances = toUpdateInstances;
        }
    }

再看下它是怎么做同步的

public void serviceChanged(Service service) {
        // merge some change events to reduce the push frequency:
        if (futureMap
                .containsKey(UtilsAndCommons.assembleFullServiceName(service.getNamespaceId(), service.getName()))) {
            return;
        }
        
        this.applicationContext.publishEvent(new ServiceChangeEvent(this, service));
    }

首先发布了一个 ServiceChange 事件, 

public void onApplicationEvent(ServiceChangeEvent event) {
        Service service = event.getService();
        String serviceName = service.getName();
        String namespaceId = service.getNamespaceId();

        //创建一个任务延迟10秒执行
        Future future = GlobalExecutor.scheduleUdpSender(() -> {
            try {
                Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");
                ConcurrentMap<String, PushClient> clients = clientMap
                        .get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
                if (MapUtils.isEmpty(clients)) {
                    return;
                }

                Map<String, Object> cache = new HashMap<>(16);
                long lastRefTime = System.nanoTime();
                //获取到所有的客户端ip
                for (PushClient client : clients.values()) {
                    if (client.zombie()) {
                        Loggers.PUSH.debug("client is zombie: " + client.toString());
                        clients.remove(client.toString());
                        Loggers.PUSH.debug("client is zombie: " + client.toString());
                        continue;
                    }

                    Receiver.AckEntry ackEntry;
                    Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());
                    String key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());
                    byte[] compressData = null;
                    Map<String, Object> data = null;
                    if (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {
                        org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);
                        compressData = (byte[]) (pair.getValue0());
                        data = (Map<String, Object>) pair.getValue1();

                        Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());
                    }

                    if (compressData != null) {
                        ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);
                    } else {
                        ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);
                        if (ackEntry != null) {
                            cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));
                        }
                    }

                    Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",
                            client.getServiceName(), client.getAddrStr(), client.getAgent(),
                            (ackEntry == null ? null : ackEntry.key));

                    //调用udp协议推送数据
                    udpPush(ackEntry);
                }
            } catch (Exception e) {
                Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);

            } finally {
                futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
            }

        }, 1000, TimeUnit.MILLISECONDS);

        futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);

    }

至此,nacos 服务端注册流程就完了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值