ReactiveLoadBalancer与ServiceInstanceListSupplier
Spring Cloud提供了client的load-balance抽象和实现。在load-balance机制中添加了ReactiveLoadBalancer
接口,并且为其提供了Round-Robin-based
和Random
实现。
为了从反应式服务中选择服务实例,使用了ServiceInstanceListSupplier
接口,Spring Cloud使用ServiceInstanceListSupplier
的service-discovery-based
的实现来从使用类路径中可用的DiscoveryClient
的ServiceDiscovery
中检索可用服务实例。
RoundRobinLoadBalancer
RoundRobinLoadBalancer
是默认情况下使用的ReactiveLoadBalancer
的实现类。可以自定义LoadBalancer
的配置来切换不同的ReactiveLoadBalancer
实现。
切换自己的Spring Cloud LoadBalancer配置
可以使用@LoadBalancerClient
来切换自己的load-balancer client配置,如下:
@Configuration
//value的值指定了在给定的load-balancer client配置下,要发送请求到哪个服务(服务id)
@LoadBalancerClient(value = "stores", configuration = CustomLoadBalancerConfiguration.class)
public class MyConfiguration {
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
或者通过@LoadBalancerClients
来定义多个配置:
@LoadBalancerClients({
@LoadBalancerClient(value = "stores", configuration = StoresLoadBalancerClientConfiguration.class),
@LoadBalancerClient(value = "customers", configuration = CustomersLoadBalancerClientConfiguration.class)
})
作为@LoadBalancerClient
和@LoadBalancerClients
注释的参数的类不应该使用@Configuration
进行注释,也不应在组件扫描之外。
为了更容易地使用自己的LoadBalancer配置,可以使用ServiceInstanceListSupplier
类的builder()
方法
也可以使用Spring Cloud预定义的其他配置方案,来替换默认配置,例如设置spring.cloud.loadbalancer.configurations
属性为zone-preference
来使用带有缓存的ZonePreferenceServiceInstanceListSupplier
,或设置为health-check
来使用带有缓存的HealthCheckServiceInstanceListSupplier
。
可以利用这个特性来实例化不同的ServiceInstanceListSupplier
或ReactorLoadBalancer
的实现来覆盖默认的配置。
Zone-Based Load-Balancing
为了实现基于区域的load-balancing,Spring Cloud提供了ZonePreferenceServiceInstanceListSupplier
,使用特定DiscoveryClient
的区域配置(例如,eureka.instance.metadata-map.zone
)来选择客户端尝试筛选可用服务实例的区域。
可以通过设置spring.cloud.loadbalancer.zone
属性,覆盖特定DiscoveryClient
的区域配置。
为了确定获取到的服务实例(ServiceInstance
)的区域(zone),会检测它元数据(metadata)中"zone"
的值。
ZonePreferenceServiceInstanceListSupplier会筛选获取到的服务实例,只返回在同一区域内的实例。如果"zone"
的值为null
,或者没有在区域内的服务实例,就会返回所有获取到的实例。
使用Zone-based Load-Balancing:
Spring Cloud使用委托来处理ServiceInstanceListSupplier
beans。建议在ZonePreferenceServiceInstanceListSupplier
的构造器中传入DiscoveryClientServiceInstanceListSupplier
的委托,然后用CachingServiceInstanceListSupplier
包装ZonePreferenceServiceInstanceListSupplier
以利用LoadBalancer Cache。
//将该类作为@LoadBalancerClient或@LoadBalancerClients的configuration值,以配置LoadBalancer
public class CustomLoadBalancerConfiguration {
@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withZonePreference()
.withCaching()
.build(context);
}
}
Health-Check Load-Balancing
为了让能够为LoadBalancer提供定期的健康检查成为可能,Spring Cloud提供了HealthCheckServiceInstanceListSupplier
,它会通过委托ServiceInstanceListSupplier
定期的验证这些服务实例是否存活,然后只返回健康的服务实例,除非一个健康的服务实例都没有,那么它就会返回所有获取到的服务实例。
这个机制在使用SimpleDiscoveryClient
时尤其有用。对于由实际Service Registry支持的客户端没有必要使用,因为在查询外部的Service Discovery后,我们已经获得了健康的服务实例。
当每个服务只有很少数量的服务实例时,也推荐使用该机制,以避免对失效的服务实例不停地调用。
HealthCheckServiceInstanceListSupplier
依赖于委托(代理)流提供的更新的服务实例。在少数情况下,当我们想要使用不刷新服务实例的委托操作时,你可以设置spring.cloud.loadbalancer.health-check.refetch-instances
为true
,来让HealthCheckServiceInstanceListSupplier
刷新服务实例列表,尽管服务实例的列表可能会改变(例如DiscoveryClientServiceInstanceListSupplier
)。
也可以通过修改spring.cloud.loadbalancer.health-check.refetch-instances-interval
的值来调整刷新的时间间隔。
修改spring.cloud.loadbalancer.health-check.repeat-health-check
为false
来选择取消额外的重复的健康检查,因为每个服务实例的刷新也会触发一次健康检查。
HealthCheckServiceInstanceListSupplier
使用前缀为spring.cloud.loadbalancer.health-check
的属性。可以用其设置定期检查的initialDelay
和interval
。
可以通过设置spring.cloud.loadbalancer.health-check.path.default
的值设置健康检查的默认URL。也可以用服务的ID作为spring.cloud.loadbalancer.health-check.path.[SERVICE_ID]
中[SERVICE_ID]
的值([SERVICE_ID]
的值为服务的正确ID)。如果[SERVICE_ID]
的值没有被指定,则默认为/actuator/health
。如果[SERVICE_ID]
的值为null
或是一个空值,则健康检查不会被执行。
如果依赖于默认路径/actuator/health
,确保添加了spring-boot-starter-actuator
到你的依赖中,除非打算自己添加一个/actuator/health
路径的服务。
也可以通过设置spring.cloud.loadbalancer.health-check.port
来自定义健康检查请求的端口。如果没有设置,则被健康检查请求的端口为服务实例上可用的端口。
使用Health-Check Load-Balancing:
Spring Cloud使用委托来处理ServiceInstanceListSupplier
beans。建议在HealthCheckServiceInstanceListSupplier
的构造器中传入一个DiscoveryClientServiceInstanceListSupplier
的委托。
public class CustomLoadBalancerConfiguration {
@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
}
对于non-reactive的模式,使用withBlockingHealthChecks()
来创建HealthCheckServiceInstanceListSupplier
。也可以传入自定义的WebClient
和RestTemplate
用于健康检查。
HealthCheckServiceInstanceListSupplier
有它自己的基于Reactor Fluxreplay()
的缓存机制。所以,如果使用该模式的Load-Balancing,可以跳过将其使用CachingServiceInstanceListSupplier
的步骤。
Same-Instance Load-Balancing
在这种模式中,可以设置LoadBalancer优先选择先前选择过的服务实例(如果该实例可用的话)。
使用Same-Instance Load-Balancing:
需要使用SameInstancePreferenceServiceInstanceListSupplier
。可以通过spring.cloud.loadbalancer.configurations
为same-instance-preference
或者通过提供自定义的ServiceInstanceListSupplier
bean:
public class CustomLoadBalancerConfiguration {
@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withSameInstancePreference()
.build(context);
}
}
这也是Zookeeper的
StickyRule
的一个替代方案。
Request-based Sticky Session Load-Balancing
在这种模式中,LoadBalancer优先选择与请求携带的cookie中提供的instanceId
对应的服务实例。
如果请求通过Spring Cloud的LoadBalancer交换过滤器方法和过滤器时使用的ClientRequestContext
或ServerHttpRequestContext
传递给LoadBalancer,Spring Cloud才会支持这种模式。
该模式目前只被WebClient-backed load-balancing支持。
使用Request-based Sticky Session Load-Balancing:
需要使用RequestBasedStickySessionServiceInstanceListSupplier
。可以通过设置spring.cloud.loadbalancer.configurations
为request-based-sticky-session
或提供自定义的ServiceInstanceListSupplier
bean:
public class CustomLoadBalancerConfiguration {
@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withRequestBasedStickySession()
.build(context);
}
}
本模式中,在向前发送请求之前更新所选服务实例(如果原始请求cookie中的服务实例不可用,则该服务实例可能与原始请求cookie中的服务实例不同)非常有用。可以通过设置spring.cloud.loadbalancer.sticky-session.add-service-instance-cookie
为true
来启用这一功能。
默认情况下,cookie的名字为sc-lb-instance-id
,通过设置spring.cloud.loadbalancer.instance-id-cookie-name
修改它的名字。
Hint-based Load-Balancing
Spring Cloud LoadBalancer可以在Request
中设置传入LoadBalancer的String
Hints,之后会被用于ReactiveLoadBalancer
来处理这些Hints。
可以通过spring.cloud.loadbalancer.hint.default
设置所有服务默认的hint,也可以通过设置spring.cloud.loadbalancer.hint.[SERVICE_ID]
([SERVICE_ID]
为服务的正确ID)来为特定的服务设置特定的hint。
Spring Cloud提供了HintBasedServiceInstanceListSupplier
,这是一个ServiceInstanceListSupplier
的hint-based实现。该类会检查hint请求头(默认的请求头名称为X-SC-LB-Hint
,可以通过spring.cloud.loadbalancer.hint-header-name
来修改该名称),如果找到一个hint请求头,就会用该请求头的hint值来筛选服务实例。如果没有hint请求头,HintBasedServiceInstanceListSupplier
使用上面提到的spring.cloud.loadbalancer.hint.[SERVICE_ID]
设置的hint值来筛选请求实例。如果们没有设置hint值,也没有hint请求头,就会返回所有服务实例。
当筛选服务实例时,HintBasedServiceInstanceListSupplier
查找请求实例中的metadataMap
中的hint
值,并返回匹配的服务实例。如果没有匹配的实例,就会返回所有的服务实例。
使用Hint-based Load-Balancing:
public class CustomLoadBalancerConfiguration {
@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHints()
.withCaching()
.build(context);
}
}
ReactorLoadBalancerExchangeFilterFunction
为了更容易的使用Spring Cloud的Load Balancer,可以使用ReactorLoadBalancerExchangeFilterFunction
结合WebClient
与BlockingLoadBalancerClient
(配合RestTemplate
)。
Spring RestTemplate作为Load Balancer Client
使用@LoadBalanced
来创建一个load-balanced RestTemplate
:
@Configuration
public class MyConfiguration {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
public class MyClass {
@Autowired
private RestTemplate restTemplate;
public String doOtherStuff() {
//URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
String results = restTemplate.getForObject("http://stores/stores", String.class);
return results;
}
}
要使用load-balancedRestTemplate
,类路径中必须要有load-balancer的实现类。(添加Spring Cloud LoadBalancer stater
依赖)
Spring WebClient作为Load Balancer Client
使用@LoadBalanced
来自动创建一个load-balanced client:
@Configuration
public class MyConfiguration {
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
public class MyClass {
@Autowired
private WebClient.Builder webClientBuilder;
public Mono<String> doOtherStuff() {
//URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
return webClientBuilder.build().get().uri("http://stores/stores")
.retrieve().bodyToMono(String.class);
}
}
要使用load-balancedRestTemplate
,类路径中必须要有load-balancer的实现类。(添加Spring Cloud LoadBalancer stater
依赖)
在底层ReactiveLoadBalancer
会起作用。
Spring WebFlux WebClient结合ReactorLoadBalancerExchangeFilterFunction**
如果spring-webflux
在类路径中,ReactorLoadBalancerExchangeFilterFunction
会被自动配置。
public class MyClass {
@Autowired
private ReactorLoadBalancerExchangeFilterFunction lbFunction;
public Mono<String> doOtherStuff() {
//URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
return WebClient.builder().baseUrl("http://stores")
.filter(lbFunction)
.build()
.get()
.uri("/stores")
.retrieve()
.bodyToMono(String.class);
}
}
Spring WebFlux WebClient结合Non-reactive Load Balancer Client
如果spring-webflux
在类路径中,LoadBalancerExchangeFilterFunction
会被自动配置。
public class MyClass {
@Autowired
private LoadBalancerExchangeFilterFunction lbFunction;
public Mono<String> doOtherStuff() {
//URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
return WebClient.builder().baseUrl("http://stores")
.filter(lbFunction)
.build()
.get()
.uri("/stores")
.retrieve()
.bodyToMono(String.class);
}
}
该方法已经被废弃了,建议使用Spring WebFlux WebClient结合
ReactorLoadBalancerExchangeFilterFunction
Spring Cloud LoadBalancer Caching
基本上,ServiceInstanceListSupplier
实现类每次要选择服务实例时都要通过DiscoveryClient
类来获取,但在此基础之上我们还提供了两层缓存实现。
Caffeine-backed LoadBalancer Cache实现
如果com.github.ben-manes.caffeine:caffeine
在类路径中,Caffeine-based实现就会被使用。
可以通过设置spring.cloud.loadbalancer.cache.caffeine.spec
属性为自己定义的CaffeineSpec
类来覆盖Caffeine Cache对LoadBalancer的默认配置。
传入自定义的CaffeineSpec
类会覆盖所有其他的LoadBalancerCache设置,包括ttl
和capacity
。
默认的LoadBalancer Cache实现
如果类路径中没有Caffeine Cache,则DefaultLoadBalancerCache
就会被使用。该类由spring-cloud-starter-loadbalancer
自动配置。
配置LoadBalancer Cache
你可以自定义ttl
(entries生存时间)值,设置spring.cloud.loadbalancer.cache.ttl
为Duration
类的String
格式。同理可以设置spring.cloud.loadbalancer.cache.capacity
属性来自定义LoadBalancer Cache的初始容量。
ttl
和initialCapacity
的默认值分别为35
秒和256
。
可以设置spring.cloud.loadbalancer.cache.enabled
为false
来禁用缓存。
虽然基本的、非缓存的实现对于 原型设计和 测试很有用,但它的效率远远低于缓存版本,因此建议在生产中始终使用缓存版本。如果DiscoveryClient
实现(例如EurekaDiscoveryClient
)已经完成了缓存,则应禁用配置LoadBalancer Cache以防止双重缓存。
Transform the load-balanced HTTP request
可以使用被筛选出来的服务实例来转换load-balanced HTTP request。
要用RestTemplate
的话,需要实现并定义LoadBalancerRequsetTransfomer
bean:
@Bean
public LoadBalancerRequestTransformer transformer() {
return new LoadBalancerRequestTransformer() {
@Override
public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
return new HttpRequestWrapper(request) {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.putAll(super.getHeaders());
headers.add("X-InstanceId", instance.getInstanceId());
return headers;
}
};
}
};
}
要用WebClient
的话,需要实现并定义LoadBalancerClientRequestTransformer
bean:
@Bean
public LoadBalancerClientRequestTransformer transformer() {
return new LoadBalancerClientRequestTransformer() {
@Override
public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
return ClientRequest.from(request)
.header("X-InstanceId", instance.getInstanceId())
.build();
}
};
}
如果定义了多个transformer,它们会按照bean的定义顺序来作用。或者,可以使用LocalBalancerRequestTransformer.DEFAULT_ORDER
或者LoadBalancerClientRequestTransformer.DEFAULT_ORDER
来定义它们的作用顺序。
Spring Cloud LoadBalancer Lifecycle
LoadBalancerLifecycle
beans提供了一些回调方法:onStart(Request<RC> request)
、onStartRequest(Request<RC> request, Response<T> lbResponse)
和onComplete(CompletionContext<RES, T, RC> completionContext)
,需要实现这些方法来规定load-balancing前后发生的逻辑。
onStart(Request<RC> request)
方法需要传入一个Request
对象参数,其中包含着用来筛选合适服务实例的信息(包括downstream client request和hint)。onStartRequest(Request<RC> request, Response<T> lbResponse)
方法需要传入一个Request
对象和一个Response
对象作为参数。onComplete(CompletionContext<RES, T, RC> completionContext)
方法需要传入一个CompletionContext
对象,其中包含LoadBalancer Response
(包括选择的服务实例),还有在服务实例上执行的请求的Status
以及(如果有的话)返回给downstream client的Response
,如果有异常发生的话还会包含相应的Throwable
对象。
supports(Class requestContextClass, Class responseClass, Class serverTypeClass)
方法返回的boolean值决定了请求过程中的处理器(processor in question)是否处理给定类型的对象,默认情况下(方法没有被重写)返回true
。
RC:
RequestContext
type; RES:client的response type;T:返回的server type
Spring Cloud LoadBalancer Statistics
Spring Cloud提供了一个LoadBalancerLifecycle
bean叫做MicrometerStatsLoadBalancerLifecycle
,它使用MicroMeter
来提供load-balancer的调用统计。
可以通过设置spring.cloud.loadbalancer.stats.micrometer.enabled
为true
,并且有一个可用的MeterRegistry
对象(Spring Boot Actuator会将其添加到项目中)。
MicrometerStatsLoadBalancerLifecycle
将下列的meters注册到MeterRegistry
中:
loadbalancer.requests.active
: 一个仪表,让你能够监测任何服务实例当前激活的请求数(或其他通过tags附加的服务实例信息)。loadbalancer.requests.success
:一个计时器,测量任何load-balanced请求的执行时间,该请求以将响应传递给underlying client而结束。loadbalancer.requests.failed
:一个计时器,测量任何load-balanced请求的执行时间,该请求因异常而结束。loadbalancer.requests.discard
:一个计数器,计量被丢弃的load-balanced请求数,即LoadBalancer尚未检索到运行请求的服务实例的请求数。
关于服务实例、请求数据和响应数据的附加信息在可用时通过tags添加到度量(metrics)中。
对于有些实现类,如BlockingLoadBalancerClient
,request和response data可能不可用,因为我们根据参数建立泛型类型,所以可能无法确定类型并读取数据。
当为给定Meter
添加至少一条记录时,Meters
将在MeterRegistry
中注册。
Configuring Individual LoadBalancerClients
单独的LoadBalancer clients可以通过一个不同的前缀spring.cloud.loadbalancer.clients.<clientId>
来单独配置,<clientId>
为loadbalancer的名称。默认配置值可以被spring.cloud.loadbalancer.
命名空间设置,并且会被优先合成为client的特定值。
spring:
cloud:
loadbalancer:
health-check:
initial-delay: 1s
clients:
myclient:
health-check:
interval: 30s
上面的例子将合成一个@ConfigurationProperties
的对象并且设置了initial-dalay=1s
和interval=30s
。
大多数client属性可以单独配置,除了以下的全局配置:
spring.cloud.loadbalancer.enabled
:全局启用/关闭load-balancingspring.cloud.loadbalancer.retry.enabled
:全局启用/关闭load-balancing重试机制,如果全局启用了它,仍然可以在单独的client属性中配置来关闭它。spring.cloud.loadbalancer.cache.enabled
:全局启用/关闭load-balancing缓存,如果全局启用了它,仍然可以通过创建自定义配置(在ServiceInstanceListSupplier
委托层级中不包含CachingServiceInstanceListSupplier
)来关闭它。spring.cloud.loadbalancer.stats.micrometer.enabled
:全局启用/关闭LoadBalancer Micrometer metrics。