SpringCloud.Honxton 版本, 全新負載均衡器Loadbalancer
SpringCloud.Honxton 版本, 全新負載均衡器Loadbalancer
前置說明
源碼分析的版本爲Honxton.Release. 爲什麼選這個版本呢? 是因爲springcloud在這個版本才加入自己的負載均衡器. 不過springcloud爲了兼容性,會在ribbon依賴引入時, 優先使用ribbon.
主要的依賴, 也可以直接下載我之前的 sample-springcloud 項目, 裏面有 h版本的分支demo, 鏈接放到評論區
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
初步分析
首先這個是在H版本纔出現, 這個組件的目的根據官方文檔所說就是: 提供自己而定客戶端一遍負載均衡器抽象和實現. 主要是額外增加響應式的負載均衡器接口以及默認 的輪詢實現(從這可以看出, 其負載均衡策略目前只有輪詢, 所以暫時不建議從ribbon切換過來, 畢竟ribbon的負載均衡策略還是很豐富的).
那麼, 一個負載均衡器怎麼自動裝配的肯定是我們需要關注的, 以及它服務列表來源, 以及怎麼負載均衡和什麼時候進行負載均衡. 接下來我們就這三個問題進行分析.
負載均衡器的自動裝配
(本次源碼基於阻塞式的客戶端)在負載均衡器的自動裝配類中, 主要是這三個 common/LoadBalancerAutoConfiguration
, loadbalancer/LoadBalancerAutoConfiguration
, BlockingLoadBalancerClientAutoConfiguration
那麼我們一個個分析, 首先是common下的LoadBalancerAutoConfiguration
自動配置類, 這個類的主要作用是提供LoadBalancerRequestFactory
, 這個主要是用於生產LoadBalancerRequest
,這個類具體的作用在下面分析
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 提供工廠類
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
// 提供resttemplate 攔截器, 這裏的方法名請忽略ribbon, 因爲這是commons包, 不可能和任何的實現有關係, 而且也沒有和ribbon相關的條件裝配, 應該是springcloud忘記改名字了.
// 這個類的作用就是連接restrtemplate 和 loadBalancerClient的紐帶
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// 將攔截器應用到RestTemplate中去
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
.... 其他方法省略
}
// 這個接口就是一個請求動作
public interface LoadBalancerRequest<T> {
T apply(ServiceInstance instance) throws Exception;
}
// LoadBalancerRequest 的工廠
public class LoadBalancerRequestFactory {
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
return execution.execute(serviceRequest, body); //具體的請求執行, 因爲這裏涉及restTemplate的源碼了, 所以不在本文範圍
};
}
}
接下來是loadbalancer依賴下的自動配置類, LoadBalancerAutoConfiguration
,
,BlockingLoadBalancerClientAutoConfiguration
, 前一個的作用是提供LoadBalancerClientFactory
, 後一個是爲了提供BlockingLoadBalancerClient
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class,
ReactiveLoadBalancerAutoConfiguration.class })
public class LoadBalancerAutoConfiguration {
// 提供ReactiveLoadBalancer 負載均衡器
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
clientFactory.setConfigurations(
this.configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}
}
public class LoadBalancerClientFactory
extends NamedContextFactory<LoadBalancerClientSpecification>
implements ReactiveLoadBalancer.Factory<ServiceInstance> {
// 如果自己設置了@LoadBalancerClient, 那麼會從對應的子上下文中去獲取, 否則就走defautl的配置
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
}
}
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@AutoConfigureBefore({
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
public class BlockingLoadBalancerClientAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@Conditional(OnNoRibbonDefaultCondition.class)
protected static class BlockingLoadbalancerClientConfig {
// 提供一個負載均衡的客戶端
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@Primary
public BlockingLoadBalancerClient blockingLoadBalancerClient(
LoadBalancerClientFactory loadBalancerClientFactory) {
// loadBalancerClientFactory
return new BlockingLoadBalancerClient(loadBalancerClientFactory);
}
}
// 阻塞的負載均衡客戶端
public class BlockingLoadBalancerClient implements LoadBalancerClient {
private final LoadBalancerClientFactory loadBalancerClientFactory;
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
// 獲取serviceId 對應的服務實例
ServiceInstance serviceInstance = choose(serviceId);
if (serviceInstance == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
return execute(serviceId, serviceInstance, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
try {
return request.apply(serviceInstance); // 回調, 這個在上面講過了, 其實就是調用restTemplate的方法去請求遠程了
}
catch (IOException iOException) {
throw iOException;
}
catch (Exception exception) {
ReflectionUtils.rethrowRuntimeException(exception);
}
return null;
}
@Override
public ServiceInstance choose(String serviceId) {
// 獲取負載均衡器
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory
.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose())
.block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
}
springcloud實現的自動配置類真的是太長了 雖然我們用的時候很簡單, 但是其實每個配置, 每個依賴都會影響自動裝配的bean是哪個, 所以這裏面的一些bean需要十分的瞭解 , 後面會對上面的各種bean進行串聯分析.
服務列表的獲取以及負載均衡的實現
從上面的一大堆裝配的類, 那麼其中提供服務列表的類就是ServiceInstanceListSupplier
,這個類其實是在LoadBalancerClientConfiguration
這個類中提供的, 而這個類其實是通過 @LoadBalancerClient
註解進行導入的, 所以在上面自動配置類裏面沒有發現這個配置類.
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnBlockingDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
public static class BlockingSupportConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnMissingBean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
// 這個接口,我想看過我前一篇文章的人應該比較熟悉了,就是獲取服務列表的springcloud的抽象接口
DiscoveryClient discoveryClient, Environment env,
ApplicationContext context) {
DiscoveryClientServiceInstanceListSupplier delegate = new DiscoveryClientServiceInstanceListSupplier(
discoveryClient, env);
ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context
.getBeanProvider(LoadBalancerCacheManager.class);
if (cacheManagerProvider.getIfAvailable() != null) {
return new CachingServiceInstanceListSupplier(delegate,
cacheManagerProvider.getIfAvailable());
}
return delegate;
}
}
}
所以服務列表的獲取就是通過DiscoveryClient(具體實現可以是EurekaDiscoveryClient
), 那麼具體的負載實現在哪個類實現呢?其實也是在上面的配置類中有的RoundRobinLoadBalancer
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
// 負載均衡
public Mono<Response<ServiceInstance>> choose(Request request) {
if (serviceInstanceListSupplierProvider != null) {
// 這個就是上面提供到服務實例列表提供者, 這裏調用它的get進行獲取
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get() // 調用get,下面會講到
.next() // 獲取第一個
.map(this::getInstanceResponse); // 將服務列表轉換爲一個實例,也就是調用取模方法
}
ServiceInstanceSupplier supplier = this.serviceInstanceSupplier
.getIfAvailable(NoopServiceInstanceSupplier::new);
return supplier.get().collectList().map(this::getInstanceResponse);
}
// 取模獲取一個服務實例
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
int pos = Math.abs(this.position.incrementAndGet());
// 取模進行輪詢
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
// 服務列表獲取實現
public class DiscoveryClientServiceInstanceListSupplier
implements ServiceInstanceListSupplier {
private final String serviceId;
private final Flux<ServiceInstance> serviceInstances;
public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate,
Environment environment) {
this.serviceId = environment.getProperty(PROPERTY_NAME);
this.serviceInstances = Flux
.defer(() -> Flux.fromIterable(delegate.getInstances(serviceId))) //defer是延遲到訂閱時才加載, fromIterable就是通過discoveryClient去獲取對應serviceId的服務實例列表
.subscribeOn(Schedulers.boundedElastic());
}
public DiscoveryClientServiceInstanceListSupplier(ReactiveDiscoveryClient delegate,
Environment environment) {
this.serviceId = environment.getProperty(PROPERTY_NAME);
this.serviceInstances = delegate.getInstances(serviceId);
}
// 這裏就是get的實現
@Override
public Flux<List<ServiceInstance>> get() {
return serviceInstances.collectList()//這裏主要是將已發射的對象變成list
.flux(); // 這裏就是轉換mono轉換 flux
// 這裏需要說明一下, 看上去這裏好像做了多餘的操作,就是將delegate.getInstances(serviceId)獲取的list一直轉來轉去,
// 其實是因爲當前分析的是阻塞的服務提供列表, 如果是響應式負載均衡器, 那麼服務端就不會一次性發送過來, 而是可能一下發2,3個一下2,3個,
// 所以這裏在獲取到2,3後就封裝成一個list, 供後去的負載均衡使用.
// 想了解響應式的服務發現客戶端, 具體可以看一下`ReactiveDiscoveryClient`這個接口和對應的實現類
}
}
總結
其實到這基本講完了, 最後再來理一下思路
首先是服務列表來源 ,主要是DiscoveryClientServiceInstanceListSupplier
這個類的get方法, 負載均衡算法是在RoundRobinLoadBalancer
的getInstanceResponse方法, 然後和RestTemplate結合的是通過LoadBalancerInterceptor
, 這些就是核心的類了.
分析就到這了, 可能講的有點亂, 不過大體的思路已經給出, 然後後面會補個uml圖, 希望讀者有發現我講的有問題可以在下面評論提出,我會虛心接受的.
和ribbon的對比
ribbon 的服務列表 DomainExtractingServerList
,
ribbon 的負載均衡客戶端 RibbonLoadBalancerClient
ribbon 的負載均衡器 ILoadBalancer
規則 IRule
其實可以看到負載均衡的實現都大相徑庭, 不過目前Loadbalancer的負載均衡算法只有輪詢,所以可以再等等,或者自己去實現豐富的負載均衡算法. 不過ribbon不好的地方在於ribbon自己也會維護一個服務列表, 這樣相當於eureka client維護一套服務列表, ribbon維護一套, 那麼這樣服務下線後, ribbon的服務列表刷新的時間會很久, 因爲無論是eureka還是ribbon都是定時去刷新的; 而Loadbalancer的實現,通過DiscoveryClientServiceInstanceListSupplier
,直接將discoveryClient封裝進去了,所以相對來說一致性會高很多