1. Nacos 客户端自动注册流程

Spring Boot 在启动时,会扫描所有的 spring.factories 文件,从而加载 spring.factories 文件定义的配置类。

而 Nacos 客户端的配置类为 NacosServiceRegistryAutoConfigurationNacosServiceRegistryAutoConfiguration 一共定义了三个 Bean 对象,分别是:

  • NacosServiceRegistry:用于将微服务实例注册到 Nacos 服务注册中心。
  • NacosRegistration:是一个信息封装类,用于表示一个已经注册到 Nacos 的服务实例。
  • NacosAutoServiceRegistration:真正在 Spring Cloud 应用启动时,自动将服务实例注册到 Nacos 服务注册中心的类。

NacosServiceRegistryAutoConfiguration 的执行条件

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 {}

//====================================

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled",
		matchIfMissing = true)
public @interface ConditionalOnNacosDiscoveryEnabled {

}

从 NacosServiceRegistryAutoConfiguration 类标记的注解可知,要加载 NacosServiceRegistryAutoConfiguration 定义的 Bean 需要满足以下条件:

  • spring.cloud.nacos.discovery.enabled 设置为 true,如果该值未被设置,则默认为 true
  • spring.cloud.service-registry.auto-registration.enabled 设置为 true,如果该值未被设置,则默认为 true
  • AutoServiceRegistrationConfigurationAutoServiceRegistrationAutoConfigurationNacosDiscoveryAutoConfiguration 这三个类被配置类加载完成后。

NacosServiceRegistry

NacosServiceRegistry Bean 的配置

@Bean
public NacosServiceRegistry nacosServiceRegistry(
		NacosServiceManager nacosServiceManager,
		NacosDiscoveryProperties nacosDiscoveryProperties) {
	return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties);
}

从 Bean 定义方法可以看出,创建 NacosServiceRegistry 需要 NacosServiceManagerNacosDiscoveryProperties Bean

  • NacosServiceManager 主要负责与 Nacos 服务端进行交互以实现对服务的操作和管理。
  • NacosDiscoveryProperties 则会读取 application 配置文件中加载定义的 spring.cloud.nacos.discovery 属性,并根本这些属性设置为其字段。

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);
		}
	}
}
  • register 是真正发起服务注册的方法
  • 调用 register 方法需要 Registration 作为参数。Registration 是一个接口,主要用于表示服务实例的注册信息,它只有一个实现类,即 NacosRegistrationNacosRegistration 也就是 NacosServiceRegistryAutoConfiguration 定义的第二个 Bean 对象。

NacosRegistration

NacosRegistration 类是主要用于表示一个已注册到 Nacos 服务注册中心的服务实例的信息。通过这个类可以获取服务实例相关的信息。如服务 ID,服务端口,服务元数据等。

@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
		ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
		NacosDiscoveryProperties nacosDiscoveryProperties,
		ApplicationContext context) {
	return new NacosRegistration(registrationCustomizers.getIfAvailable(),
			nacosDiscoveryProperties, context);
}
  • 创建 NacosRegistration 的 Bean 的条件是 Spring 容器中已存在 AutoServiceRegistrationProperties 类型的 Bean,AutoServiceRegistrationProperties 是存储 spring.cloud.service-registry.auto-registration 属性的对象。
  • 创建 NacosRegistration 需要 List<NacosRegistrationCustomizer>NacosDiscoveryPropertiesApplicationContext 作为参数。
    • NacosRegistrationCustomizer 是一个接口,用于自定义 NacosRegistration 对象,即对注册到 Nacos 服务注册中心的服务实例信息进行定制化操作。
    • NacosDiscoveryProperties 则是存储加载定义的 spring.cloud.nacos.discovery 属性的对象。
    • ApplicationContext 则是 Spring 容器。

NacosAutoServiceRegistration

从 NacosAutoServiceRegistration 的 Auto 可知,这个 Bean 便是发起服务自动注册的类。

@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
		NacosServiceRegistry registry,
		AutoServiceRegistrationProperties autoServiceRegistrationProperties,
		NacosRegistration registration) {
	return new NacosAutoServiceRegistration(registry,
			autoServiceRegistrationProperties, registration);
}
  • 创建 NacosAutoServiceRegistration 的 Bean 的条件是 Spring 容器中已存在 AutoServiceRegistrationProperties 类型的 Bean。AutoServiceRegistrationProperties 是存储 spring.cloud.service-registry.auto-registration 属性的对象。
  • 创建 NacosAutoServiceRegistration 需要 NacosServiceRegistryAutoServiceRegistrationPropertiesNacosRegistration 作为参数。
    • NacosServiceRegistryNacosRegistration 便是在 NacosServiceRegistryAutoConfiguration 定义的前两个对象。

NacosAutoServiceRegistration 的构造方法

public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
		AutoServiceRegistrationProperties autoServiceRegistrationProperties,
		NacosRegistration registration) {
	super(serviceRegistry, autoServiceRegistrationProperties);
	this.registration = registration;
}

protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
	this.serviceRegistry = serviceRegistry;
	this.properties = properties;
}
  • 通过 NacosAutoServiceRegistration 的构造函数可知,NacosAutoServiceRegistration 会将传入的三个参数分别赋予其 registrationserviceRegistryproperties 三个成员变量。

NacosAutoServiceRegistration 的 register 方法

NacosAutoServiceRegistration 最重要的方法是其 register 方法,从方法名可知,这个方法是用于注册服务的方法。

// NacosAutoServiceRegistration
@Override
protected void register() {
	if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
		log.debug("Registration disabled.");
		return;
	}
	if (this.registration.getPort() < 0) {
		this.registration.setPort(getPort().get());
	}
	super.register();
}

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

通过分析 register 方法的调用链,发现 register 方法会在 NacosAutoServiceRegistration 的父类 AbstractAutoServiceRegistration 的 onApplicationEvent 方法中被调用。onApplicationEvent 方法是在 ApplicationListener 接口中被定义。

从 Spring Boot 容器启动的流程可知,Spring 的 ApplicationContext 在启动时会在 finishRefresh 方法发布 Application 事件,调用各种 ApplicationListener 实现类的 onApplicationEvent 方法,其中就包括了 AbstractAutoServiceRegistration 的 onApplicationEvent() 方法,并最终调用 NacosAutoServiceRegistration 的 register 方法进行服务自动化注册。

image

由 NacosAutoServiceRegistration 的 register 方法实现可知,register 方法最终会调用 NacosServiceRegistry 的 register 方法来完成服务注册。

此图可以很好的梳理服务自动注册逻辑:
image

  • 通过spring.factories文件,找到了一个注册相关的Configuration配置类,这个配置类里面定义了三个 Bean 对象。
  • 创建第三个 Bean 对象,需要第一个、第二个 Bean 对象作为参数传进去,第一个 Bean 对象里面就有真正的register方法,并且赋值给了第三个 Bean 对象,父类中的serviceRegistry属性。
  • 在第三个 Bean 对象的父类当中,有实现 Spring 监听器 方法,所以在 Spring 容器启动的时候,会发布监听事件,从而执行 Nacos 注册的逻辑,调用 NacosServiceRegister 的 register 方法。

NacosServiceRegister 的 register 方法

// NacosRegistration
 @Override
public void register(Registration registration) {

   if (StringUtils.isEmpty(registration.getServiceId())) {
      log.warn("No service to register for  Nacos  client...");
      return;
   }

   // 获取 NacosNamingService
   NamingService namingService = namingService();
   
   // serviceId 是服务名称
   String serviceId = registration.getServiceId();
   // gourp 就是分组, Nacos 后台管理也是能看见分组的
   String group = NacosDiscoveryProperties.getGroup();
   
   // Instance 里面包含了有ip、port等信息,感觉这个很重要
   Instance instance = getNacosInstanceFromRegistration(registration);

   // 上面都是分支逻辑,不需要每一个都仔细去看,大概看一下就好了
   try {
   
      // 主线核心方法
      // 我第一眼就看中了这个方法,注册实例 ,点击这个方法registerInstance
      // 是一个接口,并且只有一个默认实现类  NacosNamingService
      namingService.registerInstance(serviceId, group, instance);
      log.info(" Nacos  registry, {} {} {}:{} register finished", group, serviceId,
            instance.getIp(), instance.getPort());
   }
   catch (Exception e) {
      log.error(" Nacos  registry, {} register failed...{},", serviceId,
            registration.toString(), e);
      rethrowRuntimeException(e);
   }
}

NacosRegistration 的 register 方法的逻辑主要分为五部分:

  1. 检验 serviceId 是否存在。
  2. 获取 NamingService。
  3. 创建 Instance 对象。
  4. 获取 serviceId,group 和 instance 属性值。
  5. 调用 NamingService 的 registerInstance() 方法。

检验 serviceId 是否存在

register 首先会调用 NacosRegistration 获取 getServiceId() 方法,getServiceId() 的返回值来源于 NacosDiscoveryProperties 的 service 属性。得到 service 属性值后。如果发现值为空,则直接退出方法,不进行服务注册。

// NacosDiscoveryProperties
@Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}")
private String service;
  • 通过 service 属性的 @Value() 注解可知,service 首先会从 application 配置文件的 spring.cloud.nacos.discovery.service 属性读取值,如果读取失败,则会使用 spring.application.name 的属性值填充到 service 中。

获取 NamingService

检测 service 属性值无误后,会调用 namingService() 方法, nameService 方法最终会获取到 NacosNamingService,NacosNamingService 就代表着服务注册中心在客户端的抽象。通过调用 NacosNamingService 的 registerInstance 方法来完成服务实例的注册。

private NamingService namingService() {
	return nacosServiceManager.getNamingService();
}

// NacosServiceManager
private volatile NamingService namingService;
public NamingService getNamingService() {
	if (Objects.isNull(this.namingService)) {
		buildNamingService(nacosDiscoveryProperties.getNacosProperties());
	}
	return namingService; // NacosNamingService
}

获取 serviceId,group 属性值

由检验 serviceId 是否存在的步骤可知,serviceId 的值来源于 application 配置文件的 spring.cloud.nacos.discovery.servicespring.application.name 属性。

而 group 的值则来源于 application 配置文件的 spring.cloud.nacos.discovery.group 属性,如果没有配置这个属性,则使用默认值 DEFAULT_GROUP

创建 Instance 对象

Instance 对象代表一个服务实例。其中包含了服务实例相关的信息,包括:

  • ip:服务实例所在的 IP 地址。这是用于定位服务实例的关键信息之一,其他服务可以通过这个 IP 地址来访问该服务实例。
  • port:服务实例监听的端口号。结合 IP 地址,确定了服务实例的具体网络位置。
  • weight:服务实例的权重。可以用于负载均衡,决定在多个服务实例中分配请求的比例。例如,权重高的实例可能会接收更多的请求。
  • healthy:表示服务实例的健康状态。通常是一个布尔值,用于指示服务实例是否正常运行,可被其他服务调用。
  • enabled:表示服务实例是否可用。如果设置为false,即使服务实例在运行,也可能不会被服务发现机制返回给其他服务调用者。
  • metadata:一个包含服务实例元数据的键值对集合。可以存储一些自定义的信息,如服务的版本号、所属环境等。这些元数据可以在服务发现和调用过程中被使用,例如根据特定的元数据进行服务筛选。

调用 NacosNamingService 的 registerInstance 方法

在获取到 serviceId,group 属性和创建好 Instance 对象后,就会调用 NacosNamingService 的 registerInstance 执行服务实例注册。

// NacosNamingService
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
	NamingUtils.checkInstanceIsLegal(instance);
	clientProxy.registerService(serviceName, groupName, instance);
}
  • registerInstance 方法首先检查 Instance 是否合法。以下两种情况,被认为是非法

    • 实例的心跳时间超过了心跳超时阈值,或者超过了 ip 删除超时时间。
    • 实例的集群名称(如果不为空)是否符合特定的正则表达式要求。
  • 如果 Instance 合法,则会调用 clientProxy 的 registerService 方法进行实例注册。

  • clientProxy 为 NamingClientProxy 的实现类,NamingClientProxy 接口有两个实现类,一个是 NamingHttpClientProxy,用于使用 http 协议注册服务实例。另一种是 NamingGrpcClientProxy,使用 grpc 协议注册。这两种 Proxy 又由 NameingClientProxyDelegate 来代理。
    image

  • NameingClientProxyDelegate 会根据 application 配置的 spring.cloud.nacos.discovery.ephemeral 来决定应该使用使用哪个 NamingClientProxy

    • 如果 ephemeral=true,说明注册的示例是临时实例,则使用 grpc 协议注册。临时实例在服务正常运行时会向 Nacos 服务注册中心发送心跳,以表明自己的存活状态。如果 Nacos 服务注册中心在一段时间内没有收到某个临时实例的心跳,就会将该实例从服务列表中移除。
    • 如果 ephemeral=false,则表示注册的服务实例为持久化实例,使用 http 协议。持久化实例不会因为一段时间没有发送心跳而被自动移除,通常用于一些需要长期存在且不依赖心跳保持注册状态的服务。

NamingHttpClientProxy 的 registerService 方法

// NameingHttpClientProxy
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
	NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
			instance);
	String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
	if (instance.isEphemeral()) {
		throw new UnsupportedOperationException(
				"Do not support register ephemeral instances by HTTP, please use gRPC replaced.");
	}
	final Map<String, String> params = new HashMap<>(32);
	params.put(CommonParams.NAMESPACE_ID, namespaceId);
	params.put(CommonParams.SERVICE_NAME, groupedServiceName);
	params.put(CommonParams.GROUP_NAME, groupName);
	params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
	params.put(IP_PARAM, instance.getIp());
	params.put(PORT_PARAM, String.valueOf(instance.getPort()));
	params.put(WEIGHT_PARAM, String.valueOf(instance.getWeight()));
	params.put(REGISTER_ENABLE_PARAM, String.valueOf(instance.isEnabled()));
	params.put(HEALTHY_PARAM, String.valueOf(instance.isHealthy()));
	params.put(EPHEMERAL_PARAM, String.valueOf(instance.isEphemeral()));
	params.put(META_PARAM, JacksonUtils.toJson(instance.getMetadata()));
	reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
  • 通过 debug 查看发现,最终会使用 post 请求 nacos-service 的 /nacos/v1/ns/instance 方法注册服务实例。

    • image
  • reqApi 主要封装了 HTTP 请求实现,其关键方法的调用链为:
    image

  • 最终的 HttpClientRequest 有三个实现类:

    • InterceptingHttpClientRequest:带拦截器的 HttpClientRequest
    • DefaultHttpClientRequest:默认的 HttpClientRequest,底层的 HTTP 框架是 appache 的 http-client
    • JdkHttpClientRequest:使用 JDK 内自带的 http 客户端封装的 HttpClientRequest

NamingGrpcClientProxy 的 registerService 方法

// NamingGrpcClientProxy
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
	NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
			instance);
	// 将 instance 缓存到 redosService,以备重试时使用
	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);
	// 发送服务注册消息到 nacos 服务端
	requestToServer(request, Response.class);
	// 在 redoService 记录 instance 注册成功。
	redoService.instanceRegistered(serviceName, groupName);
}

private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
		throws NacosException {
	try {
		request.putAllHeader(
				getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
		// 调用 rpcClient 发送服务实例注册请求。
		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 (NacosException e) {
		throw e;
	} catch (Exception e) {
		throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);
	}
	throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");
}
  • registerService 方法最核心的步骤是调用 GrpcClient 发送 request,Nacos 它在 RPC 的基础之上封装了一层 GrpcClient 对象,底层还是调用了 RPC 那一套。
  • request 的内容如下所示:
    image

从自动注册可以知道,最终注册请求会通过 NamingGrpcClientProxy 或者 NamingHttpClientProxy 来发送,那这两个 Proxy 是什么时候创建的?

通过查看源码发现,NamingGrpcClientProxy 和 NamingHttpClientProxy 都是被 NamingClientProxyDelegate 创建的,而 NamingClientProxyDelegate 又是在NacosNamingService 实例化时创建的, NacosNamingService 则是在 NacosServiceRegistry 的 namingService 方法通过反射机制创建的。
image

接下来,就需要了解 NameGrpcClientProxy 和 NamingHttpClientProxy 的创建流程。

NameGrpcClientProxy 的创建流程

public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory,
            NacosClientProperties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException {
	super(securityProxy);
	this.namespaceId = namespaceId;
	this.uuid = UUID.randomUUID().toString();
	this.requestTimeout = Long.parseLong(properties.getProperty(CommonParams.NAMING_REQUEST_TIMEOUT, "-1"));
	Map<String, String> labels = new HashMap<>();
	labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK);
	labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_NAMING);
	this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels);
	this.redoService = new NamingGrpcRedoService(this);
	start(serverListFactory, serviceInfoHolder);
}
private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException {
	rpcClient.serverListFactory(serverListFactory);
	rpcClient.registerConnectionListener(redoService);
	rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));
	rpcClient.start();
	NotifyCenter.registerSubscriber(this);
}
  • NameGrpcClientProxy 的构造函数,为其成员变量赋值之后,
    • 调用 RpcClientFactory 的 createClient 方法创建一个 RpcClient
    • 创建一个 NamingGrpcRedoService,NamingGrpcRedoService 是重试服务器,在客户端请求 Nacos 失败后,会通过这个类发起重试请求。
    • 最后调用 start 方法,start 方法最终会调用 RpcClient.start() 方法启动 RpcClient

RpcClient 的 start 方法

public final void start() throws NacosException {
        // 修改rpc客户端状态为STARTING
        boolean success = rpcClientStatus.compareAndSet(RpcClientStatus.INITIALIZED, RpcClientStatus.STARTING);
        if (!success) {
            return;
        }
        // 创建周期性任务线程池,线程池核心线程为2.
        clientEventExecutor = new ScheduledThreadPoolExecutor(2, r -> {
            Thread t = new Thread(r);
            t.setName("com.alibaba.nacos.client.remote.worker");
            t.setDaemon(true);
            return t;
        });
        
        // connection event consumer.
        // 向线程池提交处理连接事件的任务。
        clientEventExecutor.submit(() -> {
        	// 首先判断线程池是否被关闭,如果被关闭,则不在执行。
            while (!clientEventExecutor.isTerminated() && !clientEventExecutor.isShutdown()) {
                ConnectionEvent take;
                try {
                	// 从eventLinkedBlockingQueue拿连接事件
                	// 在连接成功时,会往队列中存放连接成功的事件;连接断开时会存放连接断开的事件
                    take = eventLinkedBlockingQueue.take();
                    // 判断事件是否是已连接的事件
                    if (take.isConnected()) {
                    	// 是已连接的事件,则通知监听连接成功的监听器
                    	// NamingGrpcRedoService 就是监听立连接成功监听器
                    	// 连接成功后,NamingGrpcRedoService 将停止重试操作。
                        notifyConnected();
                    } else if (take.isDisConnected()) { // 如果事件是连接断开的事件
                    	// 则通知监听连接断开的监听器。
                    	// NamingGrpcRedoService 也监听这个事件
                    	// 连接断开后,会通知 NamingGrpcRedoService 发起重试机制。
                        notifyDisConnected();
                    }
                } catch (Throwable e) {
                    // Do nothing
                }
            }
        });
        // 服务端健康检查及重连服务,有重连的情况则不会进行健康检查,健康检查的前提是所有连接都健康
        clientEventExecutor.submit(() -> {
            while (true) {
                try {
                	// 如果 rpclient 已经关闭,则退出循环
                    if (isShutdown()) {
                        break;
                    }
                    // 尝试获取重连信号。默认5秒内取不到则返回null
                    ReconnectContext reconnectContext = reconnectionSignal
                            .poll(rpcClientConfig.connectionKeepAlive(), TimeUnit.MILLISECONDS);
                    // 1、如果没有拿到ReconnectContext对象,则表示没有重连的需要,则会检查server的健康状况,
                    if (reconnectContext == null) {
                        // check alive time.
                        // 检查存活时间,这里默认会隔5s 检查一次 server 的健康情况
                        if (System.currentTimeMillis() - lastActiveTimeStamp >= rpcClientConfig.connectionKeepAlive()) {
                        	// 判断当前连接是否健康
                            boolean isHealthy = healthCheck();
                            if (!isHealthy) {
                            	// 如果currentConnection为空,表明还在启动连接server的情况,并不是server那边不健康的问题
                                if (currentConnection == null) {
                                    continue;
                                }
                                // 如果 currentConnection 不为空,则是因为rpc客户端和服务端连接有异常。
                                LoggerUtils.printIfInfoEnabled(LOGGER,
                                        "[{}] Server healthy check fail, currentConnection = {}",
                                        rpcClientConfig.name(), currentConnection.getConnectionId());
                                // 获取当前 rpcClient 的状态
                                RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();
                                // 如果客户端被关闭了,则直接循环。
                                // 如果不判断的话 SHUTDOWN 又被改为UNHEALTHY是不合理的
                                if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {
                                    break;
                                }
                                // 设置客户端不健康状态(使用 CAS 更新,必须要保证是从非 SHUTDOWN 状态切换到 UNHEALTHY 状态)
                                boolean statusFLowSuccess = RpcClient.this.rpcClientStatus
                                        .compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);
                                // 切换成功后,会设置一个serverInfo(相当于重新选一个随机的server来重连)
                                if (statusFLowSuccess) {
                                    reconnectContext = new ReconnectContext(null, false);
                                } else {
                                	// 如果设置失败,表示状态在更新之前发生了改变,则选择重试
									// 可能是状态期间被改为SHUTDOWN了,也可能是状态期间被改为RUNNING
                                    continue;
                                }
                                
                            } else {
                            	// 如果 isHealthy = true,说明是健康
                            	// 记录最后一次的活跃时间。用于下一次对比存活时间。
                                lastActiveTimeStamp = System.currentTimeMillis();
                                continue;
                            }
                        } else {
                        	// 如果 ReconnectContext对象不为空,说明需要重连
                        	// 此时,不应该进行健康检测。
                            continue;
                        }
                        
                    }
                    // 如果需要重连,且重连的服务器信息不为空,检查服务器的状态。
                    if (reconnectContext.serverInfo != null) {
                        // clear recommend server if server is not in server list.
                        boolean serverExist = false;
                        // 获取所有的服务器列表,遍历检查server是否存在,如果存在,则设置服务器的端口。
                        for (String server : getServerListFactory().getServerList()) {
                            ServerInfo serverInfo = resolveServerInfo(server);
                            if (serverInfo.getServerIp().equals(reconnectContext.serverInfo.getServerIp())) {
                                serverExist = true;
                                reconnectContext.serverInfo.serverPort = serverInfo.serverPort;
                                break;
                            }
                        }
                        // 如果服务器不存在则将ReconnectContext的服务信息设置为空。
                        if (!serverExist) {
                            LoggerUtils.printIfInfoEnabled(LOGGER,
                                    "[{}] Recommend server is not in server list, ignore recommend server {}",
                                    rpcClientConfig.name(), reconnectContext.serverInfo.getAddress());
                            
                            reconnectContext.serverInfo = null;
                            
                        }
                    }
                    // 执行重连操作
                    reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail);
                } catch (Throwable throwable) {
                    // Do nothing
                }
            }
        });
        
        // connect to server, try to connect to server sync retryTimes times, async starting if failed.
        // 连接服务器,如果连接失败,则根据 retry 时间重连。
        Connection connectToServer = null;
        rpcClientStatus.set(RpcClientStatus.STARTING);
        
        int startUpRetryTimes = rpcClientConfig.retryTimes();
        while (startUpRetryTimes > 0 && connectToServer == null) {
            try {
                startUpRetryTimes--;
                // 获取服务器信息
                ServerInfo serverInfo = nextRpcServer();
                
                LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Try to connect to server on start up, server: {}",
                        rpcClientConfig.name(), serverInfo);
                // 连接服务器
                connectToServer = connectToServer(serverInfo);
            } catch (Throwable e) {
                LoggerUtils.printIfWarnEnabled(LOGGER,
                        "[{}] Fail to connect to server on start up, error message = {}, start up retry times left: {}",
                        rpcClientConfig.name(), e.getMessage(), startUpRetryTimes, e);
            }
            
        }
        // 如果连接成功
        if (connectToServer != null) {
            LoggerUtils
                    .printIfInfoEnabled(LOGGER, "[{}] Success to connect to server [{}] on start up, connectionId = {}",
                            rpcClientConfig.name(), connectToServer.serverInfo.getAddress(),
                            connectToServer.getConnectionId());
			// 设置当前 rpcClient 所连接的服务器信息。
            this.currentConnection = connectToServer;
            // 将 rpcClient 状态设置为正在运行。
            rpcClientStatus.set(RpcClientStatus.RUNNING);
            // 将连接事件队列加入连接成功事件。
            eventLinkedBlockingQueue.offer(new ConnectionEvent(ConnectionEvent.CONNECTED));
        } else {
        	// 如果连接失败,则切换服务器重新连接
            switchServerAsync();
        }
        // 注册一个 Reset 请求的处理器。
        registerServerRequestHandler(new ConnectResetRequestHandler());
        
        // register client detection request.
        // 注册一个 Detection 请求的处理器。
        registerServerRequestHandler(request -> {
            if (request instanceof ClientDetectionRequest) {
                return new ClientDetectionResponse();
            }
            
            return null;
        });
        
    }

rpcClient 的 start 方法的主要逻辑包括:

  • 创建一个有两个核心线程的线程池
    • 向第一个线程提交处理连接事件的任务。该任务主要是从连接事件队列里获取连接事件,并通知事件监听器
    • 向第二个线程提交健康检查的任务。该任务每个一段时间向 nacos 服务器发起健康检查。
  • 向 nacos 服务器发起连接请求,
    • 如果多次连接失败后,则切换服务器,再异步发起请求。
    • 如果连接成功,则发布连接成功事件。
  • 向请求处理器列表添加两个处理器
    • ConnectResetRequestHandle
    • ClientDetectionRequestHandler

NameHttpClientProxy 的创建流程

// NameHttpClientProxy
private final NacosRestTemplate nacosRestTemplate = NamingHttpClientManager.getInstance().getNacosRestTemplate();

public NamingHttpClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListManager serverListManager,
		NacosClientProperties properties) {
	super(securityProxy);
	this.serverListManager = serverListManager;
	this.setServerPort(DEFAULT_SERVER_PORT);
	this.namespaceId = namespaceId;
	this.maxRetry = ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.NAMING_REQUEST_DOMAIN_RETRY_COUNT,
			String.valueOf(UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT)));
}
// AbstractNamingClientProxy
protected AbstractNamingClientProxy(SecurityProxy securityProxy) {
	this.securityProxy = securityProxy;
}
  • NameHttpClientProxy 首先会创建 NacosRestTemplate,NacosRestTemplate 是向 Nacos 发起请求的 Http 工具
  • 在 NameHttpClientProxy 的构造函数中,会为其成员变量赋值。
posted @ 2024-11-07 23:07  Jacob-Chen  阅读(420)  评论(0)    收藏  举报