Nacos源码分析系列 - 服务注册

Nacos源码分析系列 - 服务注册

Author:zxw

email:502513206@qq.com

@ Jishou University

@Nacos:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html


1.前言

nacos官方有集成好的nacos-server包,copy下来只需要运行其中的startup.bat文件即可启动注册中心,但是这边我们从源码的角度入手注册中心,所以将源码包拷贝下来,通过手动启动的方式启动nacos注册中心。首先获取到源码后,在本地导入相关的包然后找到consol

e工程,启动Nacos类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zNBdKRIQ-1598514282237)(nacos源码分析系列 - 注册中心.assets/image-20200824222148890.png)]

2.注册中心

可以看到注册中心其实就是一个Springboot的项目

@SpringBootApplication(scanBasePackages = "com.alibaba.nacos")
@ServletComponentScan
@EnableScheduling
public class Nacos {
    
    public static void main(String[] args) {
        SpringApplication.run(Nacos.class, args);
    }
}

3.启动类

这边我使用的是spring-cloud版本的nacos客户端,导入了spring-cloud-starter-alibaba-nacos-discovery在该工程中主要注入了下面这些类,根据之前分析cloud-eureka源码的经验,基本逻辑就在AutoConfiguration类中,让我们对每个类一个一个解析

// EnableAutoConfiguration
com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,
com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,
com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,
com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,
com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,
com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
// BootstrapConfiguration
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration

注入的对象很多,但是大部分都会汇聚到一个对象里进行生命周期的保管,所以我主要挑几个有用的对象来讲

NacosDiscoveryAutoConfiguration

   @Bean
   @ConditionalOnMissingBean
   public NacosDiscoveryProperties nacosProperties() {
      // 配置文件类,spring.cloud.nacos.discovery
      return new NacosDiscoveryProperties();
   }
}

NacosDiscoveryClientConfiguration

  @Bean
	public DiscoveryClient nacosDiscoveryClient(
			NacosServiceDiscovery nacosServiceDiscovery) {
        // 这个就相当于我们当前的实例的客户端了,会持有上面那个配置类的信息
		return new NacosDiscoveryClient(nacosServiceDiscovery);
	}

 @Bean
	@ConditionalOnMissingBean
	@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled",
			matchIfMissing = true)
	public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties,
			ObjectProvider<TaskScheduler> taskScheduler) {
        // 该类会在启动后发布一次心跳
		return new NacosWatch(nacosDiscoveryProperties, taskScheduler);
	}


NacosServiceRegistryAutoConfiguration

@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosDiscoveryProperties nacosDiscoveryProperties) {
        // 根据类名可以看到,跟服务注册相关了
		return new NacosServiceRegistry(nacosDiscoveryProperties);
	}

	// 继续往下
	public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
		this.nacosDiscoveryProperties = nacosDiscoveryProperties;
		this.namingService = nacosDiscoveryProperties.namingServiceInstance();
	}
public NamingService namingServiceInstance() {

		if (null != namingService) {
			return namingService;
		}
		// 通过工厂创建namingService对象
		try {
			namingService = NacosFactory.createNamingService(getNacosProperties());
		}
		catch (Exception e) {
			log.error("create naming service error!properties={},e=,", this, e);
			return null;
		}
		return namingService;
	}

NacosNamingService跟服务注册、心跳等有关的所有代码都在该类中,在该类构造方法中会调用init方法

    private void init(Properties properties) {
        // 获取namespace
        namespace = InitUtils.initNamespaceForNaming(properties);
        // 初始化注册中心地址
        initServerAddr(properties);
        
        InitUtils.initWebRootContext();
        // 初始化缓存目录地址,默认在/nacos/naming/public 目录下
        initCacheDir();
        // 初始化日志名称,默认为naming.log
        initLogName(properties);
		// 后台会启动一个线程用来创建namingEvent
        eventDispatcher = new EventDispatcher();
       	// 以下几个比较重要,在后面具体讲
        serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);
        beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
        hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),
            initPollingThreadCount(properties));
    }

InitUtils.initNamespaceForNaming()我们知道nacos的数据模型是由三元组确定,分别为Namespace、Group、dataid。下面方法用于获取Namespace,默认返回public

public static String initNamespaceForNaming(Properties properties) {
        String tmpNamespace = null;


        String isUseCloudNamespaceParsing =
            properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
                System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
                    String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));

        if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {

            tmpNamespace = TenantUtil.getUserTenantForAns();
            tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
                @Override
                public String call() {
                    String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
                    LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
                    return namespace;
                }
            });

            tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
                @Override
                public String call() {
                    String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
                    LogUtils.NAMING_LOGGER.info("initializer namespace from System Environment :" + namespace);
                    return namespace;
                }
            });
        }

        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
            @Override
            public String call() {
                String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
                LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
                return namespace;
            }
        });

        if (StringUtils.isEmpty(tmpNamespace) && properties != null) {
            tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
        }
		// 默认返回"public"
        tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
            @Override
            public String call() {
                return UtilAndComs.DEFAULT_NAMESPACE_ID;
            }
        });
        return tmpNamespace;
    }

namingProxy在该对象中,启动了两个定时线程,一个是用来更新权限验证时间,一个用来更新服务注册时间

 private void initRefreshTask() {
       
        this.executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.naming.updater");
                t.setDaemon(true);
                return t;
            }
        });
         // 30秒
        this.executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                refreshSrvIfNeed();
            }
        }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
        // 5秒
        this.executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                securityProxy.login(getServerList());
            }
        }, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
        
        refreshSrvIfNeed();
        this.securityProxy.login(getServerList());
    }

NacosAutoServiceRegistration在springcloud中,服务启动代码都是通过AutoServiceRegistration的实现类来操作

@Override
	protected void register() {
		if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
			log.debug("Registration disabled.");
			return;
		}
		if (this.registration.getPort() < 0) {
			this.registration.setPort(getPort().get());
		}
        // 调用NacosServiceRegistry的register()方法
		super.register();
	}

NacosServiceRegistry

@Override
	public void register(Registration registration) {

		if (StringUtils.isEmpty(registration.getServiceId())) {
			log.warn("No service to register for nacos client...");
			return;
		}

		String serviceId = registration.getServiceId();
		String group = nacosDiscoveryProperties.getGroup();

		Instance instance = getNacosInstanceFromRegistration(registration);

		try {
            // 注册我们的client
			namingService.registerInstance(serviceId, group, instance);
			log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
					instance.getIp(), instance.getPort());
		}
		catch (Exception e) {
			log.error("nacos registry, {} register failed...{},", serviceId,
					registration.toString(), e);
			// rethrow a RuntimeException if the registration is failed.
			// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
			rethrowRuntimeException(e);
		}
	}

接下来看看该类的服务注册代码。第一次创建时会通过groupname + servicename创建一个心跳实例并启动心跳检测定时任务

registerInstance

 public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
     // groupname 和 servicename 组合,DEFAULT_GROUP@@serviceName
     // 默认groupname为DEFAULT_GROUP
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
     // 默认为true
        if (instance.isEphemeral()) {
            // 构建心跳实例
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            // 启动心跳定时任务
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
     // 服务注册
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

registerService在该模块中组装实例参数,然后发送到/nacos/v1/ns/instance接口

 public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
                instance);
        // 组装实例信息
        final Map<String, String> params = new HashMap<String, String>(16);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put(CommonParams.GROUP_NAME, groupName);
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put("healthy", String.valueOf(instance.isHealthy()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
        // 发送请求
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
        
    }

reqApi()首先会发送一次注册请求,如果请求失败则进入重试注册循环中,有3次重试次数,如果依然注册失败则会抛出异常。有关注册是使用的HttpClient对象

 public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
            String method) throws NacosException {
        
        params.put(CommonParams.NAMESPACE_ID, getNamespaceId());
        // 判断服务地址
        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
            throw new NacosException(NacosException.INVALID_PARAM, "no server available");
        }
        
        NacosException exception = new NacosException();
        
        if (servers != null && !servers.isEmpty()) {
            // 随机获取一个注册中心地址
            Random random = new Random(System.currentTimeMillis());
            int index = random.nextInt(servers.size());
            
            for (int i = 0; i < servers.size(); i++) {
                String server = servers.get(index);
                try {
                    // 发起HttpClient请求
                    return callServer(api, params, body, server, method);
                } catch (NacosException e) {
                    exception = e;
                    if (NAMING_LOGGER.isDebugEnabled()) {
                        NAMING_LOGGER.debug("request {} failed.", server, e);
                    }
                }
                index = (index + 1) % servers.size();
            }
        }
        // 重试注册
        if (StringUtils.isNotBlank(nacosDomain)) {
            for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
                try {
                    return callServer(api, params, body, nacosDomain, method);
                } catch (NacosException e) {
                    exception = e;
                    if (NAMING_LOGGER.isDebugEnabled()) {
                        NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);
                    }
                }
            }
        }
        // 注册失败,抛出异常
        NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
                exception.getErrMsg());
        
        throw new NacosException(exception.getErrCode(),
                "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
        
    }

接下来看看注册中心端的,在InstanceController.register()

 @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
        // 获取servucename
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        // 获取namespace
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        // 构建instance实例
        final Instance instance = parseInstance(request);
        // 注册到nacos的生命周期中
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }

ServiceManager,通过该类管理我们的service生命周期活动

 public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        // 将当前服务加入service
        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);
    }

createServiceIfAbsent在执行该方法的时候首先从Map获取service实例,如果没有则在当前namespace下创建当前服务实例

 public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
     // 从当前namespaceId的map中取出该servicename对应的Service,如果不存在就创建
        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);
            }
        }
    }

自此nacos的服务注册流程就结束了,nacos中首先使用一个Map存储namespace - Map的关系,在Map中又以Key和实例信息对应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值