一. 概述
版本:2.7.8
解決問題
- 當有多個服務提供者時,避免請求集中到其中一個或多個,導致負載過高,服務不可用,需要做一定的負載均衡策略。
- Dubbo提供了多種均衡策略,默認爲random,也就是每次加權隨機調用一臺服務提供者的服務。
二. Dubbo負載均衡模式
Random LoadBalance:加權隨機
- 按照概率設置權重,比較均勻,並且可以動態調節提供者的權重。
RoundRobin LoadBalance:輪詢
- 輪詢,按公約後的權重設置輪詢比率。會存在執行比較慢的服務提供者堆積請求的情況,比如一個機器執行得非常慢,但是機器沒有宕機(如果宕機了,那麼當前機器會從ZooKeeper 的服務列表中刪除)。
- 當很多新的請求到達該機器後,由於之前的請求還沒處理完,會導致新的請求被堆積,久而久之,消費者調用這臺機器上的所有請求都會被阻塞。
LeastActive LoadBalance:最少活躍調用數
- 如果每個提供者的活躍數相同,則隨機選擇一個。
- 在每個服務提供者裏維護着一個活躍數計數器,用來記錄當前同時處理請求的個數,也就是併發處理任務的個數。這個值越小,說明當前服務提供者處理的速度越快或者當前機器的負載比較低,所以路由選擇時就選擇該活躍度最小的機器。
- 如果一個服務提供者處理速度很慢,由於堆積,同時處理的請求就比較多,也就是說活躍調用數較大,處理速度慢。這時,處理速度慢的提供者將收到更少的請求。
ConsistentHash LoadBalance一致性Hash策略
- 一致性Hash,可以保證相同參數的請求總是發到同一提供者,當某一臺提供者機器宕機時,原本發往該提供者的請求,將基於虛擬節點平攤給其他提供者,這樣就不會引起劇烈變動。
三. LoadBalance接口及實現類結構圖
四. 源碼解析
1. 接口LoadBalance
package org.apache.dubbo.rpc.cluster;
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
2. AbstractLoadBalance抽象類
- 使用模板模式執行公共業務
- 各個實現類實現抽象方法(doSelect)
- getWeight:從URL中獲取各個服務提供者的權重
package org.apache.dubbo.rpc.cluster.loadbalance;
public abstract class AbstractLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
if (invokers.size() == 1) {
return invokers.get(0);
}
return doSelect(invokers, url, invocation);
}
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
/**
* Get the weight of the invoker's invocation which takes warmup time into account
* if the uptime is within the warmup time, the weight will be reduce proportionally
*
* @param invoker the invoker
* @param invocation the invocation of this invoker
* @return weight
*/
int getWeight(Invoker<?> invoker, Invocation invocation) {
int weight;
URL url = invoker.getUrl();
// Multiple registry scenario, load balance among multiple registries.
if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
} else {
weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
if (weight > 0) {
long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
long uptime = System.currentTimeMillis() - timestamp;
if (uptime < 0) {
return 1;
}
int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight((int)uptime, warmup, weight);
}
}
}
}
return Math.max(weight, 0);
}
}
3. RandomLoadBalance加權隨機算法
RandomLoadBalance實現類
package org.apache.dubbo.rpc.cluster.loadbalance;
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
/**
* 加權隨機從invokers中選擇一個
* @param invokers invokers集合
* @param url URL
* @param invocation Invocation
* @param <T>
* @return 選擇的invoker
*/
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
int length = invokers.size();
// Every invoker has the same weight?
boolean sameWeight = true;
// the weight of every invokers
int[] weights = new int[length];
// the first invoker's weight
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// The sum of weights
int totalWeight = firstWeight;
for (int i = 1; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// save for later use
weights[i] = weight;
// Sum
totalWeight += weight;
if (sameWeight && weight != firstWeight) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 使用 ThreadLocalRandom.current().nextInt(length)從所有服務提供者裏隨機選擇一個服務提供者進行調用。需要注意的是,這裏沒有使用Random而是使用了ThreadLocalRandom,這是出於性能上的考慮,因 爲Random在高併發下會導致大量線程競爭同一個原子變量,導致大量線程原地自旋,從而浪費CPU資源
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
4. 其它模式(參考源碼)
五. 實現自定義LoadBalance
1. 消費端使用
2. 功能:自定義負載均衡實現
3. 定義實現類
MyLoadBalance實現類
package org.apache.dubbo.rpc.cluster.loadbalance;
public class MyLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 自定義複雜均衡算法,從invokers中選擇一個返回
}
}
引入SPI
- resources新增文件夾META-INF.dubbo
- 新建文件:org.apache.dubbo.rpc.cluster.LoadBalance
- 文件內容:myLoadBalance=org.apache.dubbo.rpc.cluster.loadbalance.MyLoadBalance
六. 使用自定義LoadBalance
public static void main(String[] args) {
// 1.創建服務引用對象實例
ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<>();
// 2.設置應用程序信息
referenceConfig.setApplication(new ApplicationConfig("dubbo-consumer"));
// 3.設置服務註冊中心
referenceConfig.setRegistry(new RegistryConfig("ZKAddress"));
// 4.設置服務接口和超時時間
referenceConfig.setInterface(GreetingService.class);
referenceConfig.setTimeout(5000);
// 5.設置自定義負載均衡
referenceConfig.setLoadbalance("myLoadBalance");
// 6.設置服務分組與版本
referenceConfig.setVersion("1.0.0");
referenceConfig.setGroup("dubbo");
// 7.引用服務
GreetingService greetingService = referenceConfig.get();
}