提要:
如果图片显示失败或者想学习更多关于Nacos源码的内容或者了解本人更多blog,请浏览未注册的版本 ZealSingerの博客

对于Nacos 1.4.1版本的注册中心模块和配置中心模块,两个最重要的功能模块在单体和集群下的主要使用,我们已经学习的差不多了,经过Nacos 1.4.1版本的学习,我们还是能收获到很多的,整洁的代码风格和异步任务;内存队列;推拉结合等优秀的设计。

但是Nacos依旧在更新,其中在Nacos 2.x的版本上,进行了不少的更新包括但不局限于

  • Nacos集群内部的通讯由HTTP方式变更为了gRPC

  • Nacos的Raft协议使用了蚂蚁内部的JRaft且优化

  • Nacos1.x版本中对于注册表采用双层Map的结构,而在Nacos2.x中进行了轻量化处理

  • Nacos在1.x版本中允许一个服务同时存在持久化和非持久化实例,持久化属性只是作为实例的一个元数据进行存储和识别,这就导致了实际情况下运维人员很苦恼且从系统架构层面看来存在矛盾。所以在Nacos 2.x中简化了Nacos的服务数据模型,是否持久化的数据抽象至服务级别且不再允许一个服务同时存在持久化和非持久化实例,实例的是否持久化属性配置继承服务的是否持久化属性配置

  • Nacos配置管理中,SDK和Server之间的一致性协议是通过判断MD5值是否一致来判断的。

    Nacos1.4版本中,采用HTTP1.1的短链接模拟长连接,每30s发一个心跳和Server对比SDK配置的MD5值是否和Server保持一致,如果一直则hold住链接,如果有不一致配置,则把不一致的配置返回,返回SDK获取最新配置值 Nacos2.x版本中,不再使用30s一次的长轮询,而是升级成为了长链接模式,配置变更,启动建立长链接,配置变更后服务端推送变更配置列表,然后SDK拉去配置更新,通讯效率大大提高

通过阿里自己的测试报告,面对大规模注册情况下,Nacos2.0在稳定场景下的能力至少是Nacos1.x的9倍,达到稳定状态后,Nacos2.0的表现也更加优秀,在客户端和实例约10倍数量的情况下却能有更小的CPU消耗,这个大幅度的优化,在代码层面肯定是做了不少优化的,所以Nacos2.x版本也是十分值得我们学习的。

客户端发起gRPC进行服务注册ss

大伙儿还记得在Nacos1.4中客户端是如何实现的自动发起注册,且注册逻辑是怎么处理的么? 不记得的同学可以回去看一下Nacos源码学习计划-Day02-客户端自动注册和客户端心跳检测原理 - ZealSingerの博客啦~

在Nacos1.4的版本中,客户端会利用Spring的Event事件通知机制,当Spring容器初始化完毕,会发送一个WebServerInitializedEvent初始化完毕事件,Nacos中定义了监听这个事件的监听器,当监听到这个Event就可以开启发送注册的逻辑,NacosServiceRegistry 类的 register方法,其底层就是通过HTTP请求调用Nacos服务端的注册接口

那我们现在来看看Nacos2.x版本下,这个register方法的逻辑,可以看到,register整体的逻辑和Nacos1.4版本是一样的

@Override
public void register(Registration registration) {
​
   if (StringUtils.isEmpty(registration.getServiceId())) {
      log.warn("No service to register for nacos client...");
      return;
   }
​
   NamingService namingService = namingService();
   
   // 获取服务ID、分组
   String serviceId = registration.getServiceId();
   String group = nacosDiscoveryProperties.getGroup();
   // 创建 instance 对象
   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);
      }
   }
}
​

那我们继续往下看,注册的核心方法registerInstance()方法,可以看到在2.x版本中的registerInstance()方法和1.4版本中的内容是不一样的了,这里把1.4中的代码贴出来对比一下

可以看到1.4中还会判断是否为临时实例,而很明显2.x版本中不会进行区分判断

// 2.x版本
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);
    // 调用注册方法
    clientProxy.registerService(serviceName, groupName, instance);
}
​
​
// 1.4.x版本
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws  Nacos Exception {
    NamingUtils.checkInstanceIsLegal(instance);
    
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    
    if (instance.isEphemeral()) {
​
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
​
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

接着往下看,也就是去看registerService()方法的逻辑,这个是接口中的方法,有三个实现,怎么找对应的实现类,我们已经介绍过很多方法了无非就是三种

  • 暴力法:直接每个实现类都去看一下

  • 直接法:运行对应的服务debug追踪一下

  • 观察法:找到对应的调用方法的对象,看看其初始化是如何进行的

我们这里其实任何一种方式都可以,如下,我是看到了调用registerService方法的是NacosNamingService类中的clientProxy成员进行的调用,而该成员的初始化是在同类下的init方法中,从init方法块中我们可以看到其对应的类型是NamingClientProxyDelegate

image-20251204093704744

代码我们跟到 NamingClientProxyDelegate 类的 registerService 方法,在方法中我们看到了调用了 getExecuteClientProxy 方法,然后判断该实例是不是临时实例,如果是就返回 grpcClient、否则就返回 httpClient 对象,代码如下:

@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
​
​
private NamingClientProxy getExecuteClientProxy(Instance instance) {
    return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}
​

从这里我们也可以看出Nacos对于临时实例和非临时实例的不同处理:如果注册的实例是临时实例,那么就会走 gRPC 的请求,非临时实例还是会走 HTTP 的方式

HTTP方式的我们就不多说的了,之前1.4中分析过了,我们这次主要来看gRPC的逻辑

@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
            instance);
    redoService.cacheInstanceForRedo(serviceName, groupName, instance);
    // 做注册的服务
    doRegisterService(serviceName, groupName, instance);
}
​
public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
    // 创建请求参数对象
    InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
            NamingRemoteConstants.REGISTER_INSTANCE, instance);
    // 向服务端发起请求
    requestToServer(request, Response.class);
    redoService.instanceRegistered(serviceName, groupName);
}

接下来看到requestToServer方法

private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
        throws NacosException {
    try {
        request.putAllHeader(
                getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
        Response response =
                requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
        if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
            throw new NacosException(response.getErrorCode(), response.getMessage());
        }
        if (responseClass.isAssignableFrom(response.getClass())) {
            return (T) response;
        }
        NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'",
                response.getClass().getName(), responseClass.getName());
    } catch (Exception e) {
        throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);
    }
    throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");
}
​

看到rpcClient.request(request),调用了 rpcClient 对象的请求方法。源码分析到这里,我们也就明白了,Nacos 它在 RPC 的基础之上封装了一层 GrpcClient 对象,底层还是调用了 RPC 那一套。可以看到如下RequestGrpc.RequestFutureStub grpcFutureServiceStub,这个成员的类型就是gRPC中的stub对象

不了解的小伙伴可以事后学习一下 RPC 相关的基本理论知识

image-20251204104154160

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐