1.Ribbon介紹
因為微服務是目前互聯網公司比較流行的架構,所以spring就提供了一個頂級框架-spring cloud,來解決我們在開發微服務架構中遇到的各種各樣的問題,今天的主角是spring cloud 框架中集成的組件Ribbon,那么Ribbon能解決什么問題呢,我們來思考下面的問題。
? 微服務架構中的每個服務為了高可用,很大程度上都會進行集群,我們假設現在集群了3個user服務,同時能提供相同的服務,問題來了,我們如何決定調用這3個user服務中的哪一個呢?
? 根據不同分析角度,會有不同的答案,也可以理解為根據不同的情況,我們可以寫不同的算法,來決定到底此時此刻,調用這3個user服務的哪一個,那么,Ribbon就給我們提供了不同的算法,我們可以根據業務場景,調整配置文件,決定到底使用哪個算法,這樣,算法中就會計算出調用哪個user服務了。
2.準備工作
1)我們準備一個eureka注冊中心
2)再準備一個order服務
3)再準備3個相同代碼的user服務,這樣,order服務通過eureka注冊中心,就可以發現user的3個服務
3.Ribbon的常用負載均衡策略
Ribbon是通過IRule的這個接口來選擇3個user服務中的哪個的,但是實際執行的代碼肯定是繼承了這個接口的實現類,所以選擇不同的實現類,就會選擇不同負載均衡策略
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
3.1. RoundRobinRule 輪詢策略
此策略是Ribbon的默認策略,是按照順序,依次對所有的user服務進行訪問。
通過重寫IRule的choose方法,來選擇并返回決定調用的user服務,在下面的源碼中,List allServers = lb.getAllServers(); 獲得了所有的3個user服務實例,int nextServerIndex = this.incrementAndGetModulo(serverCount); 保存了當前調用的user實例的序號,然后就可以按照順序調用下一個user服務了
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
//總服務實例數量
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
debug的圖例:
3.2. RoundRobinRule 隨機策略
就和這個策略的名字一樣,是對user的3個服務的隨機調用,所以不存在規律,如下源碼中int index = this.chooseRandomInt(serverCount); 通過隨機數來選擇下標,所以對user服務的調用是隨機的
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int index = this.chooseRandomInt(serverCount);
server = (Server)upList.get(index);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
debug的圖例:
3.3. WeightedResponseTimeRule響應時間加權重策略
根據user的3個服務的響應時間來分配權重,響應時間越長的服務,權重越低,那么被調用的概率也就越低。相反,響應時間越短的服務,權重越高,被調用的概率也就越高
響應時間加權重策略的實現分為兩步:
- WeightedResponseTimeRule實現類中默認情況下每隔30秒會統計一次每個服務的權重,在此30秒內,用的是輪詢策略
- 30秒之后,會根據統計的結果來分配每個實例的權重,然后根據權重來分配調用次數
extends RoundRobinRulepublic Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
List<Double> currentWeights = this.accumulatedWeights;
if (Thread.interrupted()) {
return null;
}
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int serverIndex = 0;
double maxTotalWeight = currentWeights.size() == 0 ? 0.0D : (Double)currentWeights.get(currentWeights.size() - 1);
//在30秒之內,maxTotalWeight變量會一直是0.0
if (maxTotalWeight >= 0.001D && serverCount == currentWeights.size()) {
double randomWeight = this.random.nextDouble() * maxTotalWeight;
int n = 0;
for(Iterator var13 = currentWeights.iterator(); var13.hasNext(); ++n) {
Double d = (Double)var13.next();
if (d >= randomWeight) {
serverIndex = n;
break;
}
}
server = (Server)allList.get(serverIndex);
} else {
server = super.choose(this.getLoadBalancer(), key);
if (server == null) {
return server;
}
}
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
}
}
return server;
}
}
debug的圖例:
3.4. RetryRule 重試策略
重試策略是指通過輪詢策略選出一個實例,然后去訪問,如果此實例為null或者已經失效,那么會重試其他的實例,answer = this.subRule.choose(key); 會根據輪詢策略選擇一個實例,然后if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline)判斷如果實例為null或者失效,那么會重新選擇
public Server choose(ILoadBalancer lb, Object key) {
long requestTime = System.currentTimeMillis();
long deadline = requestTime + this.maxRetryMillis;
Server answer = null;
answer = this.subRule.choose(key);
if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
while(!Thread.interrupted()) {
answer = this.subRule.choose(key);
if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
break;
}
Thread.yield();
}
task.cancel();
}
return answer != null && answer.isAlive() ? answer : null;
}
3.5. BestAvailableRule 最低并發策略
會根據每個服務實例的并發數量來決定,訪問并發數最少的那個服務,int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); 會獲得當前遍歷的實例的并發數,然后和其他的實例的并發數進行判斷,最終訪問并發量最少的那個實例
public Server choose(Object key) {
if (this.loadBalancerStats == null) {
return super.choose(key);
} else {
List<Server> serverList = this.getLoadBalancer().getAllServers();
int minimalConcurrentConnections = 2147483647;
long currentTime = System.currentTimeMillis();
Server chosen = null;
Iterator var7 = serverList.iterator();
while(var7.hasNext()) { //遍歷所有的實例
Server server = (Server)var7.next();
ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); //判斷并發數,并和已經判斷出的最少的并發數比較
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
}
3.6. AvailabilityFilteringRule 可用過濾策略
此策略會聰明的過濾掉一直失敗并被標記為circuit tripped的user服務,而且會過濾掉那些高并發的user服務
public Server choose(Object key) {
int count = 0;
for(Server server = this.roundRobinRule.choose(key); count++ <= 10; server = this.roundRobinRule.choose(key)) {
//通過predicate來過濾
if (this.predicate.Apply(new PredicateKey(server))) {
return server;
}
}
//過濾掉一些服務之后,會采用輪詢的方式調用剩下的服務
return super.choose(key);
}
3.7. ClientConfigEnabledRoundRobinRule 自定義策略
此策略本身并沒有實現什么特殊的處理邏輯,但是可以通過重置LoadBalancer來達到自定義一些高級策略的目的,可以重寫initWithNiwsConfig和setLoadBalancer
public void initWithNiwsConfig(IClientConfig clientConfig) {
this.roundRobinRule = new RoundRobinRule();
}
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
this.roundRobinRule.setLoadBalancer(lb);
}
public Server choose(Object key) {
if (this.roundRobinRule != null) {
return this.roundRobinRule.choose(key);
} else {
throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
}
}