«

Nacos源码学习计划-Day19-Nacos2.x-客户端gRPC发起注册请求

ZealSinger 发布于 阅读:253 技术文档


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

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

通过阿里自己的测试报告,面对大规模注册情况下,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()方法的逻辑,这个是接口中的方法,有三个实现,怎么找对应的实现类,我们已经介绍过很多方法了无非就是三种

我们这里其实任何一种方式都可以,如下,我是看到了调用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

编程 Java 项目