深入理解Spring Cloud Ribbon客戶端負載均衡原理(一 實現服務實例地址轉換)
阿新 • • 發佈:2019-02-22
missing 組織 not final str dynamics string pla pan
在使用spring cloud搭建微服務架構時,需要進行負載均衡操作。負載均衡分為硬件負載均衡和軟件負載均衡,軟件負載均衡又分為服務端負載均衡和客戶端負載均衡。本系列主要介紹利用Spring cloud Ribbon 和RestTemplate實現客戶端負載均衡,本文主要介紹將邏輯名為host的URI轉化為服務實例的過程。
一、客戶端負載均衡接口LoadBalanceClient
在進行開發時,要實現對基於RestTemplate的客戶端負載均衡,只需在創建RestTemplate對象時,添加LoadBalanced註解即可實現。通過查看源碼,該註解是通過LoadBalanceClient接口實現其功能。LoadBalanceClient接口定義三個方法,源碼如下:
1 public interface LoadBalancerClient { 2 ServiceInstance choose(String var1); 3 4 <T> T execute(String var1, LoadBalancerRequest<T> var2) throws IOException; 5 6 URI reconstructURI(ServiceInstance var1, URI var2); 7 }
- choose()方法用來從負載均衡器中挑選一個服務實例。
- execute()方法使用選出的服務實例執行具體請求。
- reconstructURI()方法將使用邏輯host名的URI轉換為實際的服務實例URI。
這三個方法之間的關系:在自動配置完成相關配置後(下文介紹), execute()方法執行的第一步需要選擇一個服務實例,及調用choose()方法(實質上並非使用這個chosse()方法,下文介紹)。選擇完服務實例後,調用reconstructURi()方法重新組織成最終的URI,完成URI轉換工作。
二、自動配置類LoadBalanceAutoConfiguration
前面從總體上概要介紹了LoadBalanceClient的工作原理,其中提到的自動配置主要用LoadBalancedAutoConfiguration自動配置類完成,源碼如下:
@Configuration @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired( required = false ) private List<RestTemplate> restTemplates = Collections.emptyList(); public LoadBalancerAutoConfiguration() { } @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { public void afterSingletonsInstantiated() { Iterator var1 = LoadBalancerAutoConfiguration.this.restTemplates.iterator(); while(var1.hasNext()) { RestTemplate restTemplate = (RestTemplate)var1.next(); Iterator var3 = customizers.iterator(); while(var3.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var3.next(); customizer.customize(restTemplate); } } } }; } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } @Bean public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient) { return new LoadBalancerInterceptor(loadBalancerClient); } }
負載均衡自動配置類主要提供三個功能,輔助實在客戶端負載均衡:
- 創建一個攔截器。當被LoadBalanced註解修飾的RestTemplate發送http請求時,將其攔截並從請求體中獲得服務名。
- 創建一個定制器,給RestTemplate添加攔截器。
- 維護一個被LoadBalanced註解修飾的RestTEmplate列表,並對其進行初始化,調用定制器為其添加攔截器。
三、實現客戶端負載均衡
第一小節定義了一個實現客戶端負載的接口,在Ribbon中其實現類是RibbonLoadBalanceClient,部分源碼如下:
public class RibbonLoadBalancerClient implements LoadBalancerClient { private SpringClientFactory clientFactory; public RibbonLoadBalancerClient(SpringClientFactory clientFactory) { this.clientFactory = clientFactory; } public URI reconstructURI(ServiceInstance instance, URI original) { Assert.notNull(instance, "instance can not be null"); String serviceId = instance.getServiceId(); RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); Server server = new Server(instance.getHost(), instance.getPort()); boolean secure = this.isSecure(server, serviceId); URI uri = original; if (secure) { uri = UriComponentsBuilder.fromUri(original).scheme("https").build().toUri(); } return context.reconstructURIWithServer(server, uri); } public ServiceInstance choose(String serviceId) { Server server = this.getServer(serviceId); return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); } public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); try { T returnVal = request.apply(ribbonServer); statsRecorder.recordStats(returnVal); return returnVal; } catch (IOException var9) { statsRecorder.recordStats(var9); throw var9; } catch (Exception var10) { statsRecorder.recordStats(var10); ReflectionUtils.rethrowRuntimeException(var10); return null; } } } }
...
protected Server getServer(ILoadBalancer loadBalancer) {
return loadBalancer == null ? null : loadBalancer.chooseServer("default");
}
}
在該類中,通過執行execute()方法下完成URL轉換,主要步驟如下:
- 首先要利用choose()方法選擇客戶端服務實例,其實質時利用netfilex Ribbon定義的ILoadBalancer接口的實現類完成。ILoadBalancer接口定義了實現客戶端負載均衡的一些操作,包括添加服務、選擇服務、標識服務、服務列表等方法,其基礎現類為BaseLoadBalanceer,在基礎實現類之上,有DynamicServerListLoadBalancer(實現了服務實例清單在運行期的動態更新能力)、ZoneAwareLoadBalancer(對
DynamicServerListLoadBalancer
的擴展,規避跨區域(Zone)訪問,減少延遲)。Spring Clond Ribbon采用的就是ZoneAwareLoadBalancer(區域感知負載均衡器)作為IloadBalancer的實現類,以此對選擇客戶端服務實例。下一篇文章介紹多種負載均衡器。 - 在選擇完服務實例後,chooseServer()方法會返回一個server對象,Ribbon將其包裝成一個RibbonServer對象,該對象是ServiceInstance接口的實現類,該實現類包含了服務治理系統中每個服務實例需要提供的一些信息,包括serviceId、host、port等。
- 將獲得RibbonServer對象傳遞給apply()方法,該方法則會利用負載均衡器中的recontructURL()方法重新組織URL,指向實際的服務實例,進行訪問。
深入理解Spring Cloud Ribbon客戶端負載均衡原理(一 實現服務實例地址轉換)