1. 程式人生 > >深入理解Spring Cloud Ribbon客戶端負載均衡原理(一 實現服務實例地址轉換)

深入理解Spring Cloud Ribbon客戶端負載均衡原理(一 實現服務實例地址轉換)

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轉換,主要步驟如下:

  1. 首先要利用choose()方法選擇客戶端服務實例,其實質時利用netfilex Ribbon定義的ILoadBalancer接口的實現類完成。ILoadBalancer接口定義了實現客戶端負載均衡的一些操作,包括添加服務、選擇服務、標識服務、服務列表等方法,其基礎現類為BaseLoadBalanceer,在基礎實現類之上,有DynamicServerListLoadBalancer(實現了服務實例清單在運行期的動態更新能力)、ZoneAwareLoadBalancer(對DynamicServerListLoadBalancer的擴展,規避跨區域(Zone)訪問,減少延遲)。Spring Clond Ribbon采用的就是ZoneAwareLoadBalancer(區域感知負載均衡器)作為IloadBalancer的實現類,以此對選擇客戶端服務實例。下一篇文章介紹多種負載均衡器。
  2. 在選擇完服務實例後,chooseServer()方法會返回一個server對象,Ribbon將其包裝成一個RibbonServer對象,該對象是ServiceInstance接口的實現類,該實現類包含了服務治理系統中每個服務實例需要提供的一些信息,包括serviceId、host、port等。
  3. 將獲得RibbonServer對象傳遞給apply()方法,該方法則會利用負載均衡器中的recontructURL()方法重新組織URL,指向實際的服務實例,進行訪問。

深入理解Spring Cloud Ribbon客戶端負載均衡原理(一 實現服務實例地址轉換)