前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
文章目录
- Nacos介绍
- 服务注册
- 客户端注册
- 服务端
- 总结
Nacos介绍
Nacos是一个基于云原生的动态服务发现、配置管理和服务治理平台,由阿里巴巴开源。它提供了服务注册与发现、配置管理、动态DNS、流量管理、服务降级、负载均衡、限流、路由管理等一系列核心功能,可以帮助企业构建弹性可扩展的微服务架构。
本文将从源码的角度介绍一下Nacos服务注册原理。
服务注册
服务注册是指将某个服务的相关信息(如服务名称、IP地址、端口号等)注册到服务注册中心中,以便其他服务或客户端能够发现和访问该服务。
在微服务架构中,由于服务的数量众多,服务之间的调用关系也变得复杂且动态,因此需要一种统一和自动化的方式来管理和发现服务。而服务注册中心就是这样一种机制,它可以帮助服务消费者快速地找到可用的服务提供者,并完成服务调用。
本文将从客户端和服务端的角度介绍Nacos服务注册的原理。
- 客户端注册主要介绍客户端是怎么注册到Nacos中的。
- 服务端注册主要介绍接收到客户端注册请求后服务端是怎么处理的。
客户端 | 服务端 | |
---|---|---|
版本 | Spring Cloud Alibaba 2021.0.1.0 | 1.4.0 |
客户端注册
Spring Cloud Alibaba Nacos客户端是基于Springboot自动装配功能实现的,了解Springboot自动装配原理的应该知道首先要查看 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个类:
-
NacosServiceRegistry :实现了Springcloud的
ServiceRegistry
接口,ServiceRegistry
接口是Springcloud提供的一个服务注册的接口,如果想集成Springcloud注册中心必须要实现的接口。NacosServiceRegistry实现了它,并完成nacos服务注册功能。 -
NacosRegistration :存储nacos服务端信息,它实现了Registration接口,Registration接口中没有任何东西,只是实现了ServiceInstance 接口,ServiceInstance 接口存储一些服务实例的信息。
-
NacosAutoServiceRegistration:继承了
AbstractAutoServiceRegistration
抽象类,AbstractAutoServiceRegistration
抽象类实现了AutoServiceRegistration
,ApplicationContextAware
,ApplicationListener<WebServerInitializedEvent>
,通过spring的事件监听机制来完成服务的注册。
先来看下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()
开始进行注册。
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
类用于处理服务注册和发现的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);}}
}
通过 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。
总结
最后总结一下服务注册的流程:
- 服务启动时,根据
spring.factories
文件自动注入spring-cloud-common
包下的 AutoServiceRegistrationConfiguration 类,同时注入 Nacos的 NacosServiceRegistry 和 NacosRegistration类。 - AutoServiceRegistrationConfiguration 类通过监听 WebServerInitializedEvent 容器启动事件,调用
start()
方法开始注册流程。 start()
方法会调用 ServiceRegistry 接口的实现类 NacosServiceRegistry 的register()
进行注册。NacosServiceRegistry#register()
方法中会创建一个 NamingService 和 Instance 对象,然后通过NacosNamingService#registerInstance()
方法将Instance 对象注册到注册中心。NacosNamingService#registerInstance()
方法会给客户端
开启一个心跳定时任务以保证服务端能感知客户端的存活,同时通过NamingProxy#registerService
将客户端服务名、IP地址、端口号和其他元数据信息以openAPI的形式请求给服务端,完成客户端的服务注册。- 服务端在
InstanceController#register
方法中处理客户端的注册请求。 - 接收到客户端请求后
ServiceManager#registerInstance
方法会将客户端注册进来,并将客户端缓存到一个名为 serviceMap 数据结构为ConcurrentHashMap
的本地缓存中。