Netflix Ribbon原理與實踐

Ribbon是什麼?

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

Ribbon提供的主要負載均衡策略介紹

簡單輪詢負載均衡(RoundRobin)

以輪詢的方式依次將請求調度不同的服務器,即每次調度執行i = (i + 1) mod n,並選出第i臺服務器。

Java代碼 
  1.     Server server = null;  
  2.     int count = 0;  
  3.     while (server == null && count++ < 10) {  
  4.         List<Server> reachableServers = lb.getReachableServers();  
  5.         List<Server> allServers = lb.getAllServers();  
  6.         int upCount = reachableServers.size();  
  7.         int serverCount = allServers.size();  
  8.   
  9.         if ((upCount == 0) || (serverCount == 0)) {  
  10.             log.warn("No up servers available from load balancer: " + lb);  
  11.             return null;  
  12.         }  
  13.   
  14.         int nextServerIndex = incrementAndGetModulo(serverCount);  
  15.         server = allServers.get(nextServerIndex);  
  16.   
  17.         if (server == null) {  
  18.             /* Transient. */  
  19.             Thread.yield();  
  20.             continue;  
  21.         }  
  22.   
  23.         if (server.isAlive() && (server.isReadyToServe())) {  
  24.             return (server);  
  25.         }  
  26.   
  27.         // Next.  
  28.         server = null;  
  29.     }  
  30.   
  31.     if (count >= 10) {  
  32.         log.warn("No available alive servers after 10 tries from load balancer: "  
  33.                 + lb);  
  34.     }  
  35.     return server;  
  36.   
  37. ///////////////////////////////////////////////////////////////////  
  38. /** 
  39.  * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}. 
  40.  * 
  41.  * @param modulo The modulo to bound the value of the counter. 
  42.  * @return The next value. 
  43.  */  
  44. private int incrementAndGetModulo(int modulo) {  
  45.     for (;;) {  
  46.         int current = nextServerCyclicCounter.get();  
  47.         int next = (current + 1) % modulo;  
  48.         if (nextServerCyclicCounter.compareAndSet(current, next))  
  49.             return next;  
  50.     }  
  51. }  

 

加權響應時間負載均衡 (WeightedResponseTime)

Html代碼 
  1. The basic idea for weighted round robin has been obtained from JCS  
  2.   The implementation for choosing the endpoint from the list of endpoints  
  3.   is as follows:Let's assume 4 endpoints:A(wt=10), B(wt=30), C(wt=40),   
  4.   D(wt=20).   
  5.     
  6.   Using the Random API, generate a random number between 1 and10+30+40+20.  
  7.   Let's assume that the above list is randomized. Based on the weights, we  
  8.   have intervals as follows:  
  9.     
  10.   1-----10 (A's weight)  
  11.     
  12.   11----40 (A's weight + B's weight)  
  13.     
  14.   41----80 (A's weight + B's weight + C's weight)  
  15.     
  16.   81----100(A's weight + B's weight + C's weight + D's weight)  
  17.     
  18.   Here's the psuedo code for deciding where to send the request:  
  19.     
  20.   if (random_number between 1 &amp; 10) {send request to A;}  
  21.     
  22.   else if (random_number between 11 &amp; 40) {send request to B;}  
  23.     
  24.   else if (random_number between 41 &amp; 80) {send request to C;}  
  25.     
  26.   else if (random_number between 81 &amp; 100) {send request to D;}  
  27.     

 

隨機負載均衡 (Random)

隨機選擇狀態爲UP的Server

Java代碼 
  1. int index = rand.nextInt(serverCount);  
  2. server = upList.get(index);  

 

區域感知輪詢負載均衡(ZoneAware)

區域感知負載均衡內置電路跳閘邏輯,可被配置基於區域同源關係(Zone Affinity,也就是更傾向於選擇發出調用的服務所在的託管區域內,這樣可以降低延遲,節省成本)選擇目標服務實例。它監控每個區域中運行實例的行爲,而且能夠實時的快速丟棄一整個區域。這樣在面對整個區域故障時,幫我們提升了彈性。

Java代碼 
  1. The key metric used to measure the zone condition is Average Active Requests,  
  2. which is aggregated per rest client per zone. It is the  
  3. total outstanding requests in a zone divided by number of available targeted instances (excluding circuit breaker tripped instances).  
  4. This metric is very effective when timeout occurs slowly on a bad zone.  
  5.   
  6. The  LoadBalancer will calculate and examine zone stats of all available zones. If the Average Active Requests for any zone has reached a configured threshold, this zone will be dropped from the active server list. In case more than one zone has reached the threshold, the zone with the most active requests per server will be dropped.  
  7. Once the the worst zone is dropped, a zone will be chosen among the rest with the probability proportional to its number of instances.  
  8. A server will be returned from the chosen zone with a given Rule (A Rule is a load balancing strategy, for example {@link AvailabilityFilteringRule})  
  9. For each request, the steps above will be repeated. That is to say, each zone related load balancing decisions are made at real time with the up-to-date statistics aiding the choice.  

 

具體實現:

Java代碼 
  1. @Override  
  2. protected void setServerListForZones(Map<String, List<Server>> zoneServersMap) {  
  3.     super.setServerListForZones(zoneServersMap);  
  4.     if (balancers == null) {  
  5.         balancers = new ConcurrentHashMap<String, BaseLoadBalancer>();  
  6.     }  
  7.     for (Map.Entry<String, List<Server>> entry: zoneServersMap.entrySet()) {  
  8.         String zone = entry.getKey().toLowerCase();  
  9.         getLoadBalancer(zone).setServersList(entry.getValue());  
  10.     }  
  11.     // check if there is any zone that no longer has a server  
  12.     // and set the list to empty so that the zone related metrics does not  
  13.     // contain stale data  
  14.     for (Map.Entry<String, BaseLoadBalancer> existingLBEntry: balancers.entrySet()) {  
  15.         if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) {  
  16.             existingLBEntry.getValue().setServersList(Collections.emptyList());  
  17.         }  
  18.     }  
  19. }      
  20.       
  21. @Override  
  22. public Server chooseServer(Object key) {  
  23.     if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {  
  24.         logger.debug("Zone aware logic disabled or there is only one zone");  
  25.         return super.chooseServer(key);  
  26.     }  
  27.     Server server = null;  
  28.     try {  
  29.         LoadBalancerStats lbStats = getLoadBalancerStats();  
  30.         Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);  
  31.         logger.debug("Zone snapshots: {}", zoneSnapshot);  
  32.         if (triggeringLoad == null) {  
  33.             triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(  
  34.                     "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold"0.2d);  
  35.         }  
  36.   
  37.         if (triggeringBlackoutPercentage == null) {  
  38.             triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(  
  39.                     "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage"0.99999d);  
  40.         }  
  41.         Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());  
  42.         logger.debug("Available zones: {}", availableZones);  
  43.         if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {  
  44.             String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);  
  45.             logger.debug("Zone chosen: {}", zone);  
  46.             if (zone != null) {  
  47.                 BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);  
  48.                 server = zoneLoadBalancer.chooseServer(key);  
  49.             }  
  50.         }  
  51.     } catch (Throwable e) {  
  52.         logger.error("Unexpected exception when choosing server using zone aware logic", e);  
  53.     }  
  54.     if (server != null) {  
  55.         return server;  
  56.     } else {  
  57.         logger.debug("Zone avoidance logic is not invoked.");  
  58.         return super.chooseServer(key);  
  59.     }  
  60. }  
  61.    
  62. @VisibleForTesting  
  63. BaseLoadBalancer getLoadBalancer(String zone) {  
  64.     zone = zone.toLowerCase();  
  65.     BaseLoadBalancer loadBalancer = balancers.get(zone);  
  66.     if (loadBalancer == null) {  
  67.         // We need to create rule object for load balancer for each zone  
  68.         IRule rule = cloneRule(this.getRule());  
  69.         loadBalancer = new BaseLoadBalancer(this.getName() + "_" + zone, rule, this.getLoadBalancerStats());  
  70.         BaseLoadBalancer prev = balancers.putIfAbsent(zone, loadBalancer);  
  71.         if (prev != null) {  
  72.             loadBalancer = prev;  
  73.         }  
  74.     }   
  75.     return loadBalancer;          
  76. }  
  77.   
  78. private IRule cloneRule(IRule toClone) {  
  79.     IRule rule;  
  80.     if (toClone == null) {  
  81.         rule = new AvailabilityFilteringRule();  
  82.     } else {  
  83.         String ruleClass = toClone.getClass().getName();                  
  84.         try {  
  85. rule = (IRule) ClientFactory.instantiateInstanceWithClientConfig(ruleClass, this.getClientConfig());  
  86.  catch (Exception e) {  
  87. throw new RuntimeException("Unexpected exception creating rule for ZoneAwareLoadBalancer", e);  
  88.   
  89.     }  
  90.     return rule;  
  91. }  
  92.   
  93.      
  94. @Override  
  95. public void setRule(IRule rule) {  
  96.     super.setRule(rule);  
  97.     if (balancers != null) {  
  98.         for (String zone: balancers.keySet()) {  
  99.             balancers.get(zone).setRule(cloneRule(rule));  
  100.         }  
  101.     }  
  102. }  

 

 

Ribbon自帶負載均衡策略比較(轉)

策略名策略聲明策略描述實現說明
BestAvailableRulepublic class BestAvailableRule extends ClientConfigEnabledRoundRobinRule選擇一個最小的併發請求的server逐個考察Server,如果Server被tripped了,則忽略,在選擇其中ActiveRequestsCount最小的server
AvailabilityFilteringRulepublic class AvailabilityFilteringRule extends PredicateBasedRule過濾掉那些因爲一直連接失敗的被標記爲circuit tripped的後端server,並過濾掉那些高併發的的後端server(active connections 超過配置的閾值)使用一個AvailabilityPredicate來包含過濾server的邏輯,其實就就是檢查status裏記錄的各個server的運行狀態
WeightedResponseTimeRulepublic class WeightedResponseTimeRule extends RoundRobinRule根據相應時間分配一個weight,相應時間越長,weight越小,被選中的可能性越低。一個後臺線程定期的從status裏面讀取評價響應時間,爲每個server計算一個weight。Weight的計算也比較簡單responsetime 減去每個server自己平均的responsetime是server的權重。當剛開始運行,沒有形成statas時,使用roubine策略選擇server。
RetryRulepublic class RetryRule extends AbstractLoadBalancerRule對選定的負載均衡策略機上重試機制。在一個配置時間段內當選擇server不成功,則一直嘗試使用subRule的方式選擇一個可用的server
RoundRobinRulepublic class RoundRobinRule extends AbstractLoadBalancerRuleroundRobin方式輪詢選擇server輪詢index,選擇index對應位置的server
RandomRulepublic class RandomRule extends AbstractLoadBalancerRule隨機選擇一個server在index上隨機,選擇index對應位置的server
ZoneAvoidanceRulepublic class ZoneAvoidanceRule extends PredicateBasedRule複合判斷server所在區域的性能和server的可用性選擇server使用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個zone的運行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用於過濾掉連接數過多的Server。

 

Ribbon架構圖

 Ribbon使用舉例:

1. 創建Maven項目:

Xml代碼 
  1. <dependency>  
  2.     <groupId>com.netflix.ribbon</groupId>  
  3.     <artifactId>ribbon-core</artifactId>  
  4.     <version>2.2.0</version>  
  5. </dependency>  
  6. <dependency>  
  7.     <groupId>com.netflix.ribbon</groupId>  
  8.     <artifactId>ribbon-httpclient</artifactId>  
  9.     <version>2.2.0</version>  
  10. </dependency>  

 

2. 配置properties file (sample-client.properties)

Java代碼 
  1. # Max number of retries   
  2. sample-client.ribbon.MaxAutoRetries=1  
  3.   
  4. # Max number of next servers to retry (excluding the first server)  
  5. sample-client.ribbon.MaxAutoRetriesNextServer=1  
  6.   
  7. # Whether all operations can be retried for this client  
  8. sample-client.ribbon.OkToRetryOnAllOperations=true  
  9.   
  10. # Interval to refresh the server list from the source  
  11. sample-client.ribbon.ServerListRefreshInterval=2000  
  12.   
  13. # Connect timeout used by Apache HttpClient  
  14. sample-client.ribbon.ConnectTimeout=3000  
  15.   
  16. # Read timeout used by Apache HttpClient  
  17. sample-client.ribbon.ReadTimeout=3000  
  18.   
  19. # Initial list of servers, can be changed via Archaius dynamic property at runtime  
  20. sample-client.ribbon.listOfServers=www.sohu.com:80,www.163.com:80,www.sina.com.cn:80  
  21.   
  22. sample-client.ribbon.EnablePrimeConnections=true  

 

3. 代碼:

Java代碼 
  1. public static void main( String[] args ) throws Exception {  
  2.     ConfigurationManager.loadPropertiesFromResources("sample-client.properties");  
  3.     System.out.println(ConfigurationManager.getConfigInstance().getProperty("sample-client.ribbon.listOfServers"));  
  4.       
  5.     RestClient client = (RestClient)ClientFactory.getNamedClient("sample-client");  
  6.     HttpRequest request = HttpRequest.newBuilder().uri(new URI("/")).build();  
  7.       
  8.     for(int i = 0; i < 20; i ++) {  
  9.         HttpResponse response = client.executeWithLoadBalancer(request);  
  10.         System.out.println("Status for URI:" + response.getRequestedURI() + " is :" + response.getStatus());  
  11.     }  
  12.       
  13.     ZoneAwareLoadBalancer lb = (ZoneAwareLoadBalancer) client.getLoadBalancer();  
  14.     System.out.println(lb.getLoadBalancerStats());  
  15.       
  16.     ConfigurationManager.getConfigInstance().setProperty("sample-client.ribbon.listOfServers""www.baidu.com:80,www.linkedin.com:80");  
  17.       
  18.     System.out.println("changing servers ...");  
  19.     Thread.sleep(3000);  
  20.       
  21.     for(int i = 0; i < 20; i ++) {  
  22.         HttpResponse response = client.executeWithLoadBalancer(request);  
  23.         System.out.println("Status for URI:" + response.getRequestedURI() + " is :" + response.getStatus());  
  24.     }  
  25.     System.out.println(lb.getLoadBalancerStats());  
  26. }  

 

發佈了45 篇原創文章 · 獲贊 19 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章