当前位置:网站首页>【七夕快乐篇】Nacos是如何实现服务注册功能的?
【七夕快乐篇】Nacos是如何实现服务注册功能的?
2022-08-04 23:08:00 【步尔斯特】
今天是一个美好的日子,祝大家七夕快乐。
很多订阅 《微服务核心技术》 专栏的读者在后台私信说:看Nacos源码时没有思路,面试中还总被问到一些细节。
那么接下的几天里,我们就来逐步分析一下Nacos的源码以及Nacos的核心功能与机制,并着手写一个注册中心,来帮助大家更好的了解分布式中间件。
大家都知道Nacos有两大模块:注册中心和配置中心。
那么Nacos是如何实现注册中心的服务注册的功能呢?我们来一探究竟。
在SpringBoot的基底下,每当我们引入一个新的适配组件,理应看一下该组件下的/META-INF/spring.factories文件,上一篇文章《注解@EnableAutoConfiguration的作用以及如何使用》提到,@SpringBootApplication会自动加载/META-INF/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 {
...
...
...
// 服务注册核心bean
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}
}
跟进NacosAutoServiceRegistration
public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
super(serviceRegistry, autoServiceRegistrationProperties);
this.registration = registration;
}
跟进super【AbstractAutoServiceRegistration】
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
AutoServiceRegistrationProperties properties) {
this.serviceRegistry = serviceRegistry;
this.properties = properties;
}
在这个类下,有个监听的方法,这个就是服务注册的核心方法了。
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
// 服务注册的核心方法
bind(event);
}
那么有监听的事件,就应该有发布的事件,那么事件是在哪里发布的呢?
事件是在WebServerStartStopLifecycle#start时发布的
spring容器启动过程中核心方法:finishRefresh(),让我们看一看这个方法,算了,我还是专门写一篇吧…移步《Spring源码之finishRefresh()》
SpringApplication#run启动过程中核心方法:
finishRefresh()getLifecycleProcessor().onRefresh();
WebServerStartStopLifecycle(实现SmartLifecycle接口)会发布 ServletWebServerInitializedEvent事件。
NacosAutoServiceRegistration的onApplicationEvent方法处理WebServerInitializedEvent事件。
@Override
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext
.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}
知道了它是如何发布的,我们来看一下AbstractAutoServiceRegistration#bind
@Deprecated
public 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();
}
跟进this.start()

跟进register(),几经辗转,我们来到NacosServiceRegistry#register

跟进namingService.registerInstance(serviceId, group, instance);,我们来到NacosNamingService#registerInstance,我们重点关注一下

跟进NamingProxy#registerService,组装客户端信息向服务端发送请求。
由此可见,服务注册的请求是POST,请求路径是/nacos/v1/ns/instance
public static String webContext = "/nacos";
public static String nacosUrlBase = webContext + "/v1/ns";
public static String nacosUrlInstance = nacosUrlBase + "/instance";
根据上述结论,我们可以找到服务端对应的API接口:InstanceController#register
@CanDistro
@PostMapping
@Secured(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);
NamingUtils.checkServiceNameFormat(serviceName);
final Instance instance = HttpRequestInstanceBuilder.newBuilder()
.setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
// 跟进
getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "",
false, namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName),
instance.getIp(), instance.getPort()));
return "ok";
}
跟进getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
InstanceOperatorServiceImpl#registerInstance
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
com.alibaba.nacos.naming.core.Instance coreInstance = parseInstance(instance);
// 跟进
serviceManager.registerInstance(namespaceId, serviceName, coreInstance);
}
跟进serviceManager.registerInstance(namespaceId, serviceName, coreInstance);
ServiceManager#registerInstance
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
// 跟进
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
Service service = getService(namespaceId, serviceName);
checkServiceIsNull(service, namespaceId, serviceName);
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
跟进createEmptyService(namespaceId, serviceName, instance.isEphemeral());
最后我们来到ServiceManager#createServiceIfAbsent
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
throws NacosException {
Service service = getService(namespaceId, serviceName);
//return if service already exists
if (service != null) {
return;
}
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);
}
}
跟进putServiceAndInit(service);
private void putServiceAndInit(Service service) throws NacosException {
// 跟进
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());
}
跟进putService(service);
public void putService(Service service) {
if (!serviceMap.containsKey(service.getNamespaceId())) {
// 将客户端信息存入服务端内存中
serviceMap.putIfAbsent(service.getNamespaceId(), new ConcurrentSkipListMap<>());
}
serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
}
再看一眼这个serviceMap的定义
/** * Map(namespace, Map(group::serviceName, Service)). */
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
来吧,总结一下吧,大体分为这么几步:
- 在
Spring启动时,先发布Nacos服务注册的事件 - 实例化
Nacos服务注册的核心类NacosAutoServiceRegistration,并监听事件 - 监听到事件之后,对事件进行处理,封装
Nacos客户端信息,并发送API请求到Nacos服务端接口 - 服务端接收到请求之后,将数据存到初始化的
ConcurrentHashMap中,一次完成服务注册
接下来,我们逐步来分析一下Nacos的其他核心机制,并手写一个注册中心,让大家更好的了解这些分布式中间件。
边栏推荐
- ffplay视频播放原理分析
- typeScript-promise
- 仪表板展示 | DataEase看中国:数据呈现中国资本市场
- If you can't get your heart, use "distributed lock" to lock your people
- 亿流量大考(3):不加机器,如何抗住每天百亿级高并发流量?
- 零基础如何入门软件测试?再到测开(小编心得)
- 【3D建模制作技巧分享】ZBrush模型如何添加不同材质
- [Cultivation of internal skills of string functions] strncpy + strncat + strncmp (2)
- 【3D建模制作技巧分享】ZBrush模型制作流程:地精
- [QNX Hypervisor 2.2用户手册]10.4 vdev hpet
猜你喜欢

If you can't get your heart, use "distributed lock" to lock your people

【内存操作函数内功修炼】memcpy + memmove + memcmp + memset(四)

社区分享|腾讯海外游戏基于JumpServer构建游戏安全运营能力

应用联合、体系化推进。集团型化工企业数字化转型路径

Deep Learning RNN Architecture Analysis

SSM整合完整流程讲解

To Offer | 03. Repeat Numbers in the array

一点点读懂thermal(一)

赶紧进来!!!教你C语言实现扫雷小游戏(文章最后有源码!!!)

【字符串函数内功修炼】strlen + strstr + strtok + strerror(三)
随机推荐
[Cultivation of internal skills of string functions] strcpy + strcat + strcmp (1)
Pytest学习-Fixture
Go 编程语言(简介)
今天又做了三个梦,其中一个梦梦里的我还有意识会思考?
Nacos配置中心之客户端长轮询
PHP(3)
golang打开文件和读写文件
年薪50W+的测试工程师都在用这个:Jmeter 脚本开发之——扩展函数
Acwing3593. 统计单词
亿流量大考(3):不加机器,如何抗住每天百亿级高并发流量?
【字符串函数内功修炼】strlen + strstr + strtok + strerror(三)
生产者消费者问题
养殖虚拟仿真软件提供高沉浸式的虚拟场景互动操作体验学习
一点点读懂cpufreq(一)
仪表板展示 | DataEase看中国:数据呈现中国资本市场
一点点读懂Thremal(二)
应用联合、体系化推进。集团型化工企业数字化转型路径
一点点读懂cpufreq(二)
请你说一下final关键字以及static关键字
话题 | 雾计算和边缘计算有什么区别?