本項目代碼地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford
我們使用 Spring Cloud 官方推薦的 Spring Cloud LoadBalancer 作為我們的客戶端負載均衡器。
Spring Cloud LoadBalancer背景
Spring Cloud LoadBalancer是一個客戶端負載均衡器,類似於Ribbon,但是由於Ribbon已經進入維護模式,並且Ribbon 2並不與Ribbon 1相互兼容,所以Spring Cloud全家桶在Spring Cloud Commons項目中,添加了Spring cloud Loadbalancer作為新的負載均衡器,並且做了向前兼容,就算你的項目中繼續用 Spring Cloud Netflix 套裝(包括Ribbon,Eureka,Zuul,Hystrix等等)讓你的項目中有這些依賴,你也可以通過簡單的配置,把ribbon替換成Spring Cloud LoadBalancer。
負載均衡器在哪里使用?
Spring Cloud 中內部微服務調用默認是 http 請求,主要通過下面三種 API:
- RestTemplate:同步 http API
- WebClient:異步響應式 http API
- 三方客戶端封裝,例如 openfeign
如果項目中加入了 spring-cloud-loadbalancer 的依賴並且配置啟用了,那么會自動在相關的 Bean 中加入負載均衡器的特性。
- 對於 RestTemplate,會自動對所有
@LoadBalanced
注解修飾的 RestTemplate Bean 增加 Interceptor 從而加上了負載均衡器的特性。 - 對於 WebClient,會自動創建
ReactorLoadBalancerExchangeFilterFunction
,我們可以通過加入ReactorLoadBalancerExchangeFilterFunction
會加入負載均衡器的特性。 - 對於三方客戶端,一般不需要我們額外配置什么。
這些使用的示例,會在我們系列升級完最后的測試部分看到。
Spring Cloud LoadBalancer 結構簡介
上一節我們提到了 NamedContextFactory,Spring Cloud LoadBalancer 這里也是使用了這個機制實現了不同微服務使用不同的 Spring Cloud LoadBalancer 配置。相關核心實現是 @LoadBalancerClient
和 @LoadBalancerClients
這兩個注解,以及 NamedContextFactory.Specification
的實現 LoadBalancerClientSpecification
,NamedContextFactory
的實現 LoadBalancerClientFactory
。
經過上一節的詳細分析,我們知道可以通過 LoadBalancerClientFactory
知道默認配置類為 LoadBalancerClientConfiguration
. 並且獲取微服務名稱可以通過 environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
public static final String NAMESPACE = "loadbalancer";
public static final String PROPERTY_NAME = NAMESPACE + ".client.name";
public LoadBalancerClientFactory() {
super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
}
查看配置類 LoadBalancerClientConfiguration
,我們可以發現這個類主要定義兩種 Bean,分別是 ReactorLoadBalancer<ServiceInstance>
和 ServiceInstanceListSupplier
。
ReactorLoadBalancer
是負載均衡器,主要提供根據服務名稱獲取服務實例列表並從從中選擇的功能。
Mono<Response<T>> choose(Request request);
在默認配置中的實現是:
LoadBalancerClientConfiguration
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
//獲取微服務名稱
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
//創建 RoundRobinLoadBalancer
//注意這里注入的是 LazyProvider,這主要因為在注冊這個 Bean 的時候相關的 Bean 可能還沒有被加載注冊,利用 LazyProvider 而不是直接注入所需的 Bean 防止報找不到 Bean 注入的錯誤。
return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
可以看出,默認配置的 ReactorLoadBalancer
實現是 RoundRobinLoadBalancer
。這個負載均衡器實現很簡單,有一個原子類型的 AtomicInteger position
,從 ServiceInstanceListSupplier
中讀取所有的服務實例列表,然后對於 position
原子加1,對列表大小取模,返回列表中這個位置的服務實例 ServiceInstance
。
public Mono<Response<ServiceInstance>> choose(Request request) {
//注入的時候注入的是 Lazy Provider,這里取出真正的 Bean,也就是 ServiceInstanceListSupplier
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
//獲取實例列表
return supplier.get(request)
.next()
//從列表中選擇一個實例
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
// 如果 ServiceInstanceListSupplier 也實現了 SelectedInstanceCallback,則執行下面的邏輯進行回調。SelectedInstanceCallback 就是每次負載均衡器選擇實例之后進行的回調
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
//postion 原子 +1 並取絕對值
int pos = Math.abs(this.position.incrementAndGet());
//返回對應下標的實例
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
ServiceInstanceListSupplier
是服務列表提供者接口:
public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {
String getServiceId();
default Flux<List<ServiceInstance>> get(Request request) {
return get();
}
static ServiceInstanceListSupplierBuilder builder() {
return new ServiceInstanceListSupplierBuilder();
}
}
spring-cloud-loadbalancer 中有很多 ServiceInstanceListSupplier
的實現,在默認配置中是通過屬性配置指定實現的,這個配置項是spring.cloud.loadbalancer.configurations
。例如:
LoadBalancerClientConfiguration
@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//spring.cloud.loadbalancer.configurations 未指定或者為 default
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default",
matchIfMissing = true)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
//通過 DiscoveryClient 提供實例
.withDiscoveryClient()
//開啟緩存
.withCaching()
.build(context);
}
@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//如果 spring.cloud.loadbalancer.configurations 指定為 zone-preference
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference")
public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
//通過 DiscoveryClient 提供實例
.withDiscoveryClient()
//啟用更傾向於同一個 zone 下實例的特性
.withZonePreference()
//開啟緩存
.withCaching()
.build(context);
}
可以看到,可以通過 ServiceInstanceListSupplier.builder()
生成官方封裝好各種特性的 ServiceInstanceListSupplier
。其實從底層實現可以看出,所有的 ServiceInstanceListSupplier
實現都是代理模式,例如對於默認配置,底層代碼近似於:
return //開啟服務實例緩存
new CachingServiceInstanceListSupplier(
//啟用通過 discoveryClient 的服務發現
new DiscoveryClientServiceInstanceListSupplier(
discoveryClient, env
)
, cacheManagerProvider.getIfAvailable()
);
除了默認配置 LoadBalancerClientConfiguration
,用戶配置自定義配置則是通過 @LoadBalancerClients
和 @LoadBalancerClient
.這個原理是通過 LoadBalancerClientConfigurationRegistrar
實現的。首先,我們來看一下 LoadBalancerClientFactory
這個 NamedContextFactory
是如何創建的:
[LoadBalancerAutoConfiguration
]
private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;
public LoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
//注入 LoadBalancerClientSpecification List 的 provider
//在 Bean 創建的時候,進行載入,而不是注冊的時候
this.configurations = configurations;
}
@ConditionalOnMissingBean
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
//創建 LoadBalancerClientFactory
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
//讀取所有的 LoadBalancerClientSpecification,設置為 LoadBalancerClientFactory 的配置
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}
那么,LoadBalancerClientSpecification
這些 Bean 是怎么創建的呢?在 @LoadBalancerClients
和 @LoadBalancerClient
注解中,都包含 @Import(LoadBalancerClientConfigurationRegistrar.class)
。這個 @Import
加載一個 ImportBeanDefinitionRegistrar
,這里是 LoadBalancerClientConfigurationRegistrar
. ImportBeanDefinitionRegistrar
里面的方法參數包含注解元數據,以及注冊 Bean 的 BeanDefinitionRegistry
。一般通過注解元數據,動態通過 BeanDefinitionRegistry
注冊 Bean,在這里的實現是:
[LoadBalancerClients
]
@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(LoadBalancerClientConfigurationRegistrar.class)
public @interface LoadBalancerClients {
//可以指定多個 LoadBalancerClient
LoadBalancerClient[] value() default {};
//指定所有的負載均衡配置的默認配置
Class<?>[] defaultConfiguration() default {};
}
[LoadBalancerClient
]
@Configuration(proxyBeanMethods = false)
@Import(LoadBalancerClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadBalancerClient {
//name 和 value 都是微服務名稱
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
//這個微服務的配置
Class<?>[] configuration() default {};
}
[LoadBalancerClientConfigurationRegistrar
]
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//獲取 LoadBalancerClients 注解的元數據
Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
//對於 value 屬性,其實就是一個 LoadBalancerClient 列表,對於每個生成一個特定微服務名字的 LoadBalancerClientSpecification
for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
}
}
//如果指定了 defaultConfiguration,則注冊為 default 的配置
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
}
//獲取 LoadBalancerClient 注解的元數據
Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
//初始化 LoadBalancerClientSpecification 的 BeanDefinition,用於注冊一個 LoadBalancerClientSpecification Bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(LoadBalancerClientSpecification.class);
//構造器參數
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//注冊 Bean
registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
}
從代碼中我們可以看出,通過使用 @LoadBalancerClients
和 @LoadBalancerClient
注解可以自動生成對應的 LoadBalancerClientSpecification
進而實現公共負載均衡配置或者特定某個微服務的負載均衡配置。
微信搜索“我的編程喵”關注公眾號,加作者微信,每日一刷,輕松提升技術,斬獲各種offer: