天天看點

【通訊】微服務中的通訊元件

通訊元件主要包括編碼&解碼器,清單過濾,調用代理用戶端,本地優先政策,容錯,重試以及逾時等相關内容。

LoadBalancerClient接口的所屬包org.springframework.cloud.client.loadbalancer,對其内容進行整理,可以得出如下圖的關系:

【通訊】微服務中的通訊元件

IRule有很多預設的實作類,這些實作類根據不同的算法和邏輯來處理負載均衡。在大多數情況下,這些預設的實作類是可以滿足需求的,如果有特性的需求,可以自己實作。

  • BestAvailableRule 選擇最小請求數
  • ClientConfigEnabledRoundRobinRule 輪詢
  • RandomRule 随機選擇一個server
  • RoundRobinRule 輪詢選擇server
  • RetryRule 根據輪詢的方式重試
  • WeightedResponseTimeRule 根據響應時間去配置設定一個weight ,weight越低,被選擇的可能性就越低
  • ZoneAvoidanceRule 根據server的zone區域和可用性來輪詢選擇
【通訊】微服務中的通訊元件

過濾器ZoneAvoidanceRule源碼解讀

ZoneAvoidanceRule源碼解讀

public class ZoneAvoidanceRule extends PredicateBasedRule {
 
    private static final Random random = new Random();
    //使用CompositePredicate來進行服務執行個體清單過濾。
    //組合過濾條件
    private CompositePredicate compositePredicate;
    
    public ZoneAvoidanceRule() {
        super();
        //主過濾條件
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        //次過濾條件
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
......
}      

CompositePredicate源碼解讀

public class ZoneAvoidanceRule extends PredicateBasedRule {
 
    private static final Random random = new Random();
    //使用CompositePredicate來進行服務執行個體清單過濾。
    //組合過濾條件
    private CompositePredicate compositePredicate;
    
    public ZoneAvoidanceRule() {
        super();
        //主過濾條件
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        //次過濾條件
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
......
}      

ZoneAvoidancePredicate源碼解讀

//服務執行個體所在的Zone必須可用
public class ZoneAvoidancePredicate extends  AbstractServerPredicate {
 
    ......
 
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        if (!ENABLED.get()) {
            return true;
        }
        String serverZone = input.getServer().getZone();
        if (serverZone == null) {
            // there is no zone information from the server, we do not want to filter
            // out this server
            return true;
        }
        LoadBalancerStats lbStats = getLBStats();
        if (lbStats == null) {
            // no stats available, do not filter
            return true;
        }
        if (lbStats.getAvailableZones().size() <= 1) {
            // only one zone is available, do not filter
            return true;
        }
        Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
        if (!zoneSnapshot.keySet().contains(serverZone)) {
            // The server zone is unknown to the load balancer, do not filter it out
            return true;
        }
        logger.debug("Zone snapshots: {}", zoneSnapshot);
        Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
        logger.debug("Available zones: {}", availableZones);
        //服務執行個體所在的Zone必須可用
        if (availableZones != null) {
            return availableZones.contains(input.getServer().getZone());
        } else {
            return false;
        }
    }    
}      

清單過濾

大概的實作類流程如下:

【通訊】微服務中的通訊元件

ServerList就是server的清單,IRule做負載均衡的關鍵參數就是server清單,就是從這裡擷取的。

LoadBalancerStats内部有兩個成員變量,一是upServerListZoneMap,二是serverStatsCache。

【通訊】微服務中的通訊元件

upServerListZoneMap定義為volatile

Map<String, List<? extends Server>>,其中存儲了zone和server清單的對應關系。

serverStatsCache定義為LoadingCache<Server,

ServerStats>,存儲了server和ServerStats對象的對應關系,其使用了guaua的cache。

根據以上兩種結構,能夠從每個server的狀态統計到整個zone的狀态。

Affinity即密切關系,這個filter是ribbon的區域親和性過濾。預設情況下,單獨使用ribbon,區域親和的過濾器預設是關閉的,可以通過修改配置打開。

ZoneAffinityServerListFilter先進行親和性過濾,之後再進行zone健康檢查,如果zone認為不健康,則不再執行過濾,認為zone不健康的條件如下:

  • zone中斷路器打開的server比例達到了整個叢集中的80%
  • server平均負載(即存活的請求數)達到60%
  • server存活數小于2,即0或1

DefaultNIWSServerListFilter繼承自ZoneAffinityServerListFilter,預設沒有任何實作。

ZonePreferenceServerListFilter,spring-cloud自己實作的過濾器,即可以通過配置,強制使用配置的zone來過濾。

其實作也比較簡單,即先用ZoneAffinityServerListFilter來過濾,如果父類沒有啟用過濾則再用配置的zone來強制過濾。

如果沒有配置zone,則該filter退化為ZoneAffinityServerListFilter。

ServerListSubsetFilter這個類用于限制傳回server清單的數量,特别适用于叢集中有成百上千的server。因為讓用戶端http連接配接池跟每個server保持連結其實用處不大,它同樣會剔除不健康的server。

調用代理用戶端

WeightedResponseTimeRule,擷取平均響應時間作為權重計算的政策。

AvailabilityFilteringRule,判斷目前斷路器是否打開作為是否可用的政策。使用活躍請求數量選擇最小的server。

ZoneAvoidanceRule,擷取可用的所有zone,擷取zone狀态資訊。

ZoneAwareLoadBalancer,它有如下功能

  • 當選擇server時能夠避免某個不健康的zone。
  • 衡量zone健康狀況的關鍵名額是平均活躍請求,即某個zone的平均活躍請求=這個zone中,還未完成的請求/存活的server

    當某個狀況不好的zone慢慢的發生逾時的時候(即大量的請求被卡住),這個名額是非常有效的。

  • 此均衡器将會計算所有的存活的zone的狀況:當任何某個zone平均活躍請求達到配置的門檻值時,這個zone将會從被從存活清單中移除;如果多個zone達到門檻值,那麼具有平均server請求量最大的zone将會被移除;
  • 一旦狀況最壞的zone被移除,那麼将會按照zone具有的執行個體數量作為機率來選擇某個zone。
  • 選擇完zone,将會使用IRule來選擇一個server,預設的實作為ZoneAvoidanceRule(由于此時隻有一個zone,故ZoneAvoidanceRule退化為AvailabilityFilteringRule)。

來看一下核心方法chooseServer是如何實作的,我們已經知道了最終負載均衡的實作是ZoneAwareLoadBalancer,而它也重寫了chooseServer,它的choose過程如下:

【通訊】微服務中的通訊元件

單個zone的choose過程說明:

  • 從LoadBalancerStats查詢存活的zone數量。
  • 調用BaseLoadBalancer的chooseServer
  • BaseLoadBalancer的實作很簡單,直接委托給具體的Rule來選擇
  • 最終會使用ZoneAvoidanceRule,而此時zone的數量為1,ZoneAvoidanceRule的zone過濾選擇功能不起作用,退化為AvailabilityFilteringRule,即按照server斷路器打開或者并發量過大過濾掉一些server,再輪詢選擇一個。

多個zone的choose過程說明

  • 從LoadBalancerStats查詢存活的zone數量。
  • 發現>1,則調用ZoneAvoidanceRule進行zone的過濾和挑選。
  • 挑選好zone之後,跟 單個zone的choose過程一樣了,委托給BaseLoadBalancer來進行具體的server挑選。

本中心優先政策

利用Ribbon的Zone-Affinity(區域親和性)來解決。Ribbon的Zone-Affinity的原理是:服務消費者在擷取注冊服務的清單時,添加一個filter來過濾注冊服務,根據配置選擇跟服務消費者在同一個區的服務,這樣一般可以降低延遲;如果過濾後的服務正忙或者異常,則停止區域感覺。進而可以做到基于實時的服務運作狀況,決定是否隻選擇本區域的服務,當本區域的服務發生故障時,可以快速的切換。

擴充IRule

可以通過以下配置項識别執行個體所在的區域:


eureka.instance.metadata-map.zone=xxx


深圳的服務全部加上:


eureka.instance.metadata-map.zone=sz
成都的服務全部加上:


eureka.instance.metadata-map.zone=cd
以達到本地優先效果。      

重試

負載均衡的重試機制
ribbon:
  OkToRetryOnAllOperations: true # 是否對所有請求都進行重試
  MaxAutoRetries: 0     #重試次數
  MaxAutoRetriesNextServer: 2   #重試切換執行個體次數
  ConnectTimeout: 250
  ReadTimeout: 300
  retryableStatusCodes: 503,500 # 這裡不配置其實也可以,不配置的時候,僅僅當請求服務執行個體報錯的時候重試,配置了的時候,當請求服務執行個體出現這裡指定的狀态,也會重試      

逾時

#ribbon請求連接配接的逾時時間,限制3秒内必須請求到服務,并不限制服務處理的傳回時間
ribbon.ConnectTimeout=3000
ribbon.SocketTimeout=5000
#請求處理的逾時時間 下級服務響應最大時間,超出時間消費方(路由也是消費方)傳回timeout
ribbon.ReadTimeout=5000
# 單獨設定某個服務的逾時時間,會覆寫其他的逾時時間限制,服務的名稱以注冊中心頁面顯示的名稱為準,逾時時間不可大于斷路器的逾時時間
#service-a.ribbon.ReadTimeout=50000
#service-a.ribbon.ConnectTimeout=50000      

關注公衆号 soft張三豐 

繼續閱讀