Spring?cloud負(fù)載均衡@LoadBalanced?&?LoadBalancerClient
LoadBalance vs Ribbon
由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作為客戶端負(fù)載均衡組件,我們討論Spring負(fù)載均衡以Spring Cloud2020之后版本為主,學(xué)習(xí)Spring Cloud LoadBalance,暫不討論Ribbon。
兩者有什么區(qū)別、Spring Cloud為什么移除了Ribbon轉(zhuǎn)向了Spring Cloud LoadBalancer,改日研究。
回顧
上篇文章我們學(xué)習(xí)了Spring Cloud LoadBalance負(fù)載均衡底層原理中的:
@LoadBalanced注解的使用:與@Bean一起作用在RestTemplate上:
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
可以實現(xiàn):在注入RestTemplate對象到Spring IoC容器的同時,啟用Spring的負(fù)載均衡機制。
- @LoadBalanced注解的底層原理:在LoadBalancerAutoConfiguration初始化的過程中,創(chuàng)建攔截器LoadBalancerInterceptor,對請求進(jìn)行攔截從而實現(xiàn)負(fù)載均衡。
- LoadBalancerInterceptor攔截器在執(zhí)行請求前調(diào)用其intercept方法,intercept負(fù)責(zé)負(fù)載均衡的實現(xiàn)(具體的實現(xiàn)邏輯尚未研究)
其中第3點,intercept方法是怎么實現(xiàn)負(fù)載均衡的,我們還沒有深入研究,這是我們今天這篇文章的主要目的。
Spring Cloud負(fù)載均衡原理
LoadBalancerClient及ReactorLoadBalancer初始化
LoadBalancerInterceptor攔截器的intercept方法究竟是怎么實現(xiàn)負(fù)載均衡的?
攔截方法intercept中會通過LoadBalancerClient對象(從Spring IoC容器中獲?。崿F(xiàn)負(fù)載均衡,LoadBalancerClient對象的注入以及攔截原理這個過程稍微復(fù)雜一點,所以我們先用簡單的方式描述其實現(xiàn)邏輯,然后再從源碼角度進(jìn)行跟蹤。
我們在上一篇文章中說過的spring-cloud-commons包下的自動配置類(如圖):
比如對@LoadBalanced注解的解析、LoadBalancerInterceptor的注入等等,就是上面自動配置類LoadBalancerAutoConfiguration完成的。
Spring cloud有兩個名字一樣的自動配置類LoadBalancerAutoConfiguration,位于不同的包下,上面一個是在spring-cloud-commes包下,下面還要提到的一個是在spring-cloud-loadbalancer包下。
spring-cloud-loadbalancer包下的自動配置類LoadBalancerAutoConfiguration負(fù)責(zé)注入LoadBalancerClientFactory對象,LoadBalancerClientFactory負(fù)責(zé)創(chuàng)建子容器(SpringCloud通過子容器來隔離各微服務(wù)的訪問參數(shù)、負(fù)載均衡策略等)。創(chuàng)建LoadBalancerClientFactory對象的過程中將LoadBalancerClientConfiguration設(shè)置給他的defaultConfigType屬性,在子容器初始化的過程中將LoadBalancerClientConfiguration注冊為配置類,從而通過LoadBalancerClientConfiguration配置類完成ReactorLoadBalancer的創(chuàng)建并注入子容器中。ReactorLoadBalancer是負(fù)載均衡策略接口,默認(rèn)的負(fù)載均衡策略為RoundRobinLoadBalancer。
spring-cloud-loadbalancer包下的另外一個自動配置類BlockingLoadBalancerClientAutoConfiguration負(fù)責(zé)注入攔截器中的LoadBalancerClient,實際注入的是BlockingLoadBalancerClient對象,BlockingLoadBalancerClient會持有LoadBalancerClientFactory對象。
LoadBalancerInterceptor的intercept方法會轉(zhuǎn)交給BlockingLoadBalancerClient處理,BlockingLoadBalancerClient通過LoadBalancerClientFactory對象向子容器(子容器不存在的話首先創(chuàng)建子容器)獲取相關(guān)配置以及負(fù)載均衡策略RoundRobinLoadBalancer,最終通過RoundRobinLoadBalancer實現(xiàn)負(fù)載均衡。
需要注意,子容器不是在系統(tǒng)初始化過程中創(chuàng)建的,而是在處理請求的過程中創(chuàng)建的。
下面分析源碼。
LoadBalancerClient
從應(yīng)用層入手分析,先看上一篇文章的案例中的orderServicede的代碼:
@Service public class OrderService { @Autowired private RestTemplate restTemplate; public String getOrder(){ //通過userService獲取user信息 String url="http://userservice/user/getUser"; System.out.println("url"+url); User user=restTemplate.getForObject(url,User.class); System.out.println(user); return user.getName(); } }
restTemplate.getForObject最終會調(diào)用到LoadBalancerInterceptor的intercept方法:
private LoadBalancerClient loadBalancer; public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }
調(diào)用loadBalancer的execute方法,而loadBalancer是LoadBalancerClient 對象、是LoadBalancerInterceptor初始化過程中通過方法參數(shù)從SpringIoC容器中注入進(jìn)來的。
前面提到過,自動配置類BlockingLoadBalancerClientAutoConfiguration負(fù)責(zé)注入攔截器中的LoadBalancerClient,實際注入的是BlockingLoadBalancerClient對象。為了不影響可讀性,我們稍后再看這部分源碼。
繼續(xù)跟蹤loadBalancer的execute方法。首先看一下LoadBalancerClient 的類結(jié)構(gòu);
接口LoadBalancerClient繼承自接口ServiceInstanceChooser,接口定義了choose方法及execute方法(包括其重載方法)。其中execute是調(diào)用入口、也是模板方法:根據(jù)請求的服務(wù)serviceId(比如userService)通過調(diào)用choose方法獲取到最終要調(diào)用的服務(wù)實例serviceInstance,最終調(diào)用到服務(wù)實例所提供的服務(wù):
@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { String hint = getHint(serviceId); LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request, buildRequestContext(request, hint)); Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId); supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); ServiceInstance serviceInstance = choose(serviceId, lbRequest); if (serviceInstance == null) { supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete( new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse()))); throw new IllegalStateException("No instances available for " + serviceId); } return execute(serviceId, serviceInstance, lbRequest); }
繼續(xù)跟蹤choose方法,是在BlockingLoadBalancerClient類中實現(xiàn)的。
BlockingLoadBalancerClient
我們已經(jīng)知道注入到Spring Ioc容器中的LoadBalancerClient其實是BlockingLoadBalancerClient對象,所以繼續(xù)跟蹤BlockingLoadBalancerClient的choose方法:
@Override public <T> ServiceInstance choose(String serviceId, Request<T> request) { ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId); if (loadBalancer == null) { return null; } Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block(); if (loadBalancerResponse == null) { return null; } return loadBalancerResponse.getServer(); }
我們需要重點關(guān)注的是兩個方法:
第一個是LoadBalancerClientFactory的getInstance方法:通過serviceId從子容器中拿到ReactiveLoadBalancer,參數(shù)serviceId(服務(wù)Id),指的就是我們注冊到Eureka注冊中心的服務(wù)Id,比如前面案例中的userService。
第二個是ReactiveLoadBalancer的choose方法:根據(jù)不同的負(fù)載均衡策略,從服務(wù)隊列中拿到serviceInstance。Spring cloud提供了兩種負(fù)載均衡策略:隨機策略RandomLoadBalancer和循環(huán)策略RoundRobinLoadBalancer。
我們先來看第一步:從子容器中獲取ReactiveLoadBalancer對象。
子容器的創(chuàng)建
如果是首次調(diào)用、子容器不存在的情況下,LoadBalancerClientFactory負(fù)責(zé)創(chuàng)建子容器。
LoadBalancerClientFactory是reactiveLoadBalancer.Factory<ServiceInstance>的實現(xiàn)類,繼承自虛擬類NamedContextFactory,創(chuàng)建子容器的大部分代碼都在NamedContextFactory類中。
我們首先看一下這個LoadBalancerClientFactory是怎么初始化的Spring IoC容器中的。其實前面已經(jīng)說過了,是通過spring-cloud-loadbalancer包下的自動配置類LoadBalancerAutoConfiguration負(fù)責(zé)注入。
我們從源碼角度驗證一下,LoadBalancerAutoConfiguration源碼:
@ConditionalOnMissingBean @Bean public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) { LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties); clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; }
看一下LoadBalancerClientFactory的構(gòu)造方法:
public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) { super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME); this.properties = properties; }
父類的構(gòu)造方法:
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) { this.defaultConfigType = defaultConfigType; this.propertySourceName = propertySourceName; this.propertyName = propertyName; }
可以看到LoadBalancerClientFactory創(chuàng)建的時候?qū)oadBalancerClientConfiguration.class賦值給他的父類NamedContextFactory的defaultConfigType屬性,在創(chuàng)建子容器的時候LoadBalancerClientConfiguration類會被注冊為子容器的配置類、從而通過LoadBalancerClientConfiguration完成ReactorLoadBalancer對象的注入(注入到子容器中)。
NamedContextFactory
先對LoadBalancerClientFactory做一個簡單的認(rèn)識。
LoadBalancerClientFactory繼承自虛擬類NamedContextFactory,實現(xiàn)了接口DisposableBean和ApplicationContextAware,這兩個接口我們并不陌生,在Spring生命周期回調(diào)的學(xué)習(xí)過程中中我們了解過這兩個接口,Spring會在容器創(chuàng)建完成后通過ApplicationContextAware的setApplicationContext方法把ApplicationContext送回來、在容器銷毀的時候回調(diào)DisposableBean接口的destroy方法。LoadBalancerClientFactory實現(xiàn)了這兩個接口,所以LoadBalancerClientFactory就可以獲取到Spring IoC根容器的applicationContext:
@Override public void setApplicationContext(ApplicationContext parent) throws BeansException { this.parent = parent; }
檢查NamedContextFactory的setApplicatonContext方法,發(fā)現(xiàn)他把Spring IoC容器設(shè)置為自己的父容器了:這也很好理解,從NamedContextFactory類名稱判斷,這個類的目的就是要創(chuàng)建“Named”容器、也就是命名容器,其實我們后面會發(fā)現(xiàn)就是用serviceId命名的容器,比如我們有userservice,那就會創(chuàng)建一個名字叫userservice的容器。通過ApplicationContextAware回調(diào)setApplicationContext方法將Spring Ioc容器設(shè)置為命名容器的“父容器”。
繼續(xù)跟蹤LoadBalancerClientFactory的getInstance方法,調(diào)用到父類NamedContextFactory的getInstance:
@Override public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) { return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class); } public <T> T getInstance(String name, Class<T> type) { AnnotationConfigApplicationContext context = getContext(name); try { return context.getBean(type); } catch (NoSuchBeanDefinitionException e) { // ignore } return null; }
最終是向Spring的ApplicationContext獲取類型為ReactorServiceInstanceLoadBalancer的bean。其中ApplicationContext通過getContext方法獲?。?/p>
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); 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); }
首先從contexts查找,contexts是以serviceId為鍵值的ConcurrentHashMap,緩存創(chuàng)建的ApplicationContext,如果尚未創(chuàng)建,則調(diào)用createContext方法創(chuàng)建后緩存到contexts中。
這個名字為contexts的ConcurrentHashMap其實就是NamedContextFactory的核心:創(chuàng)建的ApplicationContext緩存在以serviceId為鍵值的HashMap中,獲取的時候以serviceId到contexts中去查找,查找到則直接返回、查找不到則創(chuàng)建后緩存。
createContext方法:
protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context; if (this.parent != null) { // jdk11 issue // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101 // https://github.com/spring-cloud/spring-cloud-openfeign/issues/475 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); if (parent instanceof ConfigurableApplicationContext) { beanFactory.setBeanClassLoader( ((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader()); } else { beanFactory.setBeanClassLoader(parent.getClassLoader()); } context = new AnnotationConfigApplicationContext(beanFactory); context.setClassLoader(this.parent.getClassLoader()); } else { 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); } } } //注意這里會注冊this.defaultConfigType到容器中 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); } context.setDisplayName(generateDisplayName(name)); context.refresh(); return context; }
代碼比較長但是并不復(fù)雜,仔細(xì)看一下其實就是Spring IoC容器的初始化過程:
- 創(chuàng)建DefaultListableBeanFactory
- 創(chuàng)建AnnotationConfigApplicationContext
- 加載屬于當(dāng)前serviceId的配置
- 加載所有的“默認(rèn)”配置(也就是以default.開頭的配置項)
- 加載配置文件(從配置文件及環(huán)境變量中加載),注冊配置類 this.defaultConfigType,其實就是LoadBalancerClientConfiguration配置類
- 設(shè)置父容器(Spring Ioc的主容器設(shè)置為父容器)
- 刷新容器
- 返回容器
一個需要關(guān)注的重點就是:子容器創(chuàng)建的過程中,將配置類LoadBalancerClientConfiguration注冊到容器中,在容器刷新的時候,這個配置類會被加載。
ReactorLoadBalancer & LoadBalancerClientConfiguration
子容器創(chuàng)建出來之后,我們還是返回到上面的NamedContextFactory的getInstance方法中:
@Override public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) { return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class); }
會向子容器獲取ReactorServiceInstanceLoadBalancer對象。
所以我們現(xiàn)在兩個任務(wù):第一個是了解一下ReactorServiceInstanceLoadBalancer類,第二個是要了解到注入到子容器中的ReactorServiceInstanceLoadBalancer究竟是個什么對象。
第一步:看一眼ReactorLoadBalancer的類結(jié)構(gòu):
ReactorServiceInstanceLoadBalancer接口繼承自ReactorLoadBalancer,Spring Cloud提供了他的兩個實現(xiàn)類:隨機策略類和輪詢策略類。
第二步,注入到子容器中的ReactorServiceInstanceLoadBalancer究竟是個什么對象?就需要研究一下ReactorLoadBalancer的初始化過程。
子容器通過配置類LoadBalancerClientConfiguration實現(xiàn)ReactorLoadBalancer的注入,默認(rèn)實現(xiàn)類是RoundRobinLoadBalancer:
@Bean @ConditionalOnMissingBean public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RoundRobinLoadBalancer( loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); }
OK!
真相大白了,默認(rèn)就是輪詢策略RoundRobinLoadBalancer。
負(fù)載均衡策略的配置
Spring Cloud默認(rèn)的負(fù)載均衡策略是RoundRobinLoadBalancer,我們可以通過配置調(diào)整負(fù)載均衡策略為隨機策略RandomLoadBalancer。
調(diào)整方法很簡單,官網(wǎng)說了:
余事以后再說吧。
以上就是Spring cloud負(fù)載均衡@LoadBalanced & LoadBalancerClient的詳細(xì)內(nèi)容,更多關(guān)于Spring cloud負(fù)載均衡@LoadBalanced LoadBalancerClient的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于spring+springmvc+hibernate 整合深入剖析
這篇文章主要介紹了于spring+springmvc+hibernate整合實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Java中Json與List、Map、entity的互相轉(zhuǎn)化
在開發(fā)中,Json轉(zhuǎn)換的場景往往也就是那么幾個,本文主要介紹了Java中Json與List、Map、entity的互相轉(zhuǎn)化,具有一定的參考價值,感興趣的可以了解一下2022-07-07Java讀取txt文件中的數(shù)據(jù)賦給String變量方法
今天小編就為大家分享一篇Java讀取txt文件中的數(shù)據(jù)賦給String變量方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07