微服務負載均衡 —— ribbon


負載均衡的基本概念

  負載均衡是系統高可用、緩解網絡流量和處理能力擴容的重要手段,廣義的負載均衡指的是服務端負載均衡,如硬件負載均衡(F5)和軟件負載均衡(Nginx)。負載均衡設備會維護一份可用的服務器的信息,當客戶端請求到達負載均衡設備之后,設備會根據一定的負載均衡算法從可用的服務器列表中取出一台可用的服務器,然后將請求轉發到該服務器。對應的負載均衡架構如下圖所示:

負載均衡架構示意圖

  負載均衡是指將負載分攤到多個執行單元上,常見的有2中方式:
  • 獨立進程單元,通過負載均衡策略,將請求轉發到不同的執行單元,如Nginx;
  • 將負載均衡邏輯以代碼形式封裝在服務消費者的客戶端上,客戶端維護一份服務提供者的信息列表,通過負載均衡策略將請求分攤給多個服務提供者,從而達到負載均衡的目的,如Ribbon。

  Ribbon是Netflix發布的雲中間層服務開源項目,其主要功能是提供客戶端實現負載均衡算法。Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,Ribbon是一個客戶端負載均衡器,我們可以在配置文件中Load Balancer后面的所有機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連接等)去連接這些機器,我們也很容易使用Ribbon實現自定義的負載均衡算法。


 Ribbon的策略

  Ribbon使用的是客戶端的負載均衡策略,兩種方式:
  • Ribbon + RestTemplate
  • Ribbon + Feign

RestTemplate + Ribbon消費服務

  Ribbon中負載均衡的客戶端為LoadBalancerClient,在Spring Cloud項目中,Ribbon默認從Eureka Client的服務注冊列表中獲取服務的信息並保存,然后通過LoadBalancerClient來選擇不同的服務實例從而實現負載均衡。如果不希望使用Eureka的注冊信息,可以自己維護一份注冊列表,然后利用Ribbon實現負載均衡。
  LoadBalancerClient接口繼承自ServiceInstanceChooser,實現類為RibbonLoadBalancerClient。
  重要的方法如下:
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException; <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException; ServiceInstance choose(String serviceId);
  這個方法是用來選擇具體的服務實例,最終委托給ILoadBalancer的chooseServer(Object key)方法去選擇具體的服務實例。
 

負載均衡策略

  IRule用於配置負載均衡策略,其中的choose()方法是根據key來獲取server的實例。IRule中默認包含了7種負載均衡策略。

 

  1. RoundRobinRule 【輪詢】默認嘗試10次
  1. RandomRule 【隨機】
  2. AvailabilityFilteringRule 【可用過濾】會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,還有並發的連接數超過閾值的服務,然后對剩余的服務列表進行輪詢
  1. WeightedResponseTimeRule 【響應時間權重】根據平均響應時間計算所有服務的權重,響應時間越快服務權重越大被選中的概率越高。剛啟動時,如果統計信息不足,則使用輪詢策略,等信息足夠,切換到 WeightedResponseTimeRule
  1. RetryRule 【在選定負載均衡策略上使用輪詢的方式重試】先按照輪詢策略獲取服務,如果獲取失敗則在指定時間內重試,獲取可用服務
  1. BestAvailableRule 【選擇最小請求數的服務器】選過濾掉多次訪問故障而處於斷路器跳閘狀態的服務,然后選擇一個並發量最小的服務,如果沒找到,使用隨機輪詢策略選取;
  1. ZoneAvoidanceRule (默認) 【根據服務器所屬服務區的整體運行狀況來輪詢選擇】符合判斷server所在區域的性能和server的可用性選擇服務,根據服務器所屬服務區的運行狀況和可用性來進行負載均衡。

IPing

   IPing向server發送"ping",根據是否有回應來判斷server是否可用。
實現類共有5種:
  1. DummyPing 直接返回true
  1. NIWSDiscoveryPing 根據DiscoveryEnabledServer的InstanceInfo的status進行判斷,如果為UP,表明可用;
  1. NoOpPing 不真實ping,直接返回true;
  1. PingConstant 固定返回某服務是否可用,是一個常量值;
  1. PingUrl 使用HttpClient進行ping操作,根據返回結果判定是否可用。
  負載均衡器從Eureka Client獲取服務列表信息,並根據IRule的策略進行路由,根據IPing判斷服務的可用性。
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
        List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();

        if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
            logger.warn("EurekaClient has not been initialized yet, returning an empty list");
            return new ArrayList<DiscoveryEnabledServer>();
        }

        EurekaClient eurekaClient = eurekaClientProvider.get();
        if (vipAddresses!=null){
            for (String vipAddress : vipAddresses.split(",")) {
                // if targetRegion is null, it will be interpreted as the same region of client
                List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
                for (InstanceInfo ii : listOfInstanceInfo) {
                    if (ii.getStatus().equals(InstanceStatus.UP)) {

                        if(shouldUseOverridePort){
                            if(logger.isDebugEnabled()){
                                logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
                            }

                            // copy is necessary since the InstanceInfo builder just uses the original reference,
                            // and we don't want to corrupt the global eureka copy of the object which may be
                            // used by other clients in our system
                            InstanceInfo copy = new InstanceInfo(ii);

                            if(isSecure){
                                ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
                            }else{
                                ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
                            }
                        }

                        DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
                        serverList.add(des);
                    }
                }
                if (serverList.size()>0 && prioritizeVipAddressBasedServers){
                    break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
                }
            }
        }
        return serverList;
    }

  對於ribbon從Eureka Client獲取注冊信息,由於服務可能存在更新,所以需要定時從Eureka Client獲取最新的注冊信息。

  在setupPingTask()方法中,開啟了shutdownEnableTimer的PingTask任務,默認情況下,變量pingIntervalSeconds的值為10,即每10秒向Eureka Client發送一次心跳“ping”。
  在PingTask中創建了一個Pinger對象,並執行了runPinger()方法。
    class PingTask extends TimerTask {
        public void run() {
            try {
                new Pinger(pingStrategy).runPinger();
            } catch (Exception e) {
                logger.error("LoadBalancer [{}]: Error pinging", name, e);
            }
        }
    }

  在LoadBalancerAutoConfiguration類中,首先維護了一個被LoadBalanced修飾的RestTemplate對象的list。初始化過程中,通過調用customer.customize(restTemplate)方法給RestTemplate增加攔截器LoadBalancerInterceptor,LoadBalancerInterceptor用於實時攔截,在LoadBalancerInterceptor中實現了負載均衡的方法。

 

    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));
    }

 

 

 

 結論

  Ribbon的負載均衡主要通過LoadBalancerClient來實現,而具體實現交給ILoadBalancer處理,ILoadBalancer通過配置IRule、IPing等,向Eureka Client獲取注冊列表信息,默認每10s向Eureka Client發送一次“ping”來檢查是否需要更新服務的注冊列表信息,最后在得到的服務列表的就出上,ILoadBalancer根據IRule的規則進行負載均衡。
  RestTemplate加上@LoadBalance注解之后,在遠程調度的時候可以實現負載均衡,主要是維護了一個被@LoadBalance注解的RestTemplate列表,為該列表加上攔截器,在攔截器的方法中,將遠程調度方法交給Ribbon的負載均衡器ILoadBalancerClient去處理,從而達到負載均衡的目的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM