詳解SpringCloud的負載均衡
一.什么是負載均衡
負載均衡(Load-balance LB),指的是將用戶的請求平攤分配到各個服務器上,從而達到系統(tǒng)的高可用。常見的負載均衡軟件有Nginx、lvs等。
二.負載均衡的簡單分類
1)集中式LB:集中式負載均衡指的是,在服務消費者(client)和服務提供者(provider)之間提供負載均衡設施,通過該設施把消費者(client)的請求通過某種策略轉(zhuǎn)發(fā)給服務提供者(provider),常見的集中式負載均衡是Nginx;
2)進程式LB:將負載均衡的邏輯集成到消費者(client)身上,即消費者從服務注冊中心獲取服務列表,獲知有哪些地址可用,再從這些地址里選出合適的服務器,springCloud的Ribbon就是一個進程式的負載均衡工具。
三.為什么需要做負載均衡
1) 不做負載均衡,可能導致某臺機子負荷太重而掛掉;
2)導致資源浪費,比如某些機子收到太多的請求,肯定會導致某些機子收到很少請求甚至收不到請求,這樣會浪費系統(tǒng)資源。
四.springCloud如何開啟負載均衡
1)在消費者子工程的pom.xml文件的加入相關(guān)依賴(https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon/1.4.7.RELEASE);
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.7.RELEASE</version> </dependency>
消費者需要獲取服務注冊中心的注冊列表信息,把Eureka的依賴包也放進pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.7.RELEASE</version> </dependency>
2)在application.yml里配置服務注冊中心的信息
在該消費者(client)的application.yml里配置Eureka的信息
#配置Eureka eureka: client: #是否注冊自己到服務注冊中心,消費者不用提供服務 register-with-eureka: false service-url: #訪問的url defaultZone: http://localhost:8002/eureka/
3)在消費者啟動類上面加上注解@EnableEurekaClient
@EnableEurekaClient
4)在配置文件的Bean上加上
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
五.IRule
什么是IRule
IRule接口代表負載均衡的策略,它的不同的實現(xiàn)類代表不同的策略,它的四種實現(xiàn)類和它的關(guān)系如下()

說明一下(idea找Irule的方法:ctrl+n 填入IRule進行查找)
1.RandomRule:表示隨機策略,它將從服務清單中隨機選擇一個服務;
public class RandomRule extends AbstractLoadBalancerRule {
public RandomRule() {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
//傳入一個負載均衡器
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;
}
//獲取一個隨機數(shù)
int index = this.chooseRandomInt(serverCount);
//通過這個隨機數(shù)從列表里獲取服務
server = (Server)upList.get(index);
if (server == null) {
//當前線程轉(zhuǎn)為就緒狀態(tài),讓出cpu
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
小結(jié):通過獲取到的所有服務的數(shù)量,以這個數(shù)量為標準獲取一個(0,服務數(shù)量)的數(shù)作為獲取服務實例的下標,從而獲取到服務實例
2.ClientConfigEnabledRoundRobinRule:ClientConfigEnabledRoundRobinRule并沒有實現(xiàn)什么特殊的處理邏輯,但是他的子類可以實現(xiàn)一些高級策略, 當一些本身的策略無法實現(xiàn)某些需求的時候,它也可以做為父類幫助實現(xiàn)某些策略,一般情況下我們都不會使用它;
public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {
//使用“4”中的RoundRobinRule策略
RoundRobinRule roundRobinRule = new RoundRobinRule();
public ClientConfigEnabledRoundRobinRule() {
}
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");
}
}
}
小結(jié):用來作為父類,子類通過實現(xiàn)它來實現(xiàn)一些高級負載均衡策略
1)ClientConfigEnabledRoundRobinRule的子類BestAvailableRule:從該策略的名字就可以知道,bestAvailable的意思是最好獲取的,該策略的作用是獲取到最空閑的服務實例;
public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
//注入負載均衡器,它可以選擇服務實例
private LoadBalancerStats loadBalancerStats;
public BestAvailableRule() {
}
public Server choose(Object key) {
//假如負載均衡器實例為空,采用它父類的負載均衡機制,也就是輪詢機制,因為它的父類采用的就是輪詢機制
if (this.loadBalancerStats == null) {
return super.choose(key);
} else {
//獲取所有服務實例并放入列表里
List<Server> serverList = this.getLoadBalancer().getAllServers();
//并發(fā)量
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);
//淘汰掉已經(jīng)負載的服務實例
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
//獲得當前服務的請求量(并發(fā)量)
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
//找出并發(fā)了最小的服務
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
}
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
if (lb instanceof AbstractLoadBalancer) {
this.loadBalancerStats = ((AbstractLoadBalancer)lb).getLoadBalancerStats();
}
}
}
小結(jié):ClientConfigEnabledRoundRobinRule子類之一,獲取到并發(fā)了最少的服務
2)ClientConfigEnabledRoundRobinRule的另一個子類是PredicateBasedRule:通過源碼可以看出它是一個抽象類,它的抽象方法getPredicate()返回一個AbstractServerPredicate的實例,然后它的choose方法調(diào)用AbstractServerPredicate類的chooseRoundRobinAfterFiltering方法獲取具體的Server實例并返回
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
public PredicateBasedRule() {
}
//獲取AbstractServerPredicate對象
public abstract AbstractServerPredicate getPredicate();
public Server choose(Object key) {
//獲取當前策略的負載均衡器
ILoadBalancer lb = this.getLoadBalancer();
//通過AbstractServerPredicate的子類過濾掉一部分實例(它實現(xiàn)了Predicate)
//以輪詢的方式從過濾后的服務里選擇一個服務
Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
return server.isPresent() ? (Server)server.get() : null;
}
}
再看看它的chooseRoundRobinAfterFiltering()方法是如何實現(xiàn)的
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
List<Server> eligible = this.getEligibleServers(servers, loadBalancerKey);
return eligible.size() == 0 ? Optional.absent() : Optional.of(eligible.get(this.incrementAndGetModulo(eligible.size())));
}
是這樣的,先通過this.getEligibleServers(servers, loadBalancerKey)方法獲取一部分實例,然后判斷這部分實例是否為空,如果不為空則調(diào)用eligible.get(this.incrementAndGetModulo(eligible.size())方法從這部分實例里獲取一個服務,點進this.getEligibleServers看
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
if (loadBalancerKey == null) {
return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
} else {
List<Server> results = Lists.newArrayList();
Iterator var4 = servers.iterator();
while(var4.hasNext()) {
Server server = (Server)var4.next();
//條件滿足
if (this.apply(new PredicateKey(loadBalancerKey, server))) {
//添加到集合里
results.add(server);
}
}
return results;
}
}
getEligibleServers方法是根據(jù)this.apply(new PredicateKey(loadBalancerKey, server))進行過濾的,如果滿足,就添加到返回的集合中。符合什么條件才可以進行過濾呢?可以發(fā)現(xiàn),apply是用this調(diào)用的,this指的是AbstractServerPredicate(它的類對象),但是,該類是個抽象類,該實例是不存在的,需要子類去實現(xiàn),它的子類在這里暫時不是看了,以后有空再深入學習下,它的子類如下,實現(xiàn)哪個子類,就用什么 方式過濾。

再回到chooseRoundRobinAfterFiltering()方法,剛剛說完它通過 getEligibleServers方法過濾并獲取到一部分實例,然后再通過this.incrementAndGetModulo(eligible.size())方法從這部分實例里選擇一個實例返回,該方法的意思是直接返回下一個整數(shù)(索引值),通過該索引值從返回的實例列表中取得Server實例。
private int incrementAndGetModulo(int modulo) {
//當前下標
int current;
//下一個下標
int next;
do {
//獲得當前下標值
current = this.nextIndex.get();
next = (current + 1) % modulo;
} while(!this.nextIndex.compareAndSet(current, next) || current >= modulo);
return current;
}
源碼擼明白了,再來理一下chooseRoundRobinAfterFiltering()的思路:先通過getEligibleServers()方法獲得一部分服務實例,再從這部分服務實例里拿到當前服務實例的下一個服務對象使用。
小結(jié):通過AbstractServerPredicate的chooseRoundRobinAfterFiltering方法進行過濾,獲取備選的服務實例清單,然后用線性輪詢選擇一個實例,是一個抽象類,過濾策略在AbstractServerPredicate的子類中具體實現(xiàn)
3.RetryRule:是對選定的負載均衡策略加上重試機制,即在一個配置好的時間段內(nèi)(默認500ms),當選擇實例不成功,則一直嘗試使用subRule的方式選擇一個可用的實例,在調(diào)用時間到達閥值的時候還沒找到可用服務,則返回空,如果沒有配置負載策略,默認輪詢(即“4”中的輪詢);
先貼上它的源碼
public class RetryRule extends AbstractLoadBalancerRule {
//從這可以看出,默認使用輪詢機制
IRule subRule = new RoundRobinRule();
//500秒的閥值
long maxRetryMillis = 500L;
//無參構(gòu)造函數(shù)
public RetryRule() {
}
//使用輪詢機制
public RetryRule(IRule subRule) {
this.subRule = (IRule)(subRule != null ? subRule : new RoundRobinRule());
}
public RetryRule(IRule subRule, long maxRetryMillis) {
this.subRule = (IRule)(subRule != null ? subRule : new RoundRobinRule());
this.maxRetryMillis = maxRetryMillis > 0L ? maxRetryMillis : 500L;
}
public void setRule(IRule subRule) {
this.subRule = (IRule)(subRule != null ? subRule : new RoundRobinRule());
}
public IRule getRule() {
return this.subRule;
}
//設置最大耗時時間(閥值),最多重試多久
public void setMaxRetryMillis(long maxRetryMillis) {
if (maxRetryMillis > 0L) {
this.maxRetryMillis = maxRetryMillis;
} else {
this.maxRetryMillis = 500L;
}
}
//獲取重試的時間
public long getMaxRetryMillis() {
return this.maxRetryMillis;
}
//設置負載均衡器,用以獲取服務
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
this.subRule.setLoadBalancer(lb);
}
//通過負載均衡器選擇服務
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;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
小結(jié):采用RoundRobinRule的選擇機制,進行反復嘗試,當花費時間超過設置的閾值maxRetryMills時,就返回null
4.RoundRobinRule:輪詢策略,它會從服務清單中按照輪詢的方式依次選擇每個服務實例,它的工作原理是:直接獲取下一個可用實例,如果超過十次沒有獲取到可用的服務實例,則返回空且報出異常信息;
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() {
this.nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
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;
}
}
}
//遞增的形式實現(xiàn)輪詢
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
小結(jié):采用線性輪詢機制循環(huán)依次選擇每個服務實例,直到選擇到一個不為空的服務實例或循環(huán)次數(shù)達到10次
它有個子類WeightedResponseTimeRule,WeightedResponseTimeRule是對RoundRobinRule的優(yōu)化。WeightedResponseTimeRule在其父類的基礎(chǔ)上,增加了定時任務這個功能,通過啟動一個定時任務來計算每個服務的權(quán)重,然后遍歷服務列表選擇服務實例,從而達到更加優(yōu)秀的分配效果。我們這里把這個類分為三部分:定時任務,計算權(quán)值,選擇服務
1)定時任務
//定時任務
void initialize(ILoadBalancer lb) {
if (this.serverWeightTimer != null) {
this.serverWeightTimer.cancel();
}
this.serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-" + this.name, true);
//開啟一個任務,每30秒執(zhí)行一次
this.serverWeightTimer.schedule(new WeightedResponseTimeRule.DynamicServerWeightTask(), 0L, (long)this.serverWeightTaskTimerInterval);
WeightedResponseTimeRule.ServerWeight sw = new WeightedResponseTimeRule.ServerWeight();
sw.maintainWeights();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
WeightedResponseTimeRule.logger.info("Stopping NFLoadBalancer-serverWeightTimer-" + WeightedResponseTimeRule.this.name);
WeightedResponseTimeRule.this.serverWeightTimer.cancel();
}
}));
}
DynamicServerWeightTask()任務如下:
class DynamicServerWeightTask extends TimerTask {
DynamicServerWeightTask() {
}
public void run() {
WeightedResponseTimeRule.ServerWeight serverWeight = WeightedResponseTimeRule.this.new ServerWeight();
try {
//計算權(quán)重
serverWeight.maintainWeights();
} catch (Exception var3) {
WeightedResponseTimeRule.logger.error("Error running DynamicServerWeightTask for {}", WeightedResponseTimeRule.this.name, var3);
}
}
}
小結(jié):調(diào)用initialize方法開啟定時任務,再在任務里計算服務的權(quán)重
2)計算權(quán)重:第一步,先算出所有實例的響應時間;第二步,再根據(jù)所有實例響應時間,算出每個實例的權(quán)重
//用來存儲權(quán)重
private volatile List<Double> accumulatedWeights = new ArrayList();
//內(nèi)部類
class ServerWeight {
ServerWeight() {
}
//該方法用于計算權(quán)重
public void maintainWeights() {
//獲取負載均衡器
ILoadBalancer lb = WeightedResponseTimeRule.this.getLoadBalancer();
if (lb != null) {
if (WeightedResponseTimeRule.this.serverWeightAssignmentInProgress.compareAndSet(false, true)) {
try {
WeightedResponseTimeRule.logger.info("Weight adjusting job started");
AbstractLoadBalancer nlb = (AbstractLoadBalancer)lb;
//獲得每個服務實例的信息
LoadBalancerStats stats = nlb.getLoadBalancerStats();
if (stats != null) {
//實例的響應時間
double totalResponseTime = 0.0D;
ServerStats ss;
//累加所有實例的響應時間
for(Iterator var6 = nlb.getAllServers().iterator(); var6.hasNext(); totalResponseTime += ss.getResponseTimeAvg()) {
Server server = (Server)var6.next();
ss = stats.getSingleServerStat(server);
}
Double weightSoFar = 0.0D;
List<Double> finalWeights = new ArrayList();
Iterator var20 = nlb.getAllServers().iterator();
//計算負載均衡器所有服務的權(quán)重,公式是weightSoFar = weightSoFar + weight-實例平均響應時間
while(var20.hasNext()) {
Server serverx = (Server)var20.next();
ServerStats ssx = stats.getSingleServerStat(serverx);
double weight = totalResponseTime - ssx.getResponseTimeAvg();
weightSoFar = weightSoFar + weight;
finalWeights.add(weightSoFar);
}
WeightedResponseTimeRule.this.setWeights(finalWeights);
return;
}
} catch (Exception var16) {
WeightedResponseTimeRule.logger.error("Error calculating server weights", var16);
return;
} finally {
WeightedResponseTimeRule.this.serverWeightAssignmentInProgress.set(false);
}
}
}
}
}
3)選擇服務
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public 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);
if (maxTotalWeight >= 0.001D && serverCount == currentWeights.size()) {
//生產(chǎn)0到最大權(quán)重值的隨機數(shù)
double randomWeight = this.random.nextDouble() * maxTotalWeight;
int n = 0;
//循環(huán)權(quán)重區(qū)間
for(Iterator var13 = currentWeights.iterator(); var13.hasNext(); ++n) {
//獲取到循環(huán)的數(shù)
Double d = (Double)var13.next();
//假如隨機數(shù)在這個區(qū)間內(nèi),就拿該索引d服務列表獲取對應的實例
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;
}
}
小結(jié):首先生成了一個[0,最大權(quán)重值) 區(qū)間內(nèi)的隨機數(shù),然后遍歷權(quán)重列表,假如當前隨機數(shù)在這個區(qū)間內(nèi),就通過該下標獲得對應的服務。
以上就是詳解SpringCloud的負載均衡的詳細內(nèi)容,更多關(guān)于SpringCloud 負載均衡的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaCV實現(xiàn)讀取視頻信息及自動截取封面圖詳解
javacv可以幫助我們在java中很方便的使用OpenCV以及FFmpeg相關(guān)的功能接口。本文將利用Javacv實現(xiàn)在視頻網(wǎng)站中常見的讀取視頻信息和自動獲取封面圖的功能,感興趣的可以了解一下2022-06-06
Mybatis分頁插件PageHelper的配置和簡單使用方法(推薦)
在使用Java Spring開發(fā)的時候,Mybatis算是對數(shù)據(jù)庫操作的利器了。這篇文章主要介紹了Mybatis分頁插件PageHelper的配置和使用方法,需要的朋友可以參考下2017-12-12
Java RandomAccessFile 指定位置實現(xiàn)文件讀取與寫入
這篇文章主要介紹了Java RandomAccessFile 指定位置實現(xiàn)文件讀取與寫入的相關(guān)資料,需要的朋友可以參考下2017-01-01
SpringBoot與velocity的結(jié)合的示例代碼
本篇文章主要介紹了SpringBoot與velocity的結(jié)合的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
Java+Selenium調(diào)用JavaScript的方法詳解
這篇文章主要為大家講解了java在利用Selenium操作瀏覽器網(wǎng)站時候,有時會需要用的JavaScript的地方,代碼該如何實現(xiàn)呢?快跟隨小編一起學習一下吧2023-01-01
SpringBoot?MP簡單的分頁查詢測試實現(xiàn)步驟分解
好久沒水后端的東西了,最近在做vue項目寫前端的代碼,所以cloud也停進度了,吃完飯突然記得我沒有在博客里寫分頁的東西,雖然項目中用到了,但是沒有拎出來,這里就拎出來看看2023-04-04

