Nacos服务端源码分析——服务注册

Nacos 服务端注册

一、代码位置

二、服务端源码注册

2.1 InstanceController#register

 /**
     * 注册实例
     *
     * @param request http request
     * @return 'ok' if success
     * @throws Exception any error during register
     */
    @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
        //从request中获取客户端传递的namespaceId
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        //从request中获取客户端传递的serviceName
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        //检查服务名是否合法
        NamingUtils.checkServiceNameFormat(serviceName);
        //从request中获取客户端传递的参数构造instance对象
        final Instance instance = parseInstance(request);
        // 注册实例
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }

2.2 ServiceManager#registerInstance

 /**
     * 注册实例通过AP
     *
     * <p>This method creates service or cluster silently if they don't exist.
     *
     * @param namespaceId id of namespace
     * @param serviceName service name
     * @param instance    instance to register
     * @throws Exception any error occurred in the process
     */
    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        //1.创建一个空的服务
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        // 2.从缓存中获取服务
        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);
    }

2.3 ServiceManager#createEmptyService

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
        createServiceIfAbsent(namespaceId, serviceName, local, null);
    }
    
    /**
     * 如果服务不存在则创建
     *
     * @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);
        // 第一次注册service 为空
        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.validate();

            //添加服务并且初始化服务
            putServiceAndInit(service);
            if (!local) {
                addOrReplaceService(service);
            }
        }
    }
    

2.4 ServiceManager#putServiceAndInit

private void putServiceAndInit(Service service) throws NacosException {
        //添加服务,就是往serviceMap中设置服务
        putService(service);
        //从缓存中获取服务
        service = getService(service.getNamespaceId(), service.getName());
        // 服务初始化
        service.init()
        // 添加服务监听器
        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());
    }

2.5 Service#init()

  /**
     * 初始化服务
     */
    public void init() {
        //添加一个心跳检查的定时任务
        HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
        // 遍历service内部的clusterMap并初始化集群,由于我们是第一次注册实例,所以此时clusterMap还是空,暂不分析
        for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
            entry.getValue().setService(this);
            entry.getValue().init();
        }
    }

2.6 ServiceManage#addInstance

/**
     * Add instance to service.
     *
     * @param namespaceId namespace
     * @param serviceName service name
     * @param ephemeral   whether instance is ephemeral
     * @param ips         instances
     * @throws NacosException nacos exception
     */
    public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        //根据namespaceId、serviceName以及是否是临时实例,创建一个key,由于此时我们注册的是临时实例,所以key为com.alibaba.nacos.naming.iplist.ephemeral.{namespaceId}.##.{serviceName}
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        //从缓存中获取上一步中创建的空service
        Service service = getService(namespaceId, serviceName);
        // 更新实例并返回该服务下的所有实例列表
        synchronized (service) {
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
            //创建一个instances对象,用于传递实例列表
            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            //触发一个服务变更的通知以及向集群中的其他nacos节点同步实例数据
            consistencyService.put(key, instances);
        }
    }

2.7ServiceManager#updateIpAddresses

   /**
     * 就是先获取旧的实例列表,然后把新的实例信息与旧的做对比,新的实例就添加,老的实例同步ID。然后返回最新的实例列表
     *
     * @param service   service
     * @param action    {@link UtilsAndCommons#UPDATE_INSTANCE_ACTION_REMOVE} or {@link UtilsAndCommons#UPDATE_INSTANCE_ACTION_ADD}
     * @param ephemeral whether instance is ephemeral
     * @param ips       instances
     * @return instance list after operation
     * @throws NacosException nacos exception
     */
    public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)
            throws NacosException {
        // 根据唯一key获取Datum对象
        Datum datum = consistencyService
                .get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));
        // 由于ephemeral等于true, 所以从service中获取的是所有的临时实例,又由于是第一次注册,currentIPs列表等于空
        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,用来保存更新后的实例列表
        Map<String, Instance> instanceMap;
        if (datum != null && null != datum.value) {
            // 这里面的代码就是遍历Datum对象中存放的实例列表,判断当前的实例列表中是否存在该实例,若存在则将该实例的健康状态与最后一次心跳检测时间的值赋值成当前实例对应的值,将实例放入Map中返回
            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);
                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());
            }
            
            if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
                // 实例移除事件,将实例去除
                instanceMap.remove(instance.getDatumKey());
            } else {

                // 放入新注册的实例
                Instance oldInstance = instanceMap.get(instance.getDatumKey());
                if (oldInstance != null) {
                    instance.setInstanceId(oldInstance.getInstanceId());
                } else {
                    instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));
                }
                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 CopyOnWriteArrayList<>(instanceMap.values());
    }

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

由于Nacos默认都是临时实例,临时实例走的是Distro协议,因此consistencyService.put方法真正的实现类为DistroConsistencyServiceImpl

   @Override
    public void put(String key, Record value) throws NacosException {
        // 将新注册的实例更新到服务注册表中
        onPut(key, value);
        // 同步最新的实例信息到其他服务节点
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }

2.9 com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#onPut

/**
     * Put a new record.
     *
     * @param key   key of record
     * @param value record
     */
    public void onPut(String key, Record value) {
        //由于当前是临时实例,所以匹配
        if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
            //实例化datum
            Datum<Instances> datum = new Datum<>();
            datum.value = (Instances) value;
            datum.key = key;
            datum.timestamp.incrementAndGet();
            dataStore.put(key, datum);
        }
        
        if (!listeners.containsKey(key)) {
            return;
        }
        // 这里面的代码逻辑就是将一个变更事件放入到阻塞队列中。该方法在Notifier类中,Notifier类实现了Runnable,具体的实现逻辑在run方法中,下面会具体介绍
        // 这里采用队列进行异步进行服务注册表的更新
        notifier.addTask(key, DataOperation.CHANGE);
    }
    

2.10 com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#addTask

private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
        
        private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
 /**
         * 添加通知到队列中
         *
         * @param datumKey data key
         * @param action   action for data
         */
        public void addTask(String datumKey, DataOperation action) {
            //如果Notifier内部的services中已经包含datumKey并且action等于DataOperation.CHANGE,什么也不做,说明已经添加过了
            if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
                return;
            }
            //如果action == DataOperation.CHANGE
            if (action == DataOperation.CHANGE) {
                //向services中添加数据
                services.put(datumKey, StringUtils.EMPTY);
            }
            tasks.offer(Pair.with(datumKey, action));
        }

2.11 com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#run

  @Override
        public void run() {
            Loggers.DISTRO.info("distro notifier started");
            
            for (; ; ) {
                try {
                    Pair<String, DataOperation> pair = tasks.take();
                    handle(pair);
                } catch (Throwable e) {
                    Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
                }
            }
        }
        // 处理Pair
        private void handle(Pair<String, DataOperation> pair) {
            try {
                String datumKey = pair.getValue0();
                DataOperation action = pair.getValue1();
                
                services.remove(datumKey);
                
                int count = 0;

                ConcurrentLinkedQueue<RecordListener> recordListeners = listeners.get(datumKey);
                if (recordListeners == null) {
                    Loggers.DISTRO.info("[DISTRO-WARN] RecordListener not found, key: {}", datumKey);
                    return;
                }
                
                for (RecordListener listener : recordListeners) {
                    
                    count++;
                    
                    try {
                        if (action == DataOperation.CHANGE) {
                            Datum datum = dataStore.get(datumKey);
                            if (datum != null) {
                                //调用listener的onChange方法
                                listener.onChange(datumKey, datum.value);
                            } else {
                                Loggers.DISTRO.info("[DISTRO-WARN] data not found, key: {}", datumKey);
                            }
                            continue;
                        }
                        
                        if (action == DataOperation.DELETE) {
                            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);
            }
        }
    }

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

@Override
    public void onChange(String key, Instances value) throws Exception {
        
        Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);
        //从Instances实例中获取实例列表并遍历
        for (Instance instance : value.getInstanceList()) {
            
            if (instance == null) {
                // Reject this abnormal instance list:
                throw new RuntimeException("got null instance " + key);
            }
            
            if (instance.getWeight() > 10000.0D) {
                instance.setWeight(10000.0D);
            }
            
            if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) {
                instance.setWeight(0.01D);
            }
        }
        
        updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));
        
        recalculateChecksum();
    }

2.13 com.alibaba.nacos.naming.core.Service#updateIPs

    /**
     * 更新实例
     */
    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()) {
            
            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());
        
    }

2.14 com.alibaba.nacos.naming.push.PushService#serviceChanged

/**
     * Service changed.
     *
     * @param service service
     */
    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));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值