获取所有服务列表

  • com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient#getServices
  • 发一个grpc请求获取服务列表

根据serviceId获取服务列表

  • com.alibaba.nacos.client.naming.NacosNamingService#selectInstances()
  • com.alibaba.nacos.client.naming.cache.ServiceInfoHolder#getServiceInfo
  • com.alibaba.nacos.client.naming.cache.ServiceInfoHolder#serviceInfoMap
  • nacos 客户端缓存(serviceInfoMap)维护方式
    • com.alibaba.nacos.client.naming.cache.ServiceInfoHolder#processServiceInfo(o)
      • com.alibaba.nacos.client.naming.core.PushReceiver#run
      • UDP 方式接收服务列表
    • com.alibaba.nacos.client.naming.cache.ServiceInfoHolder#processServiceInfo(java.lang.String)
      • com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService.UpdateTask#run
        • 定时刷新本地服务列表
      • com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate#subscribe
        • grpc 订阅服务.
      • com.alibaba.nacos.client.naming.remote.gprc.NamingPushRequestHandler#requestReply
        • com.alibaba.nacos.common.remote.client.grpc.GrpcClient#bindRequestStream
        • grpc 客户端方式接收注册中心推送然后更新服务列表
        • 如果想要拿到这个事件,必须先订阅.意思是在本地至少调用过目标的微服务.
  • nacos 客户端的服务列表维护使用的服务端push的方式来维护. 所以服务列表都是最新的

spring cloud loadbalancer 服务列表缓存

  • 服务间调用时 负载均衡器是从自己维护的缓存列表中拿service列表
    • org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration.ReactiveSupportConfiguration
      • 针对webflux 应用服务提供的客户端服务实例提供者的配置
    • org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration.BlockingSupportConfiguration
      • 针对webmvc 应用服务提供的客户端服务实例提供者的配置
    • default
      • 缓存的服务列表提供者
    • zone-preference
      • 可用区隔离服务列表提供者+cache
    • health-check
      • 基于健康检查的服务列表提供者
    • request-based-sticky-session
      • 粘性会话 + 健康检查
    • same-instance-preference
      • 它选择先前选择的实例(如果可用)
  • default方式服务列表缓存的维护.

spring cloud loadbalancer 中的LoadBalancerClientFactory

  • 父类中的NamedContextFactory
protected AnnotationConfigApplicationContext getContext(String name) {
	if (!this.contexts.containsKey(name)) {
		synchronized (this.contexts) {
			if (!this.contexts.containsKey(name)) {
				this.contexts.put(name, createContext(name));
			}
		}
	}
	return this.contexts.get(name);
}

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}
  • 会根据每一个serviceId生成一个子的AnnotationConfigApplicationContext 容器
  • 例如gateway的filter中获取此 ReactiveLoadBalancer 获取的就是某个服务的负载均衡器.其中包含了ServiceInstanceListSupplier
private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {
	ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,
			ReactorServiceInstanceLoadBalancer.class);
	if (loadBalancer == null) {
		throw new NotFoundException("No loadbalancer available for " + serviceId);
	}
	supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
	return loadBalancer.choose(lbRequest);
}

优化.

  • 以上我们知道nacos客户端的缓存可以直接用. 不想再用 LoadBalancer 的缓存了(因为不是实时的). 可以修改下 ServiceInstanceListSupplier的bean实现
  • 配置:
@Configuration(proxyBeanMethods = false)
@ConditionalOnReactiveDiscoveryEnabled
public static class ReactiveSupportConfiguration {

    @Bean
    @ConditionalOnBean(ReactiveDiscoveryClient.class)
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        //使用nacos client的ServiceInfoHolder缓存而不使用 LoadBalancer 的缓存
        return ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context);
    }
}


@Configuration(proxyBeanMethods = false)
@ConditionalOnBlockingDiscoveryEnabled
public static class BlockingSupportConfiguration {

    @Bean
    @ConditionalOnBean(DiscoveryClient.class)
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        //使用nacos client 的ServiceInfoHolder 缓存而不使用 LoadBalancer 的缓存
        return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context);
    }
}
  • 因为 discoveryClient 的实现就是nacosDiscoveryClient 其中的getInstances(serviceId)方法是走ServiceInfoHolder的缓存的.
  • 在项目启动时不会初始化这两个配置. 在第一次调用的时候才会初始化这个配置类. 对应上面的LoadBalancerClientFactory
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐