SpringCloud(四)Ribbon負載均衡《2》(核心元件IRule及自定義Ribbon負載均衡策略)
1、Ribbon核心元件IRule
1.1、解析IRule自帶的7種演算法
IRule:根據特定演算法從服務列表中選取一個要訪問的服務。
IRule實現類如下:
- RandomRule:隨機,使用Random物件從服務列表中隨機選擇一個服務
- RoundRobinRule:輪訓策略。預設策略,同時也是更高階rules的回退策略
- RetryRule: 輪詢 + 重試。
先使用RoundRobinRule進行服務例項選擇,如果選擇服務例項失敗,則在指定時間不斷進行重試直至找到服務或超時
- WeightedResponseTimeRule: 優先選擇響應時間快
此策略會根據每個例項的平均響應時間,計算出每個服務的權重,響應時間越快,服務權重越重、被選中的概率越高。此類有個DynamicServerWeightTask的定時任務,預設情況下每隔30秒會計算一次各個服務例項的權重。
剛啟動時,如果統計資訊不足,則使用RoundRobinRule策略,等統計資訊足夠,會切換WeightedResponseTimeRule。
- BestAvailableRule: 優先選擇併發請求最小的
先過濾到斷路器處於開啟的服務,然後選擇併發請求最小的服務例項來使用。
剛啟動時,如果統計資訊不足,則使用RoundRobinRule策略,等統計資訊足夠,會切換到BestAvailableRule。
- PredicateBasedRule
抽象類。 PredicateBasedRule是ClientConfigEnabledRoundRobinRule的一個子類,它先通過內部定義的一個過濾器過濾出一部分服務例項清單,然後再採用輪詢的方式從過濾出來的結果中選取一個服務例項
- AvailabilityFilteringRule: (預設實現)
這個負載均衡器規則,會先過濾掉以下服務:
a. 由於多次訪問故障而處於斷路器開啟的服務
b. 併發的連線數量超過閾值
然後對剩餘的服務列表按照RoundRobinRule策略進行訪問
如果RestClient最近3次連線服務例項都失敗,則對應的服務的斷路器開啟。斷路器開啟的狀態預設會持續30s,然後再關閉。如果再次連線又失敗,則斷路器又開啟,並且等待的時間隨著連續失敗的次數,成指數值增加,但是等待的時間不能超過最長的等待時間
- ZoneAvoidanceRule
根據以下的規則過濾服務:
1. 如果一個ZONE不可用,則丟棄這個zone裡的所有服務例項
2. 過濾以下服務例項:”由於多次訪問故障而處於斷路器開啟的服務”和”併發的連線數量超過閾值”,然後再使用輪詢從過濾後的服務列表中選擇一個服務。
1.2、選擇隨機演算法替代預設的輪詢
package com.nari.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.RetryRule;
@Configuration
public class ConfigBean { //boot -->spring applicationContext.xml --- @Configuration配置 ConfigBean = applicationContext.xml
/**
* 通過原始碼可以看到在攔截器中注入了LoadBalancerClient的實現。當一個被@LoadBalanced註解修飾的RestTemplate物件
* 向外發起HTTP請求時,會被LoadBalancerInterceptor類的intercept函式攔截。由於我們在使用RestTemplate時採用了服務名作為host,
* 所以直接從HttpRequest的URI物件中通過getHost()就可以拿到服務名,然後呼叫execute函式根據服務名來選擇例項併發起實際的請求。
*
* 在execute函式的實現中,第一步做的就是通過getServer根據傳入的服務名serviceId去獲取具體的服務例項。
*
* 通過getServer函式的實現原始碼,我們可以看到這裡獲取具體服務例項並沒有使用LoadBalancerClient介面中的choose函式,而是使用Netflix Ribbon
* 自身的ILoadBalancer介面中定義的chooseServer函式。
*
* ILoadBalancer介面:定義了一個客戶端負載均衡器需要的一系列抽象操作。
* addServers:向負載均衡器中維護的例項列表增加服務例項。
* chooseServer:通過某種策略,從負載均衡器中挑選出一個具體的服務例項。
* markServerDown:用來通知和標識負載均衡器中某個具體服務例項已經停止服務,不然負載均衡器在下一次獲取服務例項清單前都會認為服務例項均是正常服務的。
* getReachableServers:獲取當前正常服務的例項列表。
* getAllServers:獲取所有已知的服務例項列表,包括正常服務和停止服務的例項。
* 在該介面中定義了涉及的Server物件定義是一個傳統的服務節點,在該類中儲存了服務端節點的一些元資料資訊,包括host、post以及一些部署資訊等。
*
* 通過RibbonClientConfiguration配置類,可以知道在整合時預設採用了ZoneAwareLoadBalancer來實現負載均衡器。
* 通過ZoneAwareLoadBalancer的chooseServer函式獲取了負載均衡策略分配到的任務服務例項物件Server之後,將其內容包裝成RibbonServer物件(ServiceInstance介面的實現),
*
* 然後使用該物件再回調LoadBalancerInterceptor請求攔截器中的LoadBalancerRequest的apply函式,向一個實際的具體服務例項傳送請求,從而實現
* 一開始以服務名為host的URI請求到host:port形式的實際訪問地址的轉換。
*
*/
@Bean
@LoadBalanced//Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule() {
return new RandomRule(); //達到的目的,用我們重新選擇的隨機演算法替代預設的輪詢。
//return new RetryRule();
}
}
2、自定義Ribbon負載均衡策略
以microservice-consumer-dept-80為例。
2.1、DeptConsumer80_App類上新增自定義負載均衡類註解
主啟動類新增@RibbonClient。
在啟動該微服務的時候就能去載入我們的自定義Ribbon的配置類,從而使配置生效,例如:
@RibbonClient(name="DEPT-PROVIDER", configuration=MySelfRule.class)
2.2、注意配置細節
官方文件明確給出了警告:
這個自定義配置不能放在@CommponentScan所掃描的當前包下以及子包下,否則我們自定義的這個配置類就會被所有的Ribbon客戶端所共享就,也就說我們達不到特殊定製的目的了。
2.4、自定義配置類
2.3、自定義規則深度解析
案例:自定義為每次機器訪問5次。
1、分析Ribbon隨機演算法原始碼:
/*
*
* Copyright 2013 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.netflix.loadbalancer;
import java.util.List;
import java.util.Random;
import com.netflix.client.config.IClientConfig;
/**
* A loadbalacing strategy that randomly distributes traffic amongst existing
* servers.
*
* @author stonse
*
*/
public class RandomRule extends AbstractLoadBalancerRule {
Random rand;
public RandomRule() {
rand = new Random();
}
/**
* 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 = rand.nextInt(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;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
2、參考隨機演算法,修改成我們自己的每次機器5次(My_RandomRule)
package com.myrule;
import java.util.List;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
public class My_RandomRule extends AbstractLoadBalancerRule {
// total = 0 // 當total==5以後,我們指標才能往下走,
// index = 0 // 當前對外提供服務的伺服器地址,
// total需要重新置為零,但是已經達到過一個5次,我們的index = 1
// 分析:我們5次,但是微服務只有8001 8002 8003 三臺,OK?
//
private int total = 0; // 總共被呼叫的次數,目前要求每臺被呼叫5次
private int currentIndex = 0; // 當前提供服務的機器號
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 = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
// server = upList.get(index);
// private int total = 0; // 總共被呼叫的次數,目前要求每臺被呼叫5次
// private int currentIndex = 0; // 當前提供服務的機器號
if(total < 5)
{
server = upList.get(currentIndex);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex >= upList.size())
{
currentIndex = 0;
}
}
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;
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
// TODO Auto-generated method stub
}
}