微服务框架springcloud(面试篇)——Nacos源码分析

Nacos的服务注册表结构是怎样的?

了解Nacos的服务注册表结构

需要从两方面入手:一是Nacos的分级存储模型,二是Nacos的服务端源码

Nacos源码分析

一、下载Nacos源码并运行

需要下载源码自己编译来运行。

1.下载Nacos源码

Nacos的GitHub地址:https://github.com/alibaba/nacos

找到其release页面:https://github.com/alibaba/nacos/tags,找到其中的1.4.2.版本:

点击进入后,下载Source code(zip):

2.导入Demo工程

微服务Demo(cloud-source-demo),包含了服务注册、发现等业务。

导入该项目后,查看其项目结构:

结构说明:

cloud-source-demo:项目父目录

        cloud-demo:微服务的父工程,管理微服务依赖

        order-service:订单微服务,业务中需要访问user-service,是一个服务消费者

        user-service:用户微服务,对外暴露根据id查询用户的接口,是一个服务提供者

3.导入Nacos源码

将之前下载好的Nacos源码解压到cloud-source-demo项目目录中:

然后,使用IDEA将其作为一个module来导入:

1)选择项目结构选项:

然后点击导入module:

在弹出窗口中,选择nacos源码目录:

然后选择maven模块,finish:

最后,点击OK即可:

导入后的项目结构:

4.proto编译

Nacos底层的数据通信会基于protobuf对数据做序列化和反序列化。并将对应的proto文件定义在了consistency这个子模块中:

我们需要先将proto文件编译为对应的Java代码。

1)什么是protobuf

        protobuf的全称是Protocol Buffer,是Google提供的一种数据序列化协议,这是Google官方的定义:

        Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

        可以简单理解为,是一种跨语言、跨平台的数据传输格式。与json的功能类似,但是无论是性能,还是数据大小都比json要好很多。

        protobuf的之所以可以跨语言,就是因为数据定义的格式为  .proto  格式,需要基于protoc编译为对应的语言。

2)安装protoc

Protobuf的GitHub地址:https://github.com/protocolbuffers/protobuf/releases

可以下载windows版本的来使用:

解压到任意非中文目录下,其中的bin目录中的protoc.exe可以帮助我们编译:

然后将这个bin目录配置到你的环境变量path中,可以参考JDK的配置方式:

3)编译proto

进入nacos-1.4.2的consistency模块下的src/main目录下:

然后打开cmd窗口,运行下面的两个命令:

#powershell

protoc --java_out=./java ./proto/consistency.proto

protoc --java_out=./java ./proto/Data.proto

如图:

会在nacos的consistency模块中编译出这些java代码:

5.运行

nacos服务端的入口是在console模块中的Nacos类:

我们需要让它单机启动:

然后新建一个SpringBootApplication:

然后填写应用信息:

然后运行Nacos这个main函数:

将order-service和user-service服务启动后,可以查看nacos控制台:

二、服务注册

服务注册到Nacos以后,会保存在一个本地注册表中,其结构如下:

首先最外层是一个Map,结构为:`Map<String, Map<String, Service>>`:

        - key:是namespace_id,起到环境隔离的作用。namespace下可以有多个group

        - value:又是一个`Map<String, Service>`,代表分组及组内的服务。一个组内可以有多个服务

        - key:代表group分组,不过作为key时格式是group_name:service_name

        - value:分组下的某一个服务,例如userservice,用户服务。类型为`Service`,内部也包含一个`Map<String,Cluster>`,一个服务下可以有多个集群

        - key:集群名称

        - value:`Cluster`类型,包含集群的具体信息。一个集群中可能包含多个实例,也就是具体的节点信息,其中包含一个`Set<Instance>`,就是该集群下的实例的集合

        - Instance:实例信息,包含实例的IP、Port、健康状态、权重等等信息

每一个服务去注册到Nacos时,就会把信息组织并存入这个Map中。

1.服务注册接口

Nacos提供了服务注册的API接口,客户端只需要向该接口发送请求,即可实现服务注册。

        接口说明:注册一个实例到Nacos服务。

        请求类型:POST

        请求路径:/nacos/v1/ns/instance

        请求参数:

        错误编码:

                       

2.客户端

首先,我们需要找到服务注册的入口。

1)NacosServiceRegistryAutoConfiguration

因为Nacos的客户端是基于SpringBoot的自动装配实现的,我们可以在nacos-discovery依赖:

spring-cloud-starter-alibaba-nacos-discovery-2.2.6.RELEASE.jar

这个包中找到Nacos自动装配信息:

可以看到,有很多个自动配置类被加载了,其中跟服务注册有关的就是NacosServiceRegistryAutoConfiguration这个类,我们跟入其中。

可以看到,在NacosServiceRegistryAutoConfiguration这个类中,包含一个跟自动注册有关的Bean:

2)NacosAutoServiceRegistration

NacosAutoServiceRegistration源码如图:

可以看到在初始化时,其父类AbstractAutoServiceRegistration也被初始化了。

AbstractAutoServiceRegistration如图:

可以看到它实现了`ApplicationListener`接口,监听Spring容器启动过程中的事件。

在监听到 WebServerInitializedEvent(web服务初始化完成)的事件后,执行了`bind` 方法。

其中的bind方法如下:

public void bind(WebServerInitializedEvent event) {
    // 获取 ApplicationContext
    ApplicationContext context = event.getApplicationContext();
    // 判断服务的 namespace,一般都是null
    if (context instanceof ConfigurableWebServerApplicationContext) {
        if ("management".equals(((ConfigurableWebServerApplicationContext) context)
                                .getServerNamespace())) {
            return;
        }
    }
    // 记录当前 web 服务的端口
    this.port.compareAndSet(0, event.getWebServer().getPort());
    // 启动当前服务注册流程
    this.start();
}

其中的start方法流程:

public void start() {
		if (!isEnabled()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Discovery Lifecycle disabled. Not starting");
			}
			return;
		}

		// 当前服务处于未运行状态时,才进行初始化
		if (!this.running.get()) {
            // 发布服务开始注册的事件
			this.context.publishEvent(
					new InstancePreRegisteredEvent(this, getRegistration()));
            // ☆☆☆☆开始注册☆☆☆☆
			register();
			if (shouldRegisterManagement()) {
				registerManagement();
			}
            // 发布注册完成事件
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
            // 服务状态设置为运行状态,基于AtomicBoolean
			this.running.compareAndSet(false, true);
		}

	}

其中最关键的register()方法就是完成服务注册的关键,代码如下:

protected void register() {
    this.serviceRegistry.register(getRegistration());
}

此处的this.serviceRegistry就是NacosServiceRegistry:

3)NacosServiceRegistry

NacosServiceRegistry是Spring的ServiceRegistry接口的实现类,而ServiceRegistry接口是服务注册、发现的规约接口,定义了register、deregister等方法的声明。

而NacosServiceRegistry对register的实现如下:

@Override
public void register(Registration registration) {
	// 判断serviceId是否为空,也就是spring.application.name不能为空
    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
        return;
    }
    // 获取Nacos的命名服务,其实就是注册中心服务
    NamingService namingService = namingService();
    // 获取 serviceId 和 Group
    String serviceId = registration.getServiceId();
    String group = nacosDiscoveryProperties.getGroup();
	// 封装服务实例的基本信息,如 cluster-name、是否为临时实例、权重、IP、端口等
    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);
        }
    }
}

可以看到方法中最终是调用NamingService的registerInstance方法实现注册的。

而NamingService接口的默认实现就是NacosNamingService。

4)NacosNamingService

NacosNamingService提供了服务注册、订阅等功能。

其中registerInstance就是注册服务实例,源码如下:

@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    // 检查超时参数是否异常。心跳超时时间(默认15秒)必须大于心跳周期(默认5秒)
    NamingUtils.checkInstanceIsLegal(instance);
    // 拼接得到新的服务名,格式为:groupName@@serviceId
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    // 判断是否为临时实例,默认为 true。
    if (instance.isEphemeral()) {
        // 如果是临时实例,需要定时向 Nacos 服务发送心跳
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    // 发送注册服务实例的请求
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

最终,由NacosProxy的registerService方法,完成服务注册。

public void
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值