
前言
負載均衡是指在集群中,將多個資料請求分散在不同單元上進行執行,主要為了提高系統容錯能力和加強系統對資料的處理能力,
在 Dubbo 中,一次服務的呼叫就是對所有物體域 Invoker 的一次篩選過濾,最終選定具體呼叫的 Invoker,首先在 Directory 中獲取全部 Invoker 串列,通過路由篩選出符合規則的 Invoker,最后再經過負載均衡選出具體的 Invoker,所以 Dubbo 負載均衡機制是決定一次服務呼叫使用哪個提供者的服務,
整體結構
Dubbo 負載均衡的分析入口是 org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance 抽象類,查看這個類繼承關系,

這個被 RandomLoadBalance、LeastActiveLoadBalance、RoundRobinLoadBalance 及 ConsistentHashLoadBalance 類繼承,這四個類是 Dubbo 中提供的四種負載均衡演算法的實作,
| 名稱 | 說明 |
|---|---|
| RandomLoadBalance | 隨機演算法,根據權重設定隨機的概率 |
| LeastActiveLoadBalance | 最少活躍數演算法,指請求數和完成數之差,使執行效率高的服務接收更多請求 |
| RoundRobinLoadBalance | 加權輪訓演算法,根據權重設定輪訓比例 |
| ConsistentHashLoadBalance | Hash 一致性演算法,相同請求引數分配到相同提供者 |
以上則是 Dubbo 提供的四種負載均衡演算法,
從上圖中,看到 AbstractLoadBalance 實作了 LoadBalance 介面,同時是一個 SPI 介面,指定默認實作為 RandomLoadBalance 隨機演算法機制,

抽象類 AbstractLoadBalance 中,實作了負載均衡通用的邏輯,同時給子類宣告了一個抽象方法供子類實作其負載均衡的邏輯,
public abstract class AbstractLoadBalance implements LoadBalance {
/**
*
* @param 運行時間(毫秒)
* @param 預熱時間(毫秒)
* @param 要計算的 Invoker 權重值
*/
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
// 計算預熱時期的權重
int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
// 回傳的權重值區間在: 1 ~ weight
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 校驗 invokers 是否為空
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
// 當到達負載均衡流程時,invokers 中只有一個 Invoker 時,直接回傳該 Invoker
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);
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
// 獲取當前Invoker配置的權重值
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
if (weight > 0) {
// 服務啟動時間
long timestamp = invoker.getUrl().getParameter(REMOTE_TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
// 服務已運行時長
int uptime = (int) (System.currentTimeMillis() - timestamp);
// 服務預熱時間,默認 DEFAULT_WARMUP = 10 * 60 * 1000 ,預熱十分鐘
int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
// 如果服務運行時長小于預熱時長,重新計算出預熱時期的權重
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
// 保證最后回傳的權重值不小于0
return weight >= 0 ? weight : 0;
}
}
在 AbstractLoadBalance 中,getWeight 和 calculateWarmupWeight 方法是獲取和計算當前 Invoker 的權重值,
getWeight 中獲取當前權重值,通過 URL 獲取當前 Invoker 設定的權重,如果當前服務提供者啟動時間小于預熱時間,則會重新計算權重值,對服務進行降權處理,保證服務能在啟動初期不分發設定比例的全部流量,健康運行下去,
calculateWarmupWeight 是重新計算權重值的方法,計算公式為:服務運行時長 / (預熱時長 / 設定的權重值),等價于(服務運行時長 / 預熱時長) * 設定的權重值,同時條件服務運行時長 < 預熱時長,由該公式可知,預熱時長和設定的權重值不變,服務運行時間越長,計算出的值越接近 weight,但不會等于 weight,
在回傳計算后的權重結果中,對小于1和大于設定的權重值進行了處理,當重新計算后的權重小于1時回傳1;處于1和設定的權重值之間時,直接回傳計算后的結果;當權重大于設定的權重值時(因為條件限制,不會出現該類情況),回傳設定的權重值,所以得出結論:重新計算后的權重值為 1 ~ 設定的權重值,運行時間越長,計算出的權重值越接近設定的權重值,
配置方式
服務端
通過 XML 配置方式:
<!-- 服務級別配置 -->
<dubbo:service id="xXXXService" interface="top.ytao.service.XXXXService" loadbalance="負載策略" />
<!-- 方法級別配置 -->
<dubbo:service id="xXXXService" interface="top.ytao.service.XXXXService" >
<dubbo:method name="方法名" loadbalance="負載策略"/>
</dubbo:service>
通過 Properties 配置:
dubbo.service.loadbalance=負載策略
通過注解方式:
@Service(loadbalance = "負載策略")
客戶端
通過 XML 配置方式:
<!-- 服務級別配置 -->
<dubbo:reference id="xXXXService" interface="top.ytao.service.XXXXService" loadbalance="負載策略" />
<!-- 方法級別配置 -->
<dubbo:reference id="xXXXService" interface="top.ytao.service.XXXXService">
<dubbo:method name="方法名" loadbalance="負載策略"/>
</dubbo:reference>
通過 Properties 配置:
dubbo.reference.loadbalance=負載策略
通過注解配置方式:
@Reference(loadbalance = "負載策略")
實作方式也可通過 Dubbo-Admin 管理后臺進行配置,如圖:

隨機演算法
加權隨機演算法負載均衡策略(RandomLoadBalance)是 dubbo 負載均衡的默認實作方式,根據權重分配各個 Invoker 隨機選中的比例,這里的意思是:將到達負載均衡流程的 Invoker 串列中的 權重進行求和,然后求出單個 Invoker 權重在總權重中的占比,亂數就在總權重值的范圍內生成,
如圖,假如當前有192.168.1.10和192.168.1.11兩個負載均衡的服務,權重分別為 4、6 ,則它們的被選中的比例為 2/5、3/5,

當生成亂數為 6 時,就會選中192.168.1.11的服務,
dubbo 中 RandomLoadBalance 的 doSelect 實作代碼:
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Invoker 數量
int length = invokers.size();
// 標識所有 Invoker 的權重是否都一樣
boolean sameWeight = true;
// 用一個陣列保存每個 Invoker 的權重
int[] weights = new int[length];
// 第一個 Invoker 的權重
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// 求和總權重
int totalWeight = firstWeight;
for (int i = 1; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// 保存每個 Invoker 的權重到陣列總
weights[i] = weight;
// 累加求和總權重
totalWeight += weight;
// 如果不是所有 Invoker 的權重都一樣,就給標記上 sameWeight = false
if (sameWeight && weight != firstWeight) {
sameWeight = false;
}
}
// 計算亂數取到的 Invoker,條件是必須總權重大于0,并且每個 Invoker 的權重都不一樣
if (totalWeight > 0 && !sameWeight) {
// 基于 0~總數 范圍內生成亂數
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 計算亂數對應的 Invoker
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
// 如果所有 Invoker 的權重都一樣則隨機從 Invoker 串列中回傳一個
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
以上就是加權隨機策略的實作,這里比較主要關注計算生成的亂數對應的 Invoker,通過遍歷權重陣列,生成的數累減當前權重值,當 offset 為 0 時,就表示 offset 對應當前的 Invoker 服務,
以生成的亂數為 6 為例,遍歷 Invokers 長度:
-
第一輪:offset = 6 - 4 = 2 不滿足 offset < 0,繼續遍歷,
-
第二輪:offset = 2 - 6 = -4 滿足 offset < 0,回傳當前索引對應的 Invoker,因為 offset 回傳負數,表示 offset 落在當前 Invoker 權重的區間里,
加權隨機策略并非一定按照比例被選到,理論上呼叫次數越多,分布的比例越接近權重所占的比例,
最少活躍數演算法
最小活躍數負載均衡策略(LeastActiveLoadBalance)是從最小活躍數的 Invoker 中進行選擇,什么是活躍數呢?活躍數是一個 Invoker 正在處理的請求的數量,當 Invoker 開始處理請求時,會將活躍數加 1,完成請求處理后,將相應 Invoker 的活躍數減 1,找出最小活躍數后,最后根據權重進行選擇最終的 Invoker,如果最后找出的最小活躍數相同,則隨機從中選中一個 Invoker,
public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = "leastactive";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Invoker 數量
int length = invokers.size();
// 所有 Invoker 中的最小活躍值都是 -1
int leastActive = -1;
// 最小活躍值 Invoker 的數量
int leastCount = 0;
// 最小活躍值 Invoker 在 Invokers 串列中對應的下標位置
int[] leastIndexes = new int[length];
// 保存每個 Invoker 的權重
int[] weights = new int[length];
// 總權重
int totalWeight = 0;
// 第一個最小活躍數的權重
int firstWeight = 0;
// 最小活躍數 Invoker 串列的權重是否一樣
boolean sameWeight = true;
// 找出最小活躍數 Invoker 的下標
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// 獲取最小活躍數
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
// 獲取權重
int afterWarmup = getWeight(invoker, invocation);
// 保存權重
weights[i] = afterWarmup;
// 如果當前最小活躍數為-1(-1為最小值)或小于leastActive
if (leastActive == -1 || active < leastActive) {
// 重置最小活躍數
leastActive = active;
// 重置最小活躍數 Invoker 的數量
leastCount = 1;
// 保存當前 Invoker 在 Invokers 串列中的索引至leastIndexes陣列中
leastIndexes[0] = i;
// 重置最小活躍數 invoker 的總權重值
totalWeight = afterWarmup;
// 記錄當前 Invoker 權重為第一個最小活躍數 Invoker 的權重
firstWeight = afterWarmup;
// 因為當前 Invoker 重置為第一個最小活躍數 Invoker ,所以標識所有最小活躍數 Invoker 權重都一樣的值為 true
sameWeight = true;
// 如果當前最小活躍數和已宣告的最小活躍數相等
} else if (active == leastActive) {
// 記錄當前 Invoker 的位置
leastIndexes[leastCount++] = i;
// 累加當前 Invoker 權重到總權重中
totalWeight += afterWarmup;
// 如果當前權重與firstWeight不相等,則將 sameWeight 改為 false
if (sameWeight && i > 0
&& afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
// 如果最小活躍數 Invoker 只有一個,直接回傳該 Invoker
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
if (!sameWeight && totalWeight > 0) {
// 根據權重隨機從最小活躍數 Invoker 串列中選擇一個
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= weights[leastIndex];
if (offsetWeight < 0) {
return invokers.get(leastIndex);
}
}
}
// 如果所有 Invoker 的權重都一樣則隨機從 Invoker 串列中回傳一個
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
}
這段代碼的整個邏輯就是,從 Invokers 串列中篩選出最小活躍數的 Invoker,然后類似加權隨機演算法策略方式選擇最終的 Invoker 服務,
輪詢演算法
加權輪詢負載均衡策略(RoundRobinLoadBalance)是基于權重來決定輪詢的比例,普通輪詢會將請求均勻的分布在每個節點,但不能很好調節不同性能服務器的請求處理,所以加權負載均衡來根據權重在輪詢機制中分配相對應的請求比例給每臺服務器,
public class RoundRobinLoadBalance extends AbstractLoadBalance {
public static final String NAME = "roundrobin";
private static final int RECYCLE_PERIOD = 60000;
protected static class WeightedRoundRobin {
private int weight;
private AtomicLong current = new AtomicLong(0);
private long lastUpdate;
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
current.set(0);
}
public long increaseCurrent() {
return current.addAndGet(weight);
}
public void sel(int total) {
current.addAndGet(-1 * total);
}
public long getLastUpdate() {
return lastUpdate;
}
public void setLastUpdate(long lastUpdate) {
this.lastUpdate = lastUpdate;
}
}
private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();
private AtomicBoolean updateLock = new AtomicBoolean();
/**
* get invoker addr list cached for specified invocation
* <p>
* <b>for unit test only</b>
*
* @param invokers
* @param invocation
* @return
*/
protected <T> Collection<String> getInvokerAddrList(List<Invoker<T>> invokers, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);
if (map != null) {
return map.keySet();
}
return null;
}
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// key 為 介面名+方法名
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
// 查看快取中是否存在相應服務介面的資訊,如果沒有則新添加一個元素到快取中
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
if (map == null) {
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());
map = methodWeightMap.get(key);
}
// 總權重
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
// 當前時間戳
long now = System.currentTimeMillis();
// 最大 current 的 Invoker
Invoker<T> selectedInvoker = null;
// 保存選中的 WeightedRoundRobin 物件
WeightedRoundRobin selectedWRR = null;
// 遍歷 Invokers 串列
for (Invoker<T> invoker : invokers) {
// 從快取中獲取 WeightedRoundRobin 物件
String identifyString = invoker.getUrl().toIdentityString();
WeightedRoundRobin weightedRoundRobin = map.get(identifyString);
// 獲取當前 Invoker 物件
int weight = getWeight(invoker, invocation);
// 如果當前 Invoker 沒有對應的 WeightedRoundRobin 物件,則新增一個
if (weightedRoundRobin == null) {
weightedRoundRobin = new WeightedRoundRobin();
weightedRoundRobin.setWeight(weight);
map.putIfAbsent(identifyString, weightedRoundRobin);
}
// 如果當前 Invoker 權重不等于對應的 WeightedRoundRobin 物件中的權重,則重新設定當前權重到對應的 WeightedRoundRobin 物件中
if (weight != weightedRoundRobin.getWeight()) {
weightedRoundRobin.setWeight(weight);
}
// 累加權重到 current 中
long cur = weightedRoundRobin.increaseCurrent();
// 設定 weightedRoundRobin 物件最后更新時間
weightedRoundRobin.setLastUpdate(now);
// 最大 current 的 Invoker,并賦值給相應的變數
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = invoker;
selectedWRR = weightedRoundRobin;
}
// 累加權重到總權重中
totalWeight += weight;
}
// 如果 Invokers 串列中的數量不等于快取map中的數量
if (!updateLock.get() && invokers.size() != map.size()) {
if (updateLock.compareAndSet(false, true)) {
try {
// 拷貝 map 到 newMap 中
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<String, WeightedRoundRobin>();
newMap.putAll(map);
// newMap 轉化為 Iterator
Iterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();
// 回圈洗掉超過設定時長沒更新的快取
while (it.hasNext()) {
Entry<String, WeightedRoundRobin> item = it.next();
if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {
it.remove();
}
}
// 將當前newMap服務快取中
methodWeightMap.put(key, newMap);
} finally {
updateLock.set(false);
}
}
}
// 如果存在被選中的 Invoker
if (selectedInvoker != null) {
// 計算 current = current - totalWeight
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
// 正常情況這里不會到達
return invokers.get(0);
}
}
上面選中 Invoker 邏輯為:每個 Invoker 都有一個 current 值,初始值為自身權重,在每個 Invoker 中current = current + weight,遍歷完 Invoker 后,current 最大的那個 Invoker 就是本次選中的 Invoker,選中 Invoker 后,將本次 current 值計算current = current - totalWeight,
以上面192.168.1.10和192.168.1.11兩個負載均衡的服務,權重分別為 4、6 ,基于選中前current = current + weight、選中后current = current - totalWeight計算公式得出如下
| 請求次數 | 選中前 current | 選中后 current | 被選中服務 |
|---|---|---|---|
| 1 | [4, 6] | [4, -4] | 192.168.1.11 |
| 2 | [8, 2] | [-2, 2] | 192.168.1.10 |
| 3 | [2, 8] | [2, -2] | 192.168.1.11 |
| 4 | [6, 4] | [-4, 4] | 192.168.1.10 |
| 5 | [0, 10] | [0, 0] | 192.168.1.11 |
一致性 Hash 演算法
一致性 Hash 負載均衡策略(ConsistentHashLoadBalance)是讓引數相同的請求分配到同一機器上,把每個服務節點分布在一個環上,請求也分布在環形中,以請求在環上的位置,順時針尋找換上第一個服務節點,如圖所示:

同時,為避免請求散列不均勻,dubbo 中會將每個 Invoker 再虛擬多個節點出來,使得請求呼叫更加均勻,
一致性 Hash 修改配置如下:
<!-- dubbo 默認只對第一個引數進行 hash 標識,指定hash引數 -->
<dubbo:parameter key="hash.arguments" value="https://www.cnblogs.com/ytao-blog/p/1" />
<!-- 虛擬節點數量 -->
<dubbo:parameter key="hash.nodes" value="https://www.cnblogs.com/ytao-blog/p/200" />
一致性 Hash 實作如下:
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
public static final String NAME = "consistenthash";
/**
* Hash nodes name
*/
public static final String HASH_NODES = "hash.nodes";
/**
* Hash arguments name
*/
public static final String HASH_ARGUMENTS = "hash.arguments";
private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();
@SuppressWarnings("unchecked")
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 獲取請求的方法名
String methodName = RpcUtils.getMethodName(invocation);
// key = 介面名+方法名
String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
// invokers 的 hashcode
int identityHashCode = System.identityHashCode(invokers);
// 查看快取中是否存在對應 key 的資料,或 Invokers 串列是否有過變動,如果沒有,則新添加到快取中,并且回傳負載均衡得出的 Invoker
ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
if (selector == null || selector.identityHashCode != identityHashCode) {
selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
selector = (ConsistentHashSelector<T>) selectors.get(key);
}
return selector.select(invocation);
}
// ConsistentHashSelector class ...
}
doSelect 中主要實作快取檢查和 Invokers 變動檢查,一致性 hash 負載均衡的實作在這個內部類 ConsistentHashSelector 中實作,
private static final class ConsistentHashSelector<T> {
// 存盤虛擬節點
private final TreeMap<Long, Invoker<T>> virtualInvokers;
// 節點數
private final int replicaNumber;
// invoker 串列的 hashcode,用來判斷 Invoker 串列是否變化
private final int identityHashCode;
// 請求中用來作Hash映射的引數的索引
private final int[] argumentIndex;
ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
this.identityHashCode = identityHashCode;
URL url = invokers.get(0).getUrl();
// 獲取節點數
this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160);
// 獲取配置中的 引數索引
String[] index = COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, HASH_ARGUMENTS, "0"));
argumentIndex = new int[index.length];
for (int i = 0; i < index.length; i++) {
argumentIndex[i] = Integer.parseInt(index[i]);
}
for (Invoker<T> invoker : invokers) {
// 獲取 Invoker 中的地址,包括埠號
String address = invoker.getUrl().getAddress();
// 創建虛擬節點
for (int i = 0; i < replicaNumber / 4; i++) {
byte[] digest = md5(address + i);
for (int h = 0; h < 4; h++) {
long m = hash(digest, h);
virtualInvokers.put(m, invoker);
}
}
}
}
// 找出 Invoker
public Invoker<T> select(Invocation invocation) {
// 將引數轉為字串
String key = toKey(invocation.getArguments());
// 字串引數轉換為 md5
byte[] digest = md5(key);
// 根據 md5 找出 Invoker
return selectForKey(hash(digest, 0));
}
// 將引數拼接成字串
private String toKey(Object[] args) {
StringBuilder buf = new StringBuilder();
for (int i : argumentIndex) {
if (i >= 0 && i < args.length) {
buf.append(args[i]);
}
}
return buf.toString();
}
// 利用 md5 匹配到對應的 Invoker
private Invoker<T> selectForKey(long hash) {
// 找到第一個大于當前 hash 的 Invoker
Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
if (entry == null) {
entry = virtualInvokers.firstEntry();
}
return entry.getValue();
}
// hash 運算
private long hash(byte[] digest, int number) {
return (((long) (digest[3 + number * 4] & 0xFF) << 24)
| ((long) (digest[2 + number * 4] & 0xFF) << 16)
| ((long) (digest[1 + number * 4] & 0xFF) << 8)
| (digest[number * 4] & 0xFF))
& 0xFFFFFFFFL;
}
// md5 運算
private byte[] md5(String value) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e.getMessage(), e);
}
md5.reset();
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
md5.update(bytes);
return md5.digest();
}
}
一致 hash 實作程序就是先創建好虛擬節點,虛擬節點保存在 TreeMap 中,TreeMap 的 key 為配置的引數先進行 md5 運算,然后將 md5 值進行 hash 運算,TreeMap 的 value 為被選中的 Invoker,
最后請求時,計算引數的 hash 值,去從 TreeMap 中獲取 Invoker,
總結
Dubbo 負載均衡的實作,技巧上還是比較優雅,可以多多學習其編碼思維,在研究其代碼時,需要仔細研究其實作原理,否則比較難懂其思想,
推薦閱讀
《Dubbo 路由機制的實作》
《Dubbo 擴展點加載機制:從 Java SPI 到 Dubbo SPI》
《Dubbo之服務消費原理》
《Dubbo之服務暴露》
《你必須會的 JDK 動態代理和 CGLIB 動態代理》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/152371.html
標籤:Java
下一篇:Listener
