上一篇文章單獨介紹了Ribbon框架的使用,及其如何實現客戶端對服務訪問的負載均衡,但只是單獨從Ribbon框架實現,沒有涉及spring cloud。本文着力介紹Ribbon的負載均衡機制,下一篇文章再在spring中繼承Ribbon。
Ribbon負載均衡器
上一篇文章我們已經實現了一個客戶端負載均衡請求web服務的示例。

當時,我們留了一個伏筆,其中的負載均衡的規則策略可以定制,那么本文着重研究策略定制這部分內容,其他的ribbon客戶端的構建和請求方法請參見上一篇文章。
ribbon的負載均衡的策略規則是獨立的,即這部分功能可以獨立於時間的客戶端構造和請求發送。我們需要做的是,實現一個IRule接口的對象,對象里面當然會有一些需要你去覆蓋實現的方法,這些方法中需要用代碼實現你定制的服務器選擇策略,但不包括實際的網絡請求操作。
除了可以自己定制,Ribbon已經為我們設計了幾個現成的規則策略,分別對應於多個不同IRule實現類。我們只需要將這些類對象傳給負載均衡器ILoadBalancer的chooseServer()就可以了。其中,默認情況下,BaseLoadBalancer會選擇輪詢各個server的策略方式,叫做RoundRobinRule。源碼如下:
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnections.PrimeConnectionListener, IClientConfigAware { private static Logger logger = LoggerFactory .getLogger(BaseLoadBalancer.class); private final static IRule DEFAULT_RULE = new RoundRobinRule(); private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy(); private static final String DEFAULT_NAME = "default"; private static final String PREFIX = "LoadBalancer_"; protected IRule rule = DEFAULT_RULE; protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY; protected IPing ping = null; @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>()); @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList<Server>());
好,先說到這里,我們來實驗一下默認的規則策略:
第一步,我們先構建一個負載均衡器,類型BaseLoadBalancer,它是ILoadBalancer的實現類。

負載均衡器,需要配置兩樣東西:
1、服務地址列表——誰來參選?
2、選擇策略規則——怎么選?
第二步,按照服務方的地址端口列表,配置一個Server的List。添加給負載均衡器。
第三步,構造或選擇一個IRule實現類,通過ConfigurationMannager來配置【客戶端名稱】.ribbon.NFLoadBalancerRuleClassName屬性,將配置鍵賦予一個規則類。這里我們不操作,使用默認的。
package com.happybks.invokers; import java.util.ArrayList; import java.util.List; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; public class BalancerApplication { public static void main(String[] args) { ILoadBalancer balancer=new BaseLoadBalancer(); List<Server> servers = new ArrayList<Server>(); servers.add(new Server("127.0.0.1",8091)); servers.add(new Server("127.0.0.1",8092)); balancer.addServers(servers); for(int i=0;i<10;i++) { Server choosedServer = balancer.chooseServer(null); System.out.println(choosedServer); } } }
默認策略,就用BaseLoadBalancer類默認初始化定義好的,chooseServer方法會為我們選擇已有的RoundRobinRule。
(本文出自ochina博主happBKs的博文:https://my.oschina.net/happyBKs/blog/1787825)
源碼如下:
private final static IRule DEFAULT_RULE = new RoundRobinRule();
/* * Get the alive server dedicated to key * * @return the dedicated server */ public Server chooseServer(Object key) { if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } }
我們運行代碼:10次選擇server的結果打印如下:
16:41:10.754 [main] WARN com.netflix.config.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources. 16:41:10.758 [main] INFO com.netflix.config.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath. 16:41:10.766 [main] INFO com.netflix.config.DynamicPropertyFactory - DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@75bd9247 16:41:10.818 [main] DEBUG com.netflix.loadbalancer.BaseLoadBalancer - LoadBalancer [default]: clearing server list (SET op) 16:41:10.819 [main] DEBUG com.netflix.loadbalancer.BaseLoadBalancer - LoadBalancer [default]: addServer [127.0.0.1:8091] 16:41:10.819 [main] DEBUG com.netflix.loadbalancer.BaseLoadBalancer - LoadBalancer [default]: addServer [127.0.0.1:8092] 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091
關於負載均衡相關的四個配置項
這些配置項的前綴是【客戶端名稱】.ribbon
The supported properties are listed below and should be prefixed by <clientName>.ribbon.:
NFLoadBalancerClassName: should implementILoadBalancerNFLoadBalancerRuleClassName: should implementIRuleNFLoadBalancerPingClassName: should implementIPingNIWSServerListClassName: should implementServerListNIWSServerListFilterClassNameshould implementServerListFilter
其中比較重要的是NFLoadBalancerRuleClassName,我們可以通過這個配置項定制需要的負載均衡規則,可以是ribbon提供的原生的幾種規則類,也可以是自己實現的規則類,這些類都實現了IRule接口。
NFLoadBalancerPingClassName用於配置查看服務器是否存活。
NFLoadBalancerRuleClassName指定負載均衡器的實現類。當然,可以設置自己實現的負載均衡器。
NIWSServerListClassName是服務器列表的處理類,用來維護服務器列表的。Ribbon已經實現了動態服務器列表。
NIWSServerListFilterClassName是服務器的攔截類。
負載均衡的兩種配置方法:
一種直接調用ConfigurationManager獲取配置實例,然后設置配置屬性;一種是在application.yml中配置。
自定義策略規則:
下面我們以一個示例來構建一個自己的負載均衡規則。
示例:構建一個60%的概率選擇8091,40%概率選擇8092的規則
我們構建一個實現IRule接口的實現類:
package com.happybks.invokers; import java.util.List; import java.util.Random; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; public class MyProbabilityRandomRule implements IRule { ILoadBalancer balancer = new BaseLoadBalancer(); @Override public Server choose(Object key) { List<Server> allServers = balancer.getAllServers(); Random random = new Random(); final int number = random.nextInt(10); if (number < 7) { return findServer(allServers,8091); } return findServer(allServers,8092); } private Server findServer(List<Server> allServers, int port) { for (Server server : allServers) { if (server.getPort() == port) { return server; } } System.out.println("NULL port="+port); return null; } @Override public void setLoadBalancer(ILoadBalancer lb) { this.balancer = lb; } @Override public ILoadBalancer getLoadBalancer() { return this.balancer; } }
客戶端的請求程序這里我們還是直接在一個普通的main方法中實現
我們首先需要配置請求服務器列表,這個上篇文章已經介紹過。
之后我們對對應的客戶端配置它的ribbon.NFLoadBalancerRuleClassName配置為我們剛才定義的那個實現了IRule的實現類的類名全名,注意:是IRule實現類的全名,一個字符串,不是class。
package com.happybks.invokers; import com.netflix.client.ClientException; import com.netflix.client.ClientFactory; import com.netflix.client.http.HttpRequest; import com.netflix.client.http.HttpResponse; import com.netflix.config.ConfigurationManager; import com.netflix.niws.client.http.RestClient; public class MyRuleClientApplication { public static void main(String[] args) throws Exception { // 1、設置請求的服務器 ConfigurationManager.getConfigInstance().setProperty("happybks-client.ribbon.listOfServers", "localhost:8091,localhost:8092"); // 1 // 2、 配置規則處理類 //本示例略,先默認使用其默認負載均衡策略規則 ConfigurationManager.getConfigInstance().setProperty("happybks-client.ribbon.NFLoadBalancerRuleClassName",MyProbabilityRandomRule.class.getName()); // 3、獲取 REST 請求客戶端 RestClient client = (RestClient) ClientFactory.getNamedClient("happybks-client"); // 4、創建請求實例 HttpRequest request = HttpRequest.newBuilder().uri("/carsInfo/onsale").build(); // 5、發 送 10 次請求到服務器中 for (int i = 0; i < 10; i++) { System.out.println("the "+(i+1)+"th: "); HttpResponse response = client.executeWithLoadBalancer(request); String result = response.getEntity(String.class); System.out.println(result); } } }
響應請求的服務方程序請參見之前的文章,我們不再累述。然后我們看看運行結果:
22:52:27.695 [main] WARN com.netflix.config.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources. 22:52:27.701 [main] INFO com.netflix.config.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath. 22:52:27.834 [main] INFO com.netflix.config.DynamicPropertyFactory - DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@97e1986 22:52:28.787 [main] INFO com.netflix.http4.ConnectionPoolCleaner - Initializing ConnectionPoolCleaner for NFHttpClient:happybks-client 22:52:28.798 [Connection pool clean up thread] DEBUG com.netflix.http4.ConnectionPoolCleaner - Connection pool clean up started for client happybks-client 22:52:28.799 [Connection pool clean up thread] DEBUG com.netflix.http4.MonitoredConnectionManager - Closing expired connections 22:52:28.799 [Connection pool clean up thread] DEBUG com.netflix.http4.NamedConnectionPool - Closing expired connections 22:52:28.799 [Connection pool clean up thread] DEBUG com.netflix.http4.MonitoredConnectionManager - Closing connections idle longer than 30000 MILLISECONDS 22:52:28.800 [Connection pool clean up thread] DEBUG com.netflix.http4.NamedConnectionPool - Closing connections idle longer than 30000 MILLISECONDS 22:52:29.032 [main] WARN com.netflix.client.ClientFactory - Class com.happybks.invokers.MyProbabilityRandomRule neither implements IClientConfigAware nor provides a constructor with IClientConfig as the parameter. Only default constructor will be used. 22:52:29.035 [main] INFO com.netflix.loadbalancer.BaseLoadBalancer - Client: happybks-client instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=happybks-client,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 22:52:29.111 [main] INFO com.netflix.loadbalancer.DynamicServerListLoadBalancer - Using serverListUpdater PollingServerListUpdater 22:52:29.145 [main] WARN com.netflix.client.ClientFactory - Class com.happybks.invokers.MyProbabilityRandomRule neither implements IClientConfigAware nor provides a constructor with IClientConfig as the parameter. Only default constructor will be used. 22:52:29.165 [main] INFO com.netflix.loadbalancer.DynamicServerListLoadBalancer - DynamicServerListLoadBalancer for client happybks-client initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=happybks-client,current list of Servers=[localhost:8091, localhost:8092],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:localhost:8092; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 GMT+08:00 1970; First connection made: Thu Jan 01 08:00:00 GMT+08:00 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] , [Server:localhost:8091; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 GMT+08:00 1970; First connection made: Thu Jan 01 08:00:00 GMT+08:00 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:com.netflix.loadbalancer.ConfigurationBasedServerList@152aa092 22:52:29.165 [main] INFO com.netflix.client.ClientFactory - Client: happybks-client instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=happybks-client,current list of Servers=[localhost:8091, localhost:8092],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:localhost:8092; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 GMT+08:00 1970; First connection made: Thu Jan 01 08:00:00 GMT+08:00 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] , [