概述
比較經(jīng)典的5種負(fù)載均衡算法:隨機(jī)法猎莲、輪詢法绍弟、最少連接數(shù)法、最快響應(yīng)法益眉、Hash化散列法(包括IP-Hash和參數(shù)值Hash一致性算法)晌柬,另外還可以整合權(quán)重(配置權(quán)重值和JVM預(yù)熱啟動(dòng)加權(quán))
- 隨機(jī)法:實(shí)現(xiàn)比較簡單,也不需要記住狀態(tài)位郭脂,每次隨機(jī)選舉驰唬,實(shí)現(xiàn)負(fù)載均衡的同時(shí)又避免了在選取節(jié)點(diǎn)時(shí)候的復(fù)雜運(yùn)算
- 輪詢法:實(shí)現(xiàn)更公平的負(fù)載均攤鳄袍,但是是基于所有訪問的服務(wù)器處理響應(yīng)時(shí)間差不多的業(yè)務(wù)場景
- 最少連接數(shù)法:實(shí)現(xiàn)了更貼合實(shí)際場景的負(fù)載均攤写半,真正實(shí)現(xiàn)了根據(jù)服務(wù)器的實(shí)際處理能力來分?jǐn)傉?qǐng)求畅卓,避免了慢堆積
- 通過統(tǒng)計(jì)每個(gè)Server的平均響應(yīng)時(shí)間,然后選取最快的server莹弊,可以實(shí)現(xiàn)動(dòng)態(tài)的調(diào)整負(fù)載的均攤涤久。
- Hash化散列法:IP哈希可以解決集群的Session共享問題忍弛,Hash一致性解決的是在非常復(fù)雜的集群模式下响迂,頻繁發(fā)生節(jié)點(diǎn)的新增和刪除的時(shí)候,如何實(shí)現(xiàn)影響最小的請(qǐng)求均攤细疚。
- 權(quán)重值的引入蔗彤,非常有意義的一個(gè)干預(yù)參數(shù),因?yàn)閷?shí)際的業(yè)務(wù)場景,每臺(tái)服務(wù)器的物理環(huán)境所導(dǎo)致的服務(wù)性能各不相同然遏∑锻荆可以和隨機(jī)法、輪訓(xùn)法待侵、最少連接數(shù)法結(jié)合起來用丢早,在和輪詢法結(jié)合起來用時(shí),又有平滑的負(fù)載均攤和不是很平滑的負(fù)載均攤秧倾。
總體來看怨酝,Dubbo提供的負(fù)載均衡的方法最多,但是負(fù)載的實(shí)現(xiàn)問題也多中狂,性能也有待優(yōu)化凫碌。Nginx次之扑毡,但是功能也很豐富胃榕、性能都較好。
Dubbo負(fù)載均衡算法 2.6.2版本
Dubbo提供了功能豐富(bug也多)的4種負(fù)載均衡算法瞄摊,解決Consumer如何從Provider集群間選擇哪個(gè)Provider提供服務(wù)的問題勋又。
另外Dubbo在負(fù)載均衡時(shí)引入了自定義權(quán)重配置、JVM預(yù)熱時(shí)間加權(quán)的規(guī)則進(jìn)來换帜。
四種算法:
Random LoadBalance:按照權(quán)重隨機(jī)分配Provider楔壤,比如隨機(jī)且權(quán)重Node1:Node2= 2:1,那么運(yùn)行30次惯驼,大約有20次在Node1上蹲嚣,10次在Node2上。
RoundRobin LoadBalance:按照權(quán)重輪詢分配祟牲。比如權(quán)重Node1:Node2= 20:10隙畜,那么運(yùn)行30次:前20次里面輪詢Node1和Node2大家各10次,第20次到30次说贝,全部選擇Node1议惰。因?yàn)镈ubbo默認(rèn)是不會(huì)做公約數(shù)的處理,只有完成一個(gè)完整的20+10次運(yùn)算乡恕,才能保證負(fù)載均衡的權(quán)重比例準(zhǔn)確言询,如果Consumer只調(diào)用了20次,那么這里配置的權(quán)重的結(jié)果就是1:1了傲宜,該算法很不平滑运杭。
LeastActive LoadBalance:節(jié)點(diǎn)處理越快分配更多,避免慢節(jié)點(diǎn)堆積函卒,每次篩選Provider的時(shí)候辆憔,都只取Active值最小的節(jié)點(diǎn),如果最小Active值的節(jié)點(diǎn)有多個(gè),則按照權(quán)重隨機(jī)選取躁愿。Provider每獲取到一個(gè)任務(wù)Active值++叛本,每結(jié)束一個(gè)任務(wù)Active值--
ConsistentHash LoadBalance:唯一忽略權(quán)重配置和JVM預(yù)熱的算法。先把所有Provider都分配160個(gè)虛擬節(jié)點(diǎn)彤钟,通過Hash算法来候,全部分散到Hash圓上。每次Consumer調(diào)用時(shí)逸雹,會(huì)根據(jù)參數(shù)值做Hash換算营搅,最后映射到Hash圓上,找到鄰近的虛擬節(jié)點(diǎn)梆砸,最終獲取到提供服務(wù)的Provider转质。但是Dubbo在實(shí)現(xiàn)的時(shí)候違背了Hash一致性的原則,每次Porvider發(fā)生改變的時(shí)候(新增或者剔除)帖世,都會(huì)重新創(chuàng)建一個(gè)Hash圓休蟹,而不是在之前的Hash圓上新增或者剔除不合格的Porvider
AbstractLoadBalance抽象類
這個(gè)類提供了計(jì)算權(quán)重的方法,該方法里面會(huì)根據(jù)JVM啟動(dòng)時(shí)間做加權(quán)日矫,并且直接處理了只有1個(gè)Provider或者沒有Provider的情況赂弓,通過doSelect抽象方法,讓4種負(fù)載均衡實(shí)現(xiàn)類去實(shí)現(xiàn)各自的規(guī)則哪轿。
- 獲取服務(wù)節(jié)點(diǎn)的時(shí)候盈魁,首先調(diào)用的是AbstractLoadBalance的select()方法,該方法對(duì)一些只有1個(gè)Provider或者沒有Provider做了處理窃诉,如果可用Porvider不止1個(gè)杨耙,配置的算法才有意義
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (invokers == null || invokers.size() == 0)
return null;
if (invokers.size() == 1)
return invokers.get(0);
return doSelect(invokers, url, invocation);
}
- 因?yàn)閖vm重啟后有一段預(yù)熱過程,要運(yùn)行一段時(shí)間飘痛,它的性能才能達(dá)到最佳珊膜,所以Dubbo在做負(fù)載均衡計(jì)算Provider的權(quán)重時(shí),引入了warmupTime的加權(quán)的算法敦冬。
在AbstractLoadBalance里面getWeight方法里面:weight= weight * (啟動(dòng)時(shí)間/逾期預(yù)熱時(shí)間)辅搬,warmup默認(rèn)10分鐘,Provider的權(quán)重會(huì)隨著啟動(dòng)時(shí)間的增長脖旱,取的權(quán)重值增加堪遂,到了10分鐘后,才是真正的配置的權(quán)重值
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
int ww = (int) ( (float) uptime / ( (float) warmup / (float) weight ) );
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}
- 備注:2.5.3版本代碼里面有bug萌庆,在求當(dāng)前Provider的運(yùn)行時(shí)間參數(shù)的時(shí)候溶褪,實(shí)際上取的是當(dāng)前Consumer的jvm啟動(dòng)時(shí)間,不過后來修復(fù)了
正確的取值參數(shù)為:REMOTE_TIMESTAMP_KEY践险。2.5.3版本的參數(shù)為 TIMESTAMP_KEY
long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
RandomLoadBalance源碼分析:
- 先求總權(quán)重猿妈,如果權(quán)重有不相等的吹菱,就根據(jù)總權(quán)重為上限生成隨機(jī)值,然后看該隨機(jī)值落在哪個(gè)Node上
- 如果權(quán)重未配置或者所有節(jié)點(diǎn)權(quán)重相同彭则,就按照節(jié)點(diǎn)數(shù)做隨機(jī)取值
public class RandomLoadBalance extends AbstractLoadBalance {
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size(); // 總個(gè)數(shù)
int totalWeight = 0; // 總權(quán)重
boolean sameWeight = true; // 權(quán)重是否都一樣
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight; // 累計(jì)總權(quán)重
if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false; // 計(jì)算所有權(quán)重是否一樣
}
}
if (totalWeight > 0 && ! sameWeight) {
int offset = random.nextInt(totalWeight);
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}
return invokers.get(random.nextInt(length));
}
}
RoundRobinLoadBalance源碼分析
核心思想
核心思想是先均攤鳍刷,小權(quán)重的節(jié)點(diǎn)權(quán)重使用完后被淘汰,大權(quán)重節(jié)點(diǎn)之間均攤俯抖,逐個(gè)淘汰输瓜,最后是最大的一個(gè)節(jié)點(diǎn)獨(dú)占。
比如Node1芬萍、Node2尤揣、Node3 :1:5:10的權(quán)重,那么16次調(diào)用的結(jié)果就是
(Node1柬祠,Node2北戏,Node3)
(Node2,Node3漫蛔,Node2嗜愈,Node3,Node2惩猫,Node3芝硬,Node2蚜点,Node3)
(node3轧房,node3,node3绍绘,node3奶镶,node3)
缺點(diǎn)1:不夠平滑,如果只調(diào)用了10次陪拘,那么權(quán)重類似于1:5:4
缺點(diǎn)2:沒有類似公約數(shù)的處理厂镇,節(jié)點(diǎn)1:節(jié)點(diǎn)2設(shè)置1:10和 100萬:1000萬權(quán)重的耗時(shí)相差極大
缺點(diǎn)3:大權(quán)重值時(shí)循環(huán)次數(shù)太多,第1000萬次的調(diào)用的時(shí)候左刽,會(huì)循環(huán)1000*2次才能判斷出哪個(gè)節(jié)點(diǎn)來處理請(qǐng)求捺信,節(jié)點(diǎn)數(shù)越多,權(quán)重值設(shè)置越大越嚴(yán)重欠痴。
改進(jìn)方案:
- 引入公約數(shù)迄靠,或者按照1-100進(jìn)行折算
- 借鑒Nginx的平滑的權(quán)重加權(quán)輪詢的算法
代碼分析
- 通過一個(gè)全局的sequences根據(jù)key來存儲(chǔ)調(diào)用的值。每個(gè)方法對(duì)應(yīng)一個(gè)key喇辽,value來標(biāo)記該方法是第幾次被調(diào)用掌挚。
- 通過預(yù)熱加權(quán)后的權(quán)重,計(jì)算出所有Provider的最大權(quán)重菩咨、最小權(quán)重吠式、累計(jì)權(quán)重
- 2層for循環(huán)自減陡厘,第一層for循環(huán)的上線時(shí)maxWeight,第二層循環(huán)的次數(shù)是list<invokers>.size
private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
int length = invokers.size(); // Number of invokers
int maxWeight = 0; // The maximum weight
int minWeight = Integer.MAX_VALUE; // The minimum weight
final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
int weightSum = 0;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
minWeight = Math.min(minWeight, weight); // Choose the minimum weight
if (weight > 0) {
invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
weightSum += weight;
}
}
AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
//計(jì)算當(dāng)前方法是第幾次發(fā)起的調(diào)用
int currentSequence = sequence.getAndIncrement();
//如果Providers之間的權(quán)重不相同特占,會(huì)按照權(quán)重來進(jìn)行輪詢Provider
if (maxWeight > 0 && minWeight < maxWeight) {
//關(guān)鍵的兩層循環(huán)糙置,調(diào)用次數(shù)會(huì)按照weightSum的余數(shù)來循環(huán)計(jì)算
int mod = currentSequence % weightSum;
//這里的上限是maxWeight,因?yàn)?里面會(huì)有invokerList.size()次的判斷是目,maxWeight*size >sumWeight
for (int i = 0; i < maxWeight; i++) {
//不管該invoker的權(quán)重是否自減完了罢低,仍然要取值判斷
for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
final Invoker<T> k = each.getKey();
final IntegerWrapper v = each.getValue();
if (mod == 0 && v.getValue() > 0) {
return k;
}
//只有當(dāng)value<0的時(shí)候,該節(jié)點(diǎn)已經(jīng)被淘汰胖笛,沒有資格自減mod
if (v.getValue() > 0) {
v.decrement();
mod--;
}
}
}
}
// Round robin
return invokers.get(currentSequence % length);
}
LeastActiveLoadBalance
- 配置:需要設(shè)置Consumer的filter="activelimit"
<dubbo:reference id="demoService" interface="..." loadbalance="leastactive" filter="activelimit"/>
- 算法邏輯
每個(gè)Provider對(duì)象里面有active值网持,抽選節(jié)點(diǎn)的時(shí)候優(yōu)先判斷Active是否是最小的,再根據(jù)權(quán)重值最隨機(jī)抽選節(jié)點(diǎn)长踊,這樣避免讓調(diào)用堆積在速度相應(yīng)慢的節(jié)點(diǎn)上面
- 如果最小active值的Provider只有1個(gè)功舀,那么就調(diào)用這個(gè)Provider
- 如果最小active值的Provider有多個(gè),則用一個(gè)數(shù)組存起來身弊,在這個(gè)數(shù)組中的Provider按照權(quán)重值做隨機(jī)抽選
- 如果大家的Active值都一樣辟汰,且權(quán)重也都一樣,就隨機(jī)抽選
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
...
//遍歷所有節(jié)點(diǎn)阱佛,獲取幾個(gè)指標(biāo)
//用int[] leastIndexs 保存所有活躍值都最小且相同的Providers的下標(biāo)
//totalWeight 只獲取該數(shù)組的Providers的totalWeight,不是最小的Provider不統(tǒng)計(jì)
// boolean sameWeight 判斷該數(shù)組中的Providers的權(quán)重是否都相同
for (int i = 0; i < length; i++) {
...
if (leastActive == -1 || active < leastActive) { // 發(fā)現(xiàn)更小的活躍數(shù)帖汞,重新開始
leastActive = active; // 記錄最小活躍數(shù)
leastCount = 1; // 重新統(tǒng)計(jì)相同最小活躍數(shù)的個(gè)數(shù)
leastIndexs[0] = i; // 重新記錄最小活躍數(shù)下標(biāo)
totalWeight = weight; // 重新累計(jì)總權(quán)重
firstWeight = weight; // 記錄第一個(gè)權(quán)重
sameWeight = true; // 還原權(quán)重相同標(biāo)識(shí)
} else if (active == leastActive) { // 累計(jì)相同最小的活躍數(shù)
leastIndexs[leastCount ++] = i; // 累計(jì)相同最小活躍數(shù)下標(biāo)
totalWeight += weight; // 累計(jì)總權(quán)重
// 判斷所有權(quán)重是否一樣
if (sameWeight && i > 0 && weight != firstWeight) {
sameWeight = false;
}
}
}
...
//如果最小活躍值數(shù)組不止一個(gè)且大家權(quán)重不相同
//然后按照權(quán)重做隨機(jī)選取
if (! sameWeight && totalWeight > 0) {
// 如果權(quán)重不相同且權(quán)重大于0則按總權(quán)重?cái)?shù)隨機(jī)
int offsetWeight = random.nextInt(totalWeight);
// 并確定隨機(jī)值落在哪個(gè)片斷上
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
}
// 權(quán)重相同或權(quán)重為0則均等隨機(jī)
return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
- 活躍數(shù)統(tǒng)計(jì)ActiveLimitFilter
配置了Filter時(shí),在開始調(diào)用方法時(shí)會(huì)beginCount凑术,active++翩蘸,方法調(diào)用結(jié)束時(shí)會(huì)active--
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
int active = count.getActive();
RpcStatus.beginCount(url, methodName);
Result result = invoker.invoke(invocation);
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;
}
private static void beginCount(RpcStatus status) {
status.active.incrementAndGet();
}
ConsistentHashLoadBalance
- 算法分析:
- 根據(jù)設(shè)置的參數(shù)和參數(shù)值的拼接,來通過Hash一致性算法淮逊,獲取在圓上對(duì)應(yīng)的Provider節(jié)點(diǎn)催首,默認(rèn)配置160個(gè)虛擬節(jié)點(diǎn),只取第一個(gè)參數(shù)的參數(shù)值進(jìn)行散列
- 實(shí)現(xiàn)的效果是:類似優(yōu)化粒度的隨機(jī)取值泄鹏,而且隨機(jī)的好壞和散列的算法關(guān)聯(lián)度很高郎任。目前的API并沒有支持動(dòng)態(tài)的擴(kuò)容和縮容,只是簡單的初始化和選取節(jié)點(diǎn)备籽。
- 該算法不參考權(quán)重值
- 配置
<dubbo:reference id="demoService" interface="com.mor.server.dubbo.service.DemoServer">
<dubbo:method name="sayHello" loadbalance="consistenthash"/>
</dubbo:reference>
- 源碼分析
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
return selector.select(invocation);
}
ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
...
this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
argumentIndex = new int[index.length];
//構(gòu)建一個(gè)160份鏡像的散列的invoker
for (Invoker<T> invoker : invokers) {
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);
}
}
}
哈希一致性算法
假如有2000張圖片舶治,圖片名/圖片路徑 組成,有4臺(tái)機(jī)器來存儲(chǔ)车猬,會(huì)進(jìn)行圖片進(jìn)行CRUD操作霉猛。
- 如果采取隨機(jī)法存儲(chǔ),那么每次get一張圖片诈唬,最差需要遍歷所有數(shù)據(jù)才能獲取到對(duì)應(yīng)的圖片
- 哈希取余法韩脏,根據(jù)圖片名稱 散列成一個(gè)hashcode值,然后和4取余數(shù)铸磅,這樣就可以大致均攤到4臺(tái)服務(wù)器赡矢,而且每次get的時(shí)候杭朱,都只需要查找1個(gè)節(jié)點(diǎn)即可
- 哈希取余法的缺點(diǎn):伸縮性不好,如果新增加一臺(tái)機(jī)器吹散,那么get圖片就會(huì)出錯(cuò)弧械,需要2000萬條數(shù)據(jù),全部重新排序空民,分配到5臺(tái)節(jié)點(diǎn)刃唐。如果考慮縮減一個(gè)節(jié)點(diǎn),也需要全部重新2000萬數(shù)據(jù)重排列
- 哈希一致性算法:把各個(gè)節(jié)點(diǎn)按照serverId進(jìn)行散列界轩,讓其盡可能的均勻的散布在一個(gè)最大值為 2的32次方的圓上画饥,這樣,這個(gè)圓上就有4個(gè)server節(jié)點(diǎn)浊猾,存儲(chǔ)圖片的時(shí)候抖甘,需要按照相同的算法,然后葫慎,分配到比自己值大的最近的一個(gè)節(jié)點(diǎn)衔彻。
- 哈希一致性的優(yōu)點(diǎn):新增和縮容的時(shí)候,只會(huì)影響到下一個(gè)節(jié)點(diǎn)偷办,而不是全部節(jié)點(diǎn)要調(diào)整
-
新增一個(gè)節(jié)點(diǎn)的時(shí)候艰额,比如之前節(jié)點(diǎn)在圓上的散落順序是順序散落,4臺(tái)server求出來的hash值分別是 1椒涯、100柄沮、200、300逐工,新增一臺(tái)機(jī)器的E節(jié)點(diǎn)hash值是250铡溪,那么只需要把落在它的上一個(gè)節(jié)點(diǎn),也就是D節(jié)點(diǎn)的所有數(shù)據(jù)(數(shù)據(jù)的key的區(qū)間是201-300)重新進(jìn)行排列泪喊,然后該圓就仍然有效,不會(huì)影響到 A髓涯、B袒啼、C節(jié)點(diǎn)的數(shù)據(jù)。
image - 如果要縮容纬纪,去除掉D節(jié)點(diǎn)蚓再,只需要取出300節(jié)點(diǎn)的所有數(shù)據(jù),全部分配給A節(jié)點(diǎn)即可包各。
- 哈希一致性算法引入虛擬節(jié)點(diǎn):可以降低分散的不均勻性摘仅,但是會(huì)提升容量調(diào)整時(shí)的復(fù)雜性。
- 加入虛擬數(shù)為2问畅,之前的排列是 A B C D娃属,現(xiàn)在是 A1 B1 A2 C1 C2 D1 B2 D2六荒,如果新增或者刪除A節(jié)點(diǎn),那么也只會(huì)影響到 *虛擬數(shù)的節(jié)點(diǎn)矾端,也就是 B1 和C1節(jié)點(diǎn)掏击,而不會(huì)影響其他 B2 C2 D1 D2節(jié)點(diǎn)
- 虛擬數(shù)越大,容量變化時(shí)需要調(diào)整的數(shù)據(jù)就越多秩铆,但是虛擬數(shù)越大砚亭,數(shù)據(jù)分布的就越均勻
Nginx的負(fù)載均衡算法
結(jié)合第三方插件,可以實(shí)現(xiàn)高可用殴玛,剔除掉出問題的節(jié)點(diǎn)
Nginx目前有5種負(fù)載均衡配置:
round_robin捅膘,加權(quán)輪詢,是默認(rèn)的HTTP負(fù)載均衡算法滚粟,適用于知道機(jī)器的性能篓跛,且默認(rèn)所有的請(qǐng)求對(duì)于服務(wù)器而言,處理的時(shí)間相差不大坦刀。比如我Server1 比Server2的配置要高一倍愧沟,我設(shè)置為2:1的權(quán)重,可以實(shí)現(xiàn)比較科學(xué)的負(fù)載鲤遥。算法實(shí)現(xiàn)上沐寺,簡單的輪詢很簡單,給每個(gè)Server依次編號(hào)盖奈,然后只要記錄一個(gè)調(diào)用index混坞,既可以實(shí)現(xiàn)輪詢。
ip_hash钢坦,IP哈希究孕,可保持會(huì)話
least_conn; 避免了慢堆積,會(huì)取連接數(shù)最小的server提供服務(wù)爹凹,可以避免有些請(qǐng)求耗時(shí)長厨诸,有些耗時(shí)端的情況。根據(jù)實(shí)際的連接數(shù)選擇服務(wù)器禾酱。
fair微酬,需要插件擴(kuò)展該功能,根據(jù)后端服務(wù)器的響應(yīng)時(shí)間來分配請(qǐng)求颤陶,響應(yīng)時(shí)間短的優(yōu)先分配颗管,避免慢堆積。
權(quán)重配置:而且采用的是平滑的負(fù)載均衡算法滓走,比如node1:node2:node3=1:2:5 --> node3垦江,node3,node2搅方,node3比吭,node1绽族,node3,node2梗逮,node3
平滑的輪詢負(fù)載均衡算法(Smooth Weighted Round Robin)
例如server-a:server-b:server-c=4:2:1项秉。選取7次的話,選取的結(jié)果 server-a,server-b,server-a,server-c,server-a,server-b,server-a慷彤。
每次都篩選當(dāng)前權(quán)重值最大的節(jié)點(diǎn)娄蔼,然后對(duì)該節(jié)點(diǎn)權(quán)重值-totalWeight,然后所有的節(jié)點(diǎn)都grow一下底哗,都用當(dāng)前權(quán)重+init權(quán)重
初始化的時(shí)候大家的權(quán)重(4,2,1)岁诉,Server-a的權(quán)重最大,選他干活跋选,干完之后涕癣,a節(jié)點(diǎn)的權(quán)重-最大權(quán)重,a的當(dāng)前權(quán)重為-3前标,然后所有節(jié)點(diǎn)的權(quán)重坠韩,都按照自己的初始權(quán)重自增一次(-3+4,2+2,1+1),也就是(1,4,2)炼列,開始第二輪選取
java代碼實(shí)現(xiàn)
class Server{
int initWeigth;
int printCount=0;
int weigth;
String name;
}
public List<Server> init(){
Server server1=new Server(4,4,"server-a");
totalWeight+=4;
Server server2=new Server(2,2,"server-b");
totalWeight+=2;
Server server3=new Server(1,1,"server-c");
totalWeight+=1;
List<Server> list=new ArrayList();
list.add(server1);
list.add(server2);
list.add(server3);
return list;
}
public Server chooseServer(List<Server> list){
Server choosenServer=list.get(0);
for(int i=1;i<list.size();i++){
Server server=list.get(i);
if(choosenServer.getWeigth()<server.getWeigth()){
choosenServer=server;
}
}
choosenServer.setWeigth(choosenServer.getWeigth()-totalWeight);
return choosenServer;
}
public void grow(List<Server> list){
for(int i=0;i<list.size();i++){
Server server=list.get(i);
server.setWeigth(server.getWeigth()+server.getInitWeigth());
}
}
public static void main(String[] args) {
Test smooth=new Test();
List<Server> list=smooth.init();
for(int i=0;i<7;i++){
Server server= smooth.chooseServer(list);
System.out.println("server-"+server.getName()+" is working");
server.setPrintCount(server.getPrintCount()+1);
smooth.grow(list);
}
System.out.println("--------------------");
for(int i=0;i<list.size();i++){
Server server=list.get(i);
System.out.println(server.getName()+" initWeight-"+server.getInitWeigth()+" totalPrint-"+server.getPrintCount()+"times");
}
}
參考資料
https://blog.csdn.net/revivedsun/article/category/6435629
github中文官網(wǎng)
https://dubbo.gitbooks.io/dubbo-user-book/content/preface/background.html
哈希一致性算法
https://www.cnblogs.com/lpfuture/p/5796398.html
https://blog.csdn.net/bntX2jSQfEHy7/article/details/79549368