《 Ribbon核心組件IRule的使用及自定義負載均衡算法 》
前言
在上一篇文章中,主要完成了 《 Rest微服務加入Ribbon負載均衡客戶端組件實現負載均衡 》,本篇將帶領讀者一步一步認識Ribbon的核心插件 “ IRule ”的常用 API 以及自定義算法規則詳細說明,本篇博文涉及的服務模塊包括:
- 修改消費者模塊 “ microservice-consumer-80 ”,新增具體的算法配置類,實現指定規則的調用;
Ribbon核心組件IRule的使用及自定義負載均衡算法
1、IRule 常見的API
這兒先細列哈Ribbon核心組件IRule中常見的幾個算法API,一般情況下,這幾個API已經可以滿足實際的業務需求了,如下:
API | 說明 |
RoundRobinRule | 廣爲人知和最基本的負載平衡策略,即輪詢算法。(是Ribbon默認的負載均衡機制) |
RandomRule | 一種隨機分配現有流量的負載平衡策略,即隨機訪問算法 |
RetryRule | 先按照 RoundRobinRule 的策略訪問服務,如果訪問的服務宕機或者出現異常的情況,則在指定時間內會進行重試,訪問其它可用的服務 |
BestAvailableRule | 首先會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,然後選擇一個併發量最小的服務訪問 |
ZoneAvoidanceRule | 默認規則,複合判斷server所在區域的性能和server的可用性選擇服務器 |
AvailabilityFilteringRule | 首先會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,還有併發的連接數量超過閾值的服務,然後對剩餘的服務列表按照輪詢策略進行訪問 |
WeightedResponseTimeRule | 根據平均響應時間計算所有服務的權重,響應時間越快服務權重越大被選中的概率越高。 剛啓動時如果統計信息不足,則使用RoundRobinRule策略,等統計信息足夠, 會切換到WeightedResponseTimeRule |
接下將列舉前三個API的源碼,只爲展示其表面的算法,當然更多的內部實現細節,請轉到Ribbon官網咯。
RoundRobinRule:
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*
* @author stonse
* @author Nikos Michalakis <[email protected]>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule
{
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule()
{
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb)
{
this();
setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key)
{
if (lb == null)
{
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (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))
{
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null)
{
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe()))
{
return (server);
}
// Next.
server = null;
}
if (count >= 10)
{
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo
* The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo)
{
for (;;)
{
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
}
RandomRule(這個博主在實際的業務中也常常遇到:首先獲取服務列表,再獲取服務總的個數,獲取這個總數內的隨機數,得到的這個隨機數即服務的下標,拿到下標後,再去服務列表中取指定的服務名稱即可,不知道你們有沒有遇到過呢!):
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* A loadbalacing strategy that randomly distributes traffic amongst existing
* servers.
*
* @author stonse
*
*/
public class RandomRule extends AbstractLoadBalancerRule
{
/**
* Randomly choose from all living servers
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key)
{
if (lb == null)
{
return null;
}
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)
{
/*
* No servers. End regardless of pass, because subsequent passes only get more
* restrictive.
*/
return null;
}
int index = chooseRandomInt(serverCount);
server = upList.get(index);
if (server == null)
{
/*
* The only time this should happen is if the server list were somehow trimmed.
* This is a transient condition. Retry after yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive())
{
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount)
{
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
}
RetryRule:
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
/**
* Given that {@link IRule} can be cascaded, this {@link RetryRule} class allows
* adding a retry logic to an existing Rule.
*
* @author stonse
*
*/
public class RetryRule extends AbstractLoadBalancerRule
{
IRule subRule = new RoundRobinRule();
long maxRetryMillis = 500;
public RetryRule()
{
}
public RetryRule(IRule subRule)
{
this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
}
public RetryRule(IRule subRule, long maxRetryMillis)
{
this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
this.maxRetryMillis = (maxRetryMillis > 0) ? maxRetryMillis : 500;
}
public void setRule(IRule subRule)
{
this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
}
public IRule getRule()
{
return subRule;
}
public void setMaxRetryMillis(long maxRetryMillis)
{
if (maxRetryMillis > 0)
{
this.maxRetryMillis = maxRetryMillis;
} else
{
this.maxRetryMillis = 500;
}
}
public long getMaxRetryMillis()
{
return maxRetryMillis;
}
@Override
public void setLoadBalancer(ILoadBalancer lb)
{
super.setLoadBalancer(lb);
subRule.setLoadBalancer(lb);
}
/*
* Loop if necessary. Note that the time CAN be exceeded depending on the
* subRule, because we're not spawning additional threads and returning early.
*/
public Server choose(ILoadBalancer lb, Object key)
{
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
Server answer = null;
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive())) && (System.currentTimeMillis() < deadline))
{
InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
while (!Thread.interrupted())
{
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive())) && (System.currentTimeMillis() < deadline))
{
/* pause and retry hoping it's transient */
Thread.yield();
} else
{
break;
}
}
task.cancel();
}
if ((answer == null) || (!answer.isAlive()))
{
return null;
} else
{
return answer;
}
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
}
2、實現隨機訪問策略,一種隨機分配現有流量的負載平衡策略
修改消費者服務 “ microservice-consumer-80 ” 的 “ ConfigBean ” 類,新增內容:
@Bean
public IRule randomIRule() {
return new RandomRule();
}
完整內容:
package com.huazai.springcloud.cfgbeans;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
*
* <p>
*
* @ClassName : ConfigBean
* </p>
* <p>
* @Description : TODO
* </p>
*
* @Author : HuaZai
* @ContactInformation : [email protected]
* @Date : 2018年05月23日 下午9:01:21
* @Version : V1.0.0
*
* @param
*/
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
@Bean
public IRule randomIRule() {
return new RandomRule();
}
}
測試:
首先啓動 Eureka 集羣,再啓動三臺提供者服務器,最後啓動消費者服務器,並訪問消費者服務器地址,反覆刷新,發現訪問的服務器是沒有規則的,隨機訪問,注意數據庫的變化,如下圖:
3、自定義訪問策略
新建一個類 “ IRuleConfig ”,用於定義基於 Ribbon 的訪問策略,完整內容如下:
package com.huazai.springcloud.cfgbeans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description 自定義訪問策略
* </ul>
* @className IRuleConfig
* @package com.huazai.springcloud.cfgbeans
* @createdTime 2018年05月23日 下午3:57:31
*
* @version V1.0.0
*/
@Configuration
public class IRuleConfig
{
@Bean
public IRule myIRule()
{
// return new RetryRule();
return new RandomRule();
// return new BestAvailableRule();
// return new ZoneAvoidanceRule();
// return new AvailabilityFilteringRule();
// return new WeightedResponseTimeRule();
}
}
修改消費者 “ microservice-consumer-80 ” 的主啓動類,新增註解 “ @RibbonClient ” ,其目的是在啓動該微服務的時候就能去加載自定義 Ribbon 配置類,從而使配置立即生效,完整內容如下:
package com.huazai.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import com.huazai.springcloud.cfgbeans.ConfigBean;
import com.huazai.springcloud.cfgbeans.IRuleConfig;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description
* <li>服務消費者
* </ul>
* @className MicroServiceConsumerApp
* @package com.huazai.springcloud
* @createdTime 2018年05月22日 下午3:47:02
*
* @version V1.0.0
*/
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "MICROSERVICE-PROVIDER", configuration = IRuleConfig.class)
public class MicroServiceConsumerApp
{
public static void main(String[] args)
{
SpringApplication.run(MicroServiceConsumerApp.class, args);
}
}
測試
首先啓動 Eureka 集羣,再啓動三臺提供者服務器,最後啓動消費者服務器,並訪問消費者服務器地址,反覆刷新,發現訪問的服務器是沒有規則的,隨機訪問,注意數據庫的變化,和上面一樣的哈,如下圖:
4、新增需求
需求一:由於公司資源有限(說白了就是降低成本),現在有三臺服務器,配置由高到低排序 1號服務器 > 2號服務器 > 3號服務器,要求:1號服務器承載50%的流量,2號服務器承載30%的流量,3號服務器承載20%的流量,要求自定義Ribbon 算法實現,這個是當年博主遇到的一個很犯賤的問題,對於當時而言很難,因爲什麼都要自己寫,不像現在什麼都封裝成了API,直接調用即可,當然現在一般的互聯網公司很難遇到了,所以這兒的實現就省略咯。。。
需求二:根據客戶那邊對環境的需求,指定方案需要對流量進行絕對的限制,但依然是輪詢機制,例如:1號服務器被調用3次,之後,2號服務器被調用3次,之後,3號服務器被調用3次,因爲也基於輪詢機制的,所以可以直接在上面的 " RoundRobinRule.java " 的源代碼中進行簡單修改即可。
1)新增自定義算法 “ Custom_RandomRule ” 類,並重寫IRule的 “ choose ” 方法和IClientConfigAware的 “ initWithNiwsConfig”方法,完整內容如下:
package com.huazai.springcloud.cfgbeans;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description
* <li>自定義輪詢機制
* </ul>
* @className Custom_RandomRule
* @package com.huazai.springcloud.cfgbeans
* @createdTime 2018年05月24日 下午4:49:51
*
* @version V1.0.0
*/
@SuppressWarnings("unused")
public class Custom_RandomRule extends AbstractLoadBalancerRule
{
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private int total = 0; // 總的被調用次數
private int currentIndex = 0;// 當前提供服務的機器號
private static Logger log = LoggerFactory.getLogger(Custom_RandomRule.class);
public Server choose(ILoadBalancer lb, Object key)
{
if (lb == null)
{
log.warn("no load balancer");
return null;
}
Server server = null;
while (server == null)
{
if (Thread.interrupted())
{
return null;
}
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0))
{
log.warn("No up servers available from load balancer: " + lb);
return null;
}
// 實現每臺服務請求三次的業務邏輯
if (total < 3)
{
server = reachableServers.get(currentIndex);
total++;
} else
{
total = 0;
currentIndex++;
if (currentIndex >= upCount)
{
currentIndex = 0;
}
}
if (server == null)
{
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe()))
{
return (server);
}
// Next.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
// TODO Auto-generated method stub
}
}
修改配置文件 “ IRuleConfig ” ,將 IRule 修改爲自定義的服務調用算法機制,完整內容如下:
package com.huazai.springcloud.cfgbeans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description 自定義訪問策略
* </ul>
* @className IRuleConfig
* @package com.huazai.springcloud.cfgbeans
* @createdTime 2018年05月23日 下午3:57:31
*
* @version V1.0.0
*/
@Configuration
public class IRuleConfig
{
@Bean
public IRule myIRule()
{
return new Custom_RandomRule(); // 自定義算法
}
}
效果圖如下:
GitLab 源碼地址:
項目源碼地址(zip格式的工程包):
好了,關於 Spring Cloud 進階--Ribbon核心組件IRule的使用及自定義負載均衡算法 就寫到這兒了,如果還有什麼疑問或遇到什麼問題歡迎掃碼提問,也可以給我留言哦,我會一一詳細的解答的。
歇後語:“ 共同學習,共同進步 ”,也希望大家多多關注CSND的IT社區。
作 者: | 華 仔 |
聯繫作者: | [email protected] |
來 源: | CSDN (Chinese Software Developer Network) |
原 文: | https://blog.csdn.net/Hello_World_QWP/article/details/88185574 |
版權聲明: | 本文爲博主原創文章,請在轉載時務必註明博文出處! |