【Nacos源码系列】服务注册的原理

news/2024/11/8 0:44:15/

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

文章目录

  • Nacos介绍
  • 服务注册
    • 客户端注册
    • 服务端
  • 总结

Nacos介绍

Nacos是一个基于云原生的动态服务发现、配置管理和服务治理平台,由阿里巴巴开源。它提供了服务注册与发现、配置管理、动态DNS、流量管理、服务降级、负载均衡、限流、路由管理等一系列核心功能,可以帮助企业构建弹性可扩展的微服务架构。

Nacos 架构

本文将从源码的角度介绍一下Nacos服务注册原理。

服务注册

服务注册是指将某个服务的相关信息(如服务名称、IP地址、端口号等)注册到服务注册中心中,以便其他服务或客户端能够发现和访问该服务。

在微服务架构中,由于服务的数量众多,服务之间的调用关系也变得复杂且动态,因此需要一种统一和自动化的方式来管理和发现服务。而服务注册中心就是这样一种机制,它可以帮助服务消费者快速地找到可用的服务提供者,并完成服务调用。

本文将从客户端和服务端的角度介绍Nacos服务注册的原理。

  • 客户端注册主要介绍客户端是怎么注册到Nacos中的。
  • 服务端注册主要介绍接收到客户端注册请求后服务端是怎么处理的。
客户端服务端
版本Spring Cloud Alibaba 2021.0.1.01.4.0

客户端注册

Spring Cloud Alibaba Nacos客户端是基于Springboot自动装配功能实现的,了解Springboot自动装配原理的应该知道首先要查看 spring.factories 文件有哪些自动装配的类。

spring.factories

从名字可以看出NacosServiceRegistryAutoConfiguration类是进行服务注册的,源码如下:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,AutoServiceRegistrationAutoConfiguration.class,NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {@Beanpublic NacosServiceRegistry nacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {return new NacosServiceRegistry(nacosDiscoveryProperties);}@Bean@ConditionalOnBean(AutoServiceRegistrationProperties.class)public NacosRegistration nacosRegistration(ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,NacosDiscoveryProperties nacosDiscoveryProperties,ApplicationContext context) {return new NacosRegistration(registrationCustomizers.getIfAvailable(),nacosDiscoveryProperties, context);}@Bean@ConditionalOnBean(AutoServiceRegistrationProperties.class)public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry,AutoServiceRegistrationProperties autoServiceRegistrationProperties,NacosRegistration registration) {return new NacosAutoServiceRegistration(registry,autoServiceRegistrationProperties, registration);}
}

@ConditionalOnNacosDiscoveryEnabled 注解用于使@EnableDiscoveryClient 注解生效,但其实有没有在启动类使用@EnableDiscoveryClient都能让客户端生效。因为@ConditionalOnProperty 注解的属性matchIfMissing = true,也就是有没有对应的value值,自动配置都会生效。

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled",matchIfMissing = true)
public @interface ConditionalOnNacosDiscoveryEnabled {
}

从源码看到NacosServiceRegistryAutoConfiguration 类会注入以下3个类:

  1. NacosServiceRegistry :实现了Springcloud的ServiceRegistry接口,ServiceRegistry接口是Springcloud提供的一个服务注册的接口,如果想集成Springcloud注册中心必须要实现的接口。NacosServiceRegistry实现了它,并完成nacos服务注册功能。

  2. NacosRegistration :存储nacos服务端信息,它实现了Registration接口,Registration接口中没有任何东西,只是实现了ServiceInstance 接口,ServiceInstance 接口存储一些服务实例的信息。

  3. NacosAutoServiceRegistration:继承了AbstractAutoServiceRegistration抽象类,AbstractAutoServiceRegistration抽象类实现了AutoServiceRegistration , ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> ,通过spring的事件监听机制来完成服务的注册。

NacosAutoServiceRegistration

先来看下NacosAutoServiceRegistration的父类AbstractAutoServiceRegistration抽象类部分源码:

public abstract class AbstractAutoServiceRegistration<R extends Registration>
implements AutoServiceRegistration,ApplicationContextAware,ApplicationListener<WebServerInitializedEvent>{// 具体的服务注册接口private final ServiceRegistry<R> serviceRegistry;// 自动注册配置private AutoServiceRegistrationProperties properties;protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,AutoServiceRegistrationProperties properties) {this.serviceRegistry = serviceRegistry;this.properties = properties;}// 监听接口@Override@SuppressWarnings("deprecation")public void onApplicationEvent(WebServerInitializedEvent event) {bind(event);}// 绑定WebServerInitializedEvent事件@Deprecatedpublic void bind(WebServerInitializedEvent event) {ApplicationContext context = event.getApplicationContext();if (context instanceof ConfigurableWebServerApplicationContext) {if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {return;}}this.port.compareAndSet(0, event.getWebServer().getPort());// 启动this.start();}public void start() {if (!isEnabled()) {if (logger.isDebugEnabled()) {logger.debug("Discovery Lifecycle disabled. Not starting");}return;}// only initialize if nonSecurePort is greater than 0 and it isn't already running// because of containerPortInitializer belowif (!this.running.get()) {//发布实例注册事件this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));//服务注册register();if (shouldRegisterManagement()) {registerManagement();}//发布实例注册配置事件this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));this.running.compareAndSet(false, true);}}//销毁服务@PreDestroypublic void destroy() {stop();}/***  注册服务* Register the local service with the {@link ServiceRegistry}.*/protected void register() {//真正注册服务的地方this.serviceRegistry.register(getRegistration());}
}

从源码可以看到 AbstractAutoServiceRegistration实现了ApplicationListener 监听接口,并监听了WebServerInitializedEvent 事件,WebServerInitializedEvent 事件在容器启动时会进行事件发布。

AbstractAutoServiceRegistration 启动流程是这样的:
容器启动时,发布了WebServerInitializedEvent 事件,AbstractAutoServiceRegistration会调用onApplicationEvent(WebServerInitializedEvent)方法,onApplicationEvent()方法会调用绑定了WebServerInitializedEvent 事件的bind(WebServerInitializedEvent) 方法,同时后续调用start()方法启动注册流程,start()方法会调用ServiceRegistry#register() 开始进行注册。

AbstractAutoServiceRegistration注册流程

NacosServiceRegistry实现了ServiceRegistry接口,所以 注册从NacosServiceRegistry#register()方法开始的。

@Override
public void register(Registration registration) {if (StringUtils.isEmpty(registration.getServiceId())) {log.warn("No service to register for nacos client...");return;}// 通过配置文件获取服务相关的信息NamingService namingService = namingService();String serviceId = registration.getServiceId();String group = nacosDiscoveryProperties.getGroup();// 通过配置文件获取实例相关的信息Instance instance = getNacosInstanceFromRegistration(registration);try {//服务注册namingService.registerInstance(serviceId, group, instance);log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,instance.getIp(), instance.getPort());}catch (Exception e) {if (nacosDiscoveryProperties.isFailFast()) {log.error("nacos registry, {} register failed...{},", serviceId,registration.toString(), e);rethrowRuntimeException(e);}else {log.warn("Failfast is false. {} register failed...{},", serviceId,registration.toString(), e);}}
}

NacosServiceRegistry#register()方法中,会先通过客户端的配置文件创建一个 NamingService ,然后把封装过服务实例的基本信息的 Registration 对象(Registration 接口只是空继承了 ServiceInstance 接口,类中没有任何方法)生成一个 Instance 对象,最后通过 NamingService#registerInstance() 将服务实例注册到Nacos注册中心去。

追溯源码可以发现 NamingService 是通过**NamingFactory#createNamingService(Properties)**方法 创建的:

public static NamingService createNamingService(Properties properties) throws NacosException {try {Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");Constructor constructor = driverImplClass.getConstructor(Properties.class);NamingService vendorImpl = (NamingService) constructor.newInstance(properties);return vendorImpl;} catch (Throwable e) {throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);}
}

createNamingService 方法通过反射调用**NacosNamingService(Properties)**构造方法创建对象,

public NacosNamingService(Properties properties) throws NacosException {init(properties);
}//初始化操作
private void init(Properties properties) throws NacosException {//验证properties参数是否正确ValidatorUtils.checkInitParam(properties);//初始化namespacethis.namespace = InitUtils.initNamespaceForNaming(properties);InitUtils.initSerialization();//初始化server 地址initServerAddr(properties);InitUtils.initWebRootContext();//初始化缓存目录initCacheDir();//初始化日志名initLogName(properties);//创建事件转发器this.eventDispatcher = new EventDispatcher();this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);//初始化心跳反应this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}

再看一下 NacosNamingService#registerInstance() 方法:

@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);// 临时节点if (instance.isEphemeral()) {//封装心跳信息BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);//加入定时任务beatReactor.addBeatInfo(groupedServiceName, beatInfo);}//实际进行代理注册服务serverProxy.registerService(groupedServiceName, groupName, instance);
}//添加定时任务
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);// 心跳key,形如:  DEFAULT_GROUP@@provider#192.168.71.70#9093String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());BeatInfo existBeat = null;//fix #1733if ((existBeat = dom2Beat.remove(key)) != null) {existBeat.setStopped(true);}dom2Beat.put(key, beatInfo);//心跳定时任务 默认5s一次executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

NacosNamingService#registerInstance() 方法会对临时节点一个心跳封装,创建一个 BeatTask 心跳任务以保证客户端的健康,默认5s执行一次,最后通过NamingProxy#registerService执行服务注册。

进行服务注册的是
com.alibaba.nacos.client.naming.net.NamingProxy#registerService方法,且看源码:

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);
}

NamingProxy#registerService方法会将服务实例的信息放到Map 集合中,然后传递给reqApi方法。
reqApi方法调用callServer方法,callServer方法最终会把这些包含客户端信息的参数封装起来并生成一个url,通过Nacos提供的openAPI形式调用服务端接口完成注册。

请求参数

总结一下客户端注册流程,客户端启动时,会向Nacos注册中心发送注册请求。注册请求中包含了服务名、IP地址、端口号和其他元数据信息。客户端将自己的信息注册到Nacos中心,这样服务消费者才能够发现它。
在Nacos中,服务提供者注册的信息被称为服务实例(Instance),一个服务可以有多个实例,每个实例都有一个唯一的ID来标识自己。

服务端

客户端注册完成之后,服务端在接到注册请求之后会做什么呢?

通过actuator找到到客户端请求注册之后服务端的controller:

InstanceController

InstanceController类用于处理服务注册和发现的HTTP请求,包括注册、注销和查询服务实例列表等操作。

@Autowired
private ServiceManager serviceManager;@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);//检查服务格式是否正确checkServiceNameFormat(serviceName);//将请求中的参数转化为实例信息final Instance instance = parseInstance(request);//服务端注册实例信息serviceManager.registerInstance(namespaceId, serviceName, instance);return "ok";
}

InstanceController#register方法将客户端传过来的参数进行解析并进行一系列操作之后,通过ServiceManager#registerInstance方法进行实例注册。

ServiceManager 类提供了一些注册和管理服务的方法。来看下ServiceManager#registerInstance方法将一个服务实例注册到Nacos注册中心中,且看源码:

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {// 创建一个服务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);
}

createEmptyService 方法会初始化一个服务,使服务能注册到nacos注册中心。

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {createServiceIfAbsent(namespaceId, serviceName, local, null);
}/*** Create service if not exist.** @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 {//根据namespace和服务名称获取服务,获取不到新建一个服务。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 thrownservice.setLastModifiedMillis(System.currentTimeMillis());service.recalculateChecksum();if (cluster != null) {cluster.setService(service);service.getClusterMap().put(cluster.getName(), cluster);}//验证服务名是否正确service.validate();//将服务放入serviceMap本地缓存中并进行初始化putServiceAndInit(service);if (!local) {addOrReplaceService(service);}}
}

Service对象

通过 createServiceIfAbsent 会进行创建服务,并在最后将创建好的服务放到本地缓存中。

/**
* Map(namespace, Map(group::serviceName, Service)).
* 同一个namespace下放到一起,同一个组的服务
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();private void putServiceAndInit(Service service) throws NacosException {// 放到本地缓存中putService(service);//初始化服务,对客户端进行心跳检查和服务集群进行初始化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());
}/**
* Put service into manager.
*
* @param service service
*/
public void putService(Service service) {if (!serviceMap.containsKey(service.getNamespaceId())) {// 同步,确保只有一个线程进行操作synchronized (putServiceLock) {if (!serviceMap.containsKey(service.getNamespaceId())) {serviceMap.put(service.getNamespaceId(), new ConcurrentHashMap<>(16));}}}serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
}

可以看到最后nacos服务端将服务缓存到private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();中,nacos通过不同的 namespace 来管理服务,在同一个 namespace 下又使用不同的group来管理服务。

至此,服务端接收到客户端的简单流程就结束了,至于服务端怎么更新缓存、服务集群怎么更新实例、还有诸如心跳健康检查之类的后续再介绍。

总结一下服务端注册流程,Nacos注册中心接收到服务提供者的注册请求后,将服务实例的信息保存到本地缓存或持久化存储中。同时,Nacos还会根据服务名和实例的标识来生成一个全局唯一的服务实例ID。

总结

最后总结一下服务注册的流程:

  1. 服务启动时,根据spring.factories文件自动注入 spring-cloud-common包下的 AutoServiceRegistrationConfiguration 类,同时注入 Nacos的 NacosServiceRegistryNacosRegistration类。
  2. AutoServiceRegistrationConfiguration 类通过监听 WebServerInitializedEvent 容器启动事件,调用start()方法开始注册流程。
  3. start()方法会调用 ServiceRegistry 接口的实现类 NacosServiceRegistryregister() 进行注册。
  4. NacosServiceRegistry#register()方法中会创建一个 NamingServiceInstance 对象,然后通过NacosNamingService#registerInstance()方法将Instance 对象注册到注册中心。
  5. NacosNamingService#registerInstance()方法会给客户端
    开启一个心跳定时任务以保证服务端能感知客户端的存活,同时通过 NamingProxy#registerService将客户端服务名、IP地址、端口号和其他元数据信息以openAPI的形式请求给服务端,完成客户端的服务注册。
  6. 服务端在InstanceController#register方法中处理客户端的注册请求。
  7. 接收到客户端请求后ServiceManager#registerInstance方法会将客户端注册进来,并将客户端缓存到一个名为 serviceMap 数据结构为 ConcurrentHashMap 的本地缓存中。

http://www.ppmy.cn/news/479211.html

相关文章

努比亚NX549 miniS 刷机遇坑记录,最终完美解决方案

由于想实现大量添加各地微信好友的需求&#xff0c;我们需要用到一款APP名字叫做“天下游”&#xff0c;这个专注于虚拟定位&#xff0c;亲测后发现真心强大好用&#xff01;&#xff01;&#xff01;但是必须要手机先获取ROOT权限&#xff0c;于是就有了下面的遇坑情况&#x…

安卓2.2刷机包_老用户福音 努比亚为红魔电竞手机和Z17用户更新安卓9.0

标签&#xff1a;安卓9.0,安卓9.0刷机包,安卓9.0刷机包官方下载 【rom之家资讯】努比亚手机作为与诸多青年人一同成长起来的互联网手机品牌&#xff0c;拥有非常深厚的用户基础&#xff0c;许多老用户也选择一直使用努比亚手机。现在努比亚为用户带来了Z17的系统更新消息和红魔…

nubia,无IMEI码(串号丢失)解救【转】

写在开头&#xff1a;怀着无比激动的心情迎来了Z5Sn的第一次升级1.30&#xff0c;乐极生悲的是升级后手机的IMEI码为空&#xff08;咱手上的是工程机&#xff0c;升级有此风险&#xff0c;也是后来联系了官方技术员才知道&#xff09;。这样的手机完全没有信号&#xff0c;根本…

NS2教程

柯老师的NS2新网址 Due to some reasons, my NS2 website is sometimes donw and unavailable for many users. Therefore, I provide another backup website. 1. NS2 http://csie.nqu.edu.tw/smallko/ns2/ns2.htm 2. old_NS2 (backup of NS2 Learning Guide) http://csie.n…

努比亚Z11miniRoot及真机调试常见问题

一、努比亚Z11 mini ROOT 努比亚Z11官网论坛root教程 二、Android真机连接Eclipse时&#xff0c;打不开File Explorer下的data文件夹解决方法 当用真机开发Android时&#xff0c;连接了Eclipse后&#xff0c;默认在File Explorer下是达不开我们手机的data文件夹的&#xff0c;这…

数字非洲,沐光而行

“华为是什么公司&#xff1f;我们不相信中国企业能有先进的通信技术&#xff01;你们也不要总是来找我&#xff01;”1998年&#xff0c;华为人刚踏上非洲所遇到的&#xff0c;不是来自阳光大陆的热情&#xff0c;而是来自刚果&#xff08;金&#xff09;客户冷冰冰的拒绝。 2…

Spring MVC各种参数进行封装

目录 一、简单数据类型 1.1 控制器方法 1.2 测试结果 二、对象类型 2.1 单个对象 2.1.1 控制器方法 2.1.2 测试结果 2.2 关联对象 2.2.1 控制器方法 2.2.2 测试结果 三、集合类型 3.1 简单数据类型集合 3.1.1 控制方法 3.1.2 测试结果 3.2 对象数据类型集合 3.…