Spring Cloud LoadBalancer 教程

ServiceInstanceListSupplier 接口

在 Spring Cloud LoadBalancer 中,ServiceInstanceListSupplier 是负责提供服务实例列表的核心组件,是负载均衡流程中“获取可用实例”的关键环节。它直接对接服务发现组件(如 Eureka、Nacos 等),并可对实例列表进行过滤、转换或缓存,为负载均衡策略(如轮询、随机)提供决策所需的实例数据。

ServiceInstanceListSupplier 作为“服务实例数据源”,封装了从服务注册中心获取实例列表的逻辑,并支持对实例进行预处理(如健康检查过滤、元数据转换)。

在实际开发中,通过配置或自定义 ServiceInstanceListSupplier,可实现对实例列表的精细化控制,满足灰度发布、区域路由等复杂业务需求。

关键实现类与层级关系

ServiceInstanceListSupplier 有多个内置实现类,形成了“装饰器模式”的层级结构,通过组合实现复杂的实例处理逻辑。核心实现如下:

ServiceInstanceListSupplier 接口

其中:

  • DiscoveryClientServiceInstanceListSupplier  基础实现,对接 DiscoveryClient(服务发现客户端),从注册中心获取原始实例列表。

  • NoopServiceInstanceListSupplier  空实现,用于测试或占位场景,不提供实际的服务实例列表。

  • DelegatingServiceInstanceListSupplier  🌈委托型实现,作为装饰器的基类,用于组合多个 ServiceInstanceListSupplier 的功能,实现职责链模式。

    • WeightedServiceInstanceListSupplier  支持加权的 ServiceInstanceListSupplier 实现,可根据实例的权重属性进行实例筛选或排序,适配加权负载均衡场景。

    • HintBasedServiceInstanceListSupplier  基于提示(Hint)的实例筛选,可根据请求上下文中的提示信息(如特定标签、版本)过滤实例,适用于灰度发布等场景。

    • HealthCheckServiceInstanceListSupplier  带健康检查的 ServiceInstanceListSupplier 实现,过滤掉不健康的服务实例,确保仅返回健康的实例供负载均衡选择。

    • RequestBasedStickySessionServiceInstanceListSupplier  基于请求的粘性会话 ServiceInstanceListSupplier,确保同一请求(或用户)的多次调用路由到同一实例,适用于需要会话保持的场景。

    • SameInstancePreferenceServiceInstanceListSupplier  优先选择上次使用的实例,减少实例切换带来的开销,提升请求连续性。

    • CachingServiceInstanceListSupplier  带缓存的 ServiceInstanceListSupplier 实现,对实例列表进行缓存以减少对服务注册中心的频繁查询,提升性能。

    • RetryAwareServiceInstanceListSupplier  支持重试的 ServiceInstanceListSupplier 实现,在获取实例失败时可触发重试逻辑,增强可靠性。

    • SubsetServiceInstanceListSupplier  子集 ServiceInstanceListSupplier 实现,从全量实例中选择一个子集进行负载均衡,适用于大集群下的实例分组场景。

    • ZonePreferenceServiceInstanceListSupplier  基于区域偏好的 ServiceInstanceListSupplier 实现,优先选择与调用方同区域的实例,降低跨区域调用的网络延迟。

接口定义

ServiceInstanceListSupplier 接口定义如下:

package org.springframework.cloud.loadbalancer.core;

import java.util.List;
import java.util.function.Supplier;

import reactor.core.publisher.Flux;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;

/**
 * 服务实例列表供应商接口,负责提供服务实例的反应式数据流,是负载均衡中"实例数据源"的核心抽象。
 * 作为连接服务发现组件(如Eureka、Nacos)与负载均衡策略的桥梁,它封装了服务实例的获取、
 * 过滤、动态更新等逻辑,为负载均衡器提供实时可用的实例列表。
 *
 * 该接口继承自 Supplier,但返回值为响应式流 Flux,支持实例列表的动态推送
 * (如服务上下线、健康状态变更时自动更新),适配云原生环境下服务实例的动态变化特性。
 *
 * @author Olga Maciaszek-Sharma
 * @since 2.2.0
 * @see DiscoveryClientServiceInstanceListSupplier 基于服务发现客户端的默认实现
 * @see CachingServiceInstanceListSupplier 带缓存功能的装饰器实现
 * @see HealthCheckServiceInstanceListSupplier 带健康检查过滤的装饰器实现
 */
public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {

	/**
	 * 获取当前供应商对应的服务唯一标识(serviceId)。
	 * 每个服务实例列表供应商绑定一个具体的服务,此方法用于标识其对应的服务ID,
	 * 确保负载均衡器能正确关联到目标服务的实例列表。
	 *
	 * @return 服务唯一标识(如"user-service")
	 */
	String getServiceId();

	/**
	 * 基于请求上下文获取服务实例列表的响应式流。
	 * 默认实现直接调用 get(),即不依赖请求上下文;
	 * 子类可重写此方法,实现基于请求特征(如请求参数、Header)的动态实例筛选
	 * (如灰度发布中根据请求的版本信息过滤实例)。
	 *
	 * @param request 包含请求上下文的对象,可携带额外信息用于实例筛选
	 * @return 服务实例列表的响应式流(Flux),当实例发生变化时会推送最新列表
	 */
	default Flux<List<ServiceInstance>> get(Request request) {
		return get();
	}

	/**
	 * 创建一个 ServiceInstanceListSupplierBuilder 构建器,用于便捷地构建
	 * 带装饰器的 ServiceInstanceListSupplier 实例(如组合缓存、健康检查等功能)。
	 *
	 * @return 服务实例列表供应商的构建器
	 */
	static ServiceInstanceListSupplierBuilder builder() {
		return new ServiceInstanceListSupplierBuilder();
	}

	/**
	 * 从服务发现源获取服务实例列表的响应式流(继承自 Supplier 的核心方法)。
	 * 返回的 Flux 会持续推送最新的实例列表:
	 * - 初始订阅时推送当前可用实例
	 * - 当服务实例发生变化(如上下线、健康状态变更)时,自动推送更新后的列表
	 * 该流通常会经过多层装饰器处理(如过滤不健康实例、缓存结果)后提供给负载均衡器。
	 *
	 * @return 服务实例列表的反应式流,非空(若无可可用实例则返回空列表)
	 */
	@Override
	Flux<List<ServiceInstance>> get();

}

从源码可知,总共提供了三个方法,通过 get() 或 get(Request request) 从注册中心获取服务实例列表。

装饰基类 DelegatingServiceInstanceListSupplier

注意:如果不了解装饰模式,可以先熟悉一下,便于后续阅读。

在“关键实现类与层级关系”部分简单介绍了 DelegatingServiceInstanceListSupplier 类,它是装饰器模式的核心实现类,属于 ServiceInstanceListSupplier 家族中的“委托型” ServiceInstanceListSupplier。它通过包装其他 ServiceInstanceListSupplier 实例,实现对实例列表的多层处理(如过滤、缓存、健康检查等),是构建复杂实例处理逻辑的基础组件。

ServiceInstanceListSupplier 通过委托机制实现了实例列表的“多层处理”能力。它让健康检查、缓存、区域过滤等功能可独立实现并灵活组合,是构建复杂负载均衡逻辑的关键组件。在实际使用中,开发者通常无需直接操作该类,而是通过其众多子类(如 HealthCheck、Caching 等)来定制实例处理流程。

DelegatingServiceInstanceListSupplier 在装饰模式中的位置如下图:

ServiceInstanceListSupplier 接口

简单分析:

  • ServiceInstanceListSupplier 作为 Component 接口:定义了获取服务实例列表的统一契约,是整个模式的核心抽象。

  • DiscoveryClientServiceInstanceListSupplier 作为 ConcreteComponent:是 “被装饰的基础组件”,负责从服务发现客户端(如 Eureka、Nacos)获取原始实例列表,提供最基础的实例数据源。

  • DelegatingServiceInstanceListSupplier 作为 Decorator 抽象装饰器:持有 ServiceInstanceListSupplier 类型的 delegate 引用,通过委托机制实现对其他 ServiceInstanceListSupplier 的功能扩展,是所有装饰器子类的基类。

  • WeightedServiceInstanceListSupplier 和 HealthCheckServiceInstanceListSupplier 作为 ConcreteDecorator:是具体的装饰器实现,分别在 delegate 的基础上添加“加权筛选”和“健康检查过滤”的额外行为,实现了功能的动态扩展。

上述“通过组合而非继承来扩展功能” 的设计理念,能够灵活地将健康检查、缓存、加权等多种实例处理逻辑组合起来,满足 Spring Cloud LoadBalancer 对服务实例列表的复杂处理需求。

内部实现细节

DelegatingServiceInstanceListSupplier 内部持有一个被委托的 ServiceInstanceListSupplier 实例(称为 delegate),所有方法都会委托给 delegate 执行,并在其基础上添加额外逻辑。

下面是 DelegatingServiceInstanceListSupplier 的源码:

public abstract class DelegatingServiceInstanceListSupplier
		implements ServiceInstanceListSupplier, SelectedInstanceCallback, InitializingBean, DisposableBean {

    /**
     * 被委托的服务实例列表供应商(装饰器模式中的"被装饰对象")。
     * 所有基础功能调用都会委托给该实例,子类可在此基础上添加额外逻辑。
     */
	protected final ServiceInstanceListSupplier delegate;

	public DelegatingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
		Assert.notNull(delegate, "delegate may not be null");
		this.delegate = delegate;
	}

	/**
	 * 获取被委托的服务实例列表供应商。
	 */
	public ServiceInstanceListSupplier getDelegate() {
		return delegate;
	}

    /**
     * 获取服务ID,直接调用被委托对象的 getServiceId() 方法。
     * 确保整个装饰器链中返回一致的服务标识。
     */
	@Override
	public String getServiceId() {
		return delegate.getServiceId();
	}

	/**
	 * 实例选择回调方法,当负载均衡器选中某个实例时触发。
	 * 若被委托对象实现了 SelectedInstanceCallback,则将回调传递给它,
	 * 确保委托链中的回调逻辑能被逐层执行(如粘性会话的实例记录)。
	 */
	@Override
	public void selectedServiceInstance(ServiceInstance serviceInstance) {
		if (delegate instanceof SelectedInstanceCallback selectedInstanceCallbackDelegate) {
			selectedInstanceCallbackDelegate.selectedServiceInstance(serviceInstance);
		}
	}

	/**
	 * 初始化回调方法,在Bean属性设置完成后执行。
	 * 若被委托对象实现了 InitializingBean,则调用其初始化方法,
	 * 确保委托链中的初始化逻辑(如缓存初始化、健康检查器启动)能被正确执行。
	 * @throws Exception 初始化过程中发生的异常
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		if (delegate instanceof InitializingBean) {
			((InitializingBean) delegate).afterPropertiesSet();
		}
	}

	/**
	 * 销毁回调方法,在Bean销毁时执行。
	 * 若被委托对象实现了 DisposableBean,则调用其销毁方法,
	 * 确保委托链中的资源释放逻辑(如缓存清理、连接关闭)能被正确执行。
	 * @throws Exception 销毁过程中发生的异常
	 */
	@Override
	public void destroy() throws Exception {
		if (delegate instanceof DisposableBean) {
			((DisposableBean) delegate).destroy();
		}
	}

}

上述源码中并没有 get() 方法的身影,因为 get() 方法由子类去实现,下面以 WeightedServiceInstanceListSupplier 类为例,它支持加权的实例列表供应商,可根据实例的权重属性进行实例筛选或排序,适配加权负载均衡场景。部分源码如下:

public class WeightedServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {

	//...

	@Override
	public Flux<List<ServiceInstance>> get() {
        // 委托给被装饰的供应商获取原始实例列表,再通过expandByWeight方法按权重扩展
		return delegate.get().map(this::expandByWeight);
	}

	@Override
	public Flux<List<ServiceInstance>> get(Request request) {
		// 若配置为对委托链传递请求上下文,则先通过 delegate.get(request) 
        // 获取基于请求过滤的实例列表,再按权重扩展
        if (callGetWithRequestOnDelegates) {
            return delegate.get(request).map(this::expandByWeight);
        }
        // 否则直接调用无参get()方法,使用默认实例列表进行权重扩展
        return get();
	}

	/**
     * 根据实例权重对服务实例列表进行扩展,生成一个按权重比例分布的实例列表,
     * 使负载均衡策略(如轮询)能按照权重概率选择实例。
     *
     * <p>例如:若实例A权重为2、实例B权重为1,扩展后列表中A出现2次、B出现1次,
     * 轮询时A被选中的概率是B的2倍,从而实现加权负载均衡。
     *
     * @param instances 原始服务实例列表(未按权重处理)
     * @return 按权重扩展后的实例列表(实际为 LazyWeightedServiceInstanceList 惰性实现,节省内存)
     */
    private List<ServiceInstance> expandByWeight(List<ServiceInstance> instances) {
    	// 若原始实例列表为空,直接返回空列表(无实例可处理)
    	if (instances.size() == 0) {
    		return instances;
    	}
    
    	// 计算每个实例的权重值,存储到数组中
    	int[] weights = instances.stream().mapToInt(instance -> {
    		try {
    			// 应用权重函数获取实例的权重(权重函数可自定义,如从实例元数据中提取)
    			int weight = weightFunction.apply(instance);
    			// 若权重小于等于0,使用默认权重(DEFAULT_WEIGHT,通常为1),并记录调试日志
    			if (weight <= 0) {
    				if (LOG.isDebugEnabled()) {
						LOG.debug(String.format(
								"The weight of the instance %s should be a positive integer, but it got %d, using %d as default",
								instance.getInstanceId(), weight, DEFAULT_WEIGHT));
					}
    				return DEFAULT_WEIGHT;
    			}
    			// 权重合法,直接返回
    			return weight;
    		}
    		// 若计算权重时发生异常(如权重函数执行失败),使用默认权重并记录日志
    		catch (Exception e) {
    			if (LOG.isDebugEnabled()) {
					LOG.debug(String.format(
							"Exception occurred during apply weight function to instance %s, using %d as default",
							instance.getInstanceId(), DEFAULT_WEIGHT), e);
				}
    			return DEFAULT_WEIGHT;
    		}
    	}).toArray();
    
    	// 返回一个惰性加载的加权实例列表(不实际创建重复实例,而是通过索引映射实现权重分布,优化内存)
    	return new LazyWeightedServiceInstanceList(instances, weights);
    }

	//...

}

到这里,是不是已经对 ServiceInstanceListSupplier 和 DelegatingServiceInstanceListSupplier 有了清晰的认识,如果我们要自定义负载均衡策略,只需继承 DelegatingServiceInstanceListSupplier(装饰器基类),重写 get() 或 get(Request) 方法,添加筛选逻辑。例如:

/**
 * 自定义服务实例列表供应商
 */
public class ProdEnvServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {

    // 接收被装饰的 ServiceInstanceListSupplier(如从服务发现获取原始实例的供应商)
    public ProdEnvServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
        super(delegate);
    }

    /**
     * 重写get()方法:对委托返回的实例列表进行过滤
     */
    @Override
    public Flux<List<ServiceInstance>> get() {
        // 1. 调用委托的 get() 获取原始实例列表
        // 2. 过滤出元数据中 env=prod 的实例
        return getDelegate().get()
                .map(instances -> instances.stream()
                        .filter(this::isProdEnv) // 自定义过滤逻辑
                        .collect(Collectors.toList()));
    }

    /**
     * 支持基于请求的筛选(可选)
     */
    @Override
    public Flux<List<ServiceInstance>> get(Request request) {
        // 若需要结合请求上下文过滤,可在此实现
        return getDelegate().get(request)
                .map(instances -> instances.stream()
                        .filter(this::isProdEnv)
                        .collect(Collectors.toList()));
    }

    /**
     * 自定义过滤逻辑:检查实例元数据中是否包含env=prod
     */
    private boolean isProdEnv(ServiceInstance instance) {
        String env = instance.getMetadata().getOrDefault("env", "").toString();
        return "prod".equals(env);
    }
}

被装饰组件:DiscoveryClientServiceInstanceListSupplier

DiscoveryClientServiceInstanceListSupplier 是基于服务发现客户端(DiscoveryClient)的 ServiceInstanceListSupplier 核心实现,负责从 Spring Cloud 服务发现组件(如 Eureka、Nacos、Consul 等)中获取原始服务实例列表,是整个负载均衡实例供应链的 “数据源起点”。

DiscoveryClientServiceInstanceListSupplier 作为 ServiceInstanceListSupplier 体系中的 “基础组件(ConcreteComponent)”,不依赖其他 ServiceInstanceListSupplier,直接对接 DiscoveryClient 接口获取服务实例,为后续装饰器(如健康检查、缓存、过滤)提供原始数据。

屏蔽不同服务发现组件(Eureka/Nacos)的差异,通过 DiscoveryClient 统一接口获取实例,确保负载均衡器无需关心实例的具体来源。

基于 DiscoveryClient 的实例变更通知机制,实时推送服务实例的上下线、元数据变更等信息,保证实例列表的时效性。

原理解析

DiscoveryClientServiceInstanceListSupplier 通过 DiscoveryClient 从服务注册中心获取指定服务(serviceId)的所有实例,包括实例的 host、port、元数据(如版本、环境)、健康状态等信息。

部分源码:

/**
 * 基于服务发现客户端(DiscoveryClient/ReactiveDiscoveryClient)的服务实例列表供应商实现。
 * 作为负载均衡实例数据源的基础实现,直接从服务注册中心(如Eureka、Nacos)获取指定服务的原始 
 * 原始实例列表,并通过反应式流(Flux)提供实时更新的实例数据。
 *
 * @since Spring Cloud LoadBalancer 2.2.0
 * @see ServiceInstanceListSupplier 服务实例列表供应商核心接口
 * @see DiscoveryClient 阻塞式服务发现客户端接口
 * @see ReactiveDiscoveryClient 反应式服务发现客户端接口
 */
public class DiscoveryClientServiceInstanceListSupplier implements ServiceInstanceListSupplier {
	/**
	 * 配置服务发现调用超时时间的属性键,可通过该配置自定义超时
     * 如 "spring.cloud.loadbalancer.service-discovery.timeout=5s"
	 */
	public static final String SERVICE_DISCOVERY_TIMEOUT = "spring.cloud.loadbalancer.service-discovery.timeout";

	private static final Log LOG = LogFactory.getLog(DiscoveryClientServiceInstanceListSupplier.class);

	/**
	 * 服务发现调用的超时时间,默认30秒,可通过{SERVICE_DISCOVERY_TIMEOUT}配置覆盖。
	 */
	private Duration timeout = Duration.ofSeconds(30);

	/**
	 * 当前供应商对应的服务唯一标识(serviceId)。
	 */
	private final String serviceId;

	/**
	 * 服务实例列表的响应式流,用于推送最新的实例数据(包括初始获取和后续更新)。
	 */
	private final Flux<List<ServiceInstance>> serviceInstances;

	/**
	 * 构造方法(基于阻塞式 DiscoveryClient)。
	 * 初始化服务ID、超时时间,并创建从阻塞式服务发现客户端获取实例的反应式流。
	 *
	 * @param delegate 阻塞式服务发现客户端(如EurekaClient、NacosDiscoveryClient)
	 * @param environment 环境配置,用于获取服务ID和超时配置
	 */
	public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, Environment environment) {
		// 从环境配置中获取当前服务ID(通常由负载均衡器自动注入)
		this.serviceId = environment.getProperty(PROPERTY_NAME);
		// 解析超时配置(从环境变量中读取,如未配置则使用默认值)
		resolveTimeout(environment);
		// 创建响应式式流:延迟执行(defer)以实现每次订阅时获取最新实例
		this.serviceInstances = Flux.defer(() -> 
			// 将阻塞式调用(delegate.getInstances)包装为Mono
			Mono.fromCallable(() -> delegate.getInstances(serviceId))
		)
		// 配置超时:超过timeout未获取到实例则返回空列表,并记录调试日志
		.timeout(timeout, Flux.defer(() -> {
			logTimeout();
			return Flux.just(new ArrayList<>());
		}), Schedulers.boundedElastic())
		// 异常处理:服务发现调用失败时返回空列表,并记录错误日志
		.onErrorResume(error -> {
			logException(error);
			return Flux.just(new ArrayList<>());
		});
	}

	/**
	 * 构造方法(基于响应式 ReactiveDiscoveryClient)。
	 * 初始化服务ID、超时时间,并创建从反应式服务发现客户端获取实例的反应式流。
	 *
	 * @param delegate 反应式服务发现客户端(如ReactiveEurekaClient)
	 * @param environment 环境配置,用于获取服务ID和超时配置
	 */
	public DiscoveryClientServiceInstanceListSupplier(ReactiveDiscoveryClient delegate, Environment environment) {
		this.serviceId = environment.getProperty(PROPERTY_NAME);
		resolveTimeout(environment);
		// 基于响应式服务发现客户端创建流,直接调用其getInstances方法(返回Flux)
		this.serviceInstances = Flux
			.defer(() -> delegate.getInstances(serviceId)
				.collectList() // 将 Flux<ServiceInstance> 转换为 Mono<List<ServiceInstance>>
				.flux()
				// 超时处理:与阻塞式构造方法逻辑一致
				.timeout(timeout, Flux.defer(() -> {
					logTimeout();
					return Flux.just(new ArrayList<>());
				}))
				// 异常处理:与阻塞式构造方法逻辑一致
				.onErrorResume(error -> {
					logException(error);
					return Flux.just(new ArrayList<>());
				})
			);
	}

	/**
	 * 获取当前供应商对应的服务ID。
	 *
	 * @return 服务唯一标识(serviceId)
	 */
	@Override
	public String getServiceId() {
	    return serviceId;
	}

	/**
	 * 提供服务实例列表的反应式流。
	 * 每次订阅时会触发从服务发现客户端获取最新实例,支持实时推送实例变更。
	 *
	 * @return 包含服务实例列表的Flux,实例变更时会推送新列表
	 */
	@Override
	public Flux<List<ServiceInstance>> get() {
	    return serviceInstances;
	}

	/**
	 * 从环境配置中解析服务发现超时时间,覆盖默认值。
	 * 支持的时间格式:如"5s"(5秒)、"1m"(1分钟)等(由DurationStyle解析)。
	 *
	 * @param environment 环境配置对象
	 */
	private void resolveTimeout(Environment environment) {
		String providedTimeout = environment.getProperty(SERVICE_DISCOVERY_TIMEOUT);
		if (providedTimeout != null) {
			timeout = DurationStyle.detectAndParse(providedTimeout);
		}
	}

	/**
	 * 记录服务发现超时日志(调试级别)。
	 */
	private void logTimeout() {
		if (LOG.isDebugEnabled()) {
			LOG.debug(String.format("获取服务 %s 的实例超时,超时时间:%s", serviceId, timeout));
		}
	}

	/**
	 * 记录服务发现异常日志(错误级别)。
	 *
	 * @param error 服务发现过程中抛出的异常
	 */
	private void logException(Throwable error) {
		if (LOG.isErrorEnabled()) {
			LOG.error(String.format("获取服务 %s 的实例时发生异常", serviceId), error);
		}
	}

}

到这里就结束了,更多关于 ServiceInstanceListSupplier 的知识,请阅读官方文档。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号