Dubbo:負(fù)載均衡實(shí)現(xiàn)解析
dubbo作為分布式遠(yuǎn)程調(diào)用框架,要保證的點(diǎn)很多,比如:服務(wù)注冊(cè)與發(fā)現(xiàn)、故障轉(zhuǎn)移、高性能通信、負(fù)載均衡等等!
負(fù)載均衡的目的是為了特定場(chǎng)景下,能夠?qū)⒄?qǐng)求合理地平分到各服務(wù)實(shí)例上,以便發(fā)揮所有機(jī)器的疊加作用。主要考慮的點(diǎn)如:不要分配請(qǐng)求到掛掉的機(jī)器,性能越好的機(jī)器可以分配更多的請(qǐng)求。。。
一般負(fù)載均衡是借助外部工具,硬件負(fù)載均衡或軟件負(fù)載均衡,如F5/nginx。當(dāng)然了,在當(dāng)前分布式環(huán)境遍地開花的情況下,客戶端的負(fù)載均衡看起來(lái)就更輕量級(jí),顯得不可或缺。
今天我們就來(lái)看看dubbo是如何進(jìn)行負(fù)載均衡的吧!
1. dubbo負(fù)載均衡的作用?
其出發(fā)點(diǎn),自然也就是普通的負(fù)載均衡器的出發(fā)點(diǎn)了。將負(fù)載均衡功能實(shí)現(xiàn)在rpc客戶端側(cè),以便能夠隨時(shí)適應(yīng)外部的環(huán)境變化,更好地發(fā)揮硬件作用。而且客戶端的負(fù)載均衡
天然地就避免了單點(diǎn)問(wèn)題。定制化的自有定制化的優(yōu)勢(shì)和劣勢(shì)。
它可以從配置文件中指定,也可以在管理后臺(tái)進(jìn)行配置修改。
事實(shí)上,它支持 服務(wù)端服務(wù)/方法級(jí)別、客戶端服務(wù)/方法級(jí)別 的負(fù)載均衡配置。
2. dubbo有哪些負(fù)載均衡方式?
即dubbo提供了哪些負(fù)載均衡策略呢?
Dubbo內(nèi)置了4種負(fù)載均衡策略:
RandomLoadBalance:隨機(jī)負(fù)載均衡。隨機(jī)的選擇一個(gè)。是Dubbo的默認(rèn)負(fù)載均衡策略。
RoundRobinLoadBalance:輪詢負(fù)載均衡。輪詢選擇一個(gè)。
LeastActiveLoadBalance:最少活躍調(diào)用數(shù),相同活躍數(shù)的隨機(jī)。活躍數(shù)指調(diào)用前后計(jì)數(shù)差。使慢的 Provider 收到更少請(qǐng)求,因?yàn)樵铰?Provider 的調(diào)用前后計(jì)數(shù)差會(huì)越大。
ConsistentHashLoadBalance:一致性哈希負(fù)載均衡。相同參數(shù)的請(qǐng)求總是落在同一臺(tái)機(jī)器上。
3. 如何配置dubbo負(fù)載均衡策略?
其實(shí)在第一點(diǎn)時(shí)已經(jīng)提過(guò),有多種級(jí)別的配置:服務(wù)端服務(wù)/方法級(jí)別、客戶端服務(wù)/方法級(jí)別; 具體配置如下:
<!-- 服務(wù)端服務(wù)級(jí)別 --><dubbo:service interface="..." loadbalance="roundrobin" /><!-- 客戶端服務(wù)級(jí)別 --><dubbo:reference interface="..." loadbalance="roundrobin" /><!-- 服務(wù)端方法級(jí)別 --><dubbo:service interface="..."><dubbo:method name="hello" loadbalance="roundrobin"/></dubbo:service><!-- 客戶端方法級(jí)別 --><dubbo:reference interface="..."><dubbo:method name="hello" loadbalance="roundrobin"/></dubbo:reference>
多個(gè)配置是有覆蓋關(guān)系的, 配置的優(yōu)先級(jí)是:
1. 客戶端方法級(jí)別配置;(最優(yōu)先)
2. 客戶端接口級(jí)別配置;
3. 服務(wù)端方法級(jí)別配置;
4. 服務(wù)端接口級(jí)別配置;(最后使用)
注意: 雖說(shuō)以上配置有全封閉服務(wù)端配置的,有針對(duì)客戶端配置的,但是,真正使負(fù)載均衡起作用的是,客戶端在發(fā)起調(diào)用的時(shí)候,使用相應(yīng)負(fù)載均衡算法進(jìn)行選擇調(diào)用。(服務(wù)端不可能有這能力)
負(fù)載均衡器的初始化過(guò)程如下:
// 調(diào)用提供者服務(wù)入口// org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke@Overridepublic Result invoke(final Invocation invocation) throws RpcException {checkWhetherDestroyed();// binding attachments into invocation.Map<String, Object> contextAttachments = RpcContext.getContext().getAttachments();if (contextAttachments != null && contextAttachments.size() != 0) {((RpcInvocation) invocation).addAttachments(contextAttachments);}List<Invoker<T>> invokers = list(invocation);// 根據(jù)可用的提供者列表和要調(diào)用的方法,決定選取的負(fù)載均衡器LoadBalance loadbalance = initLoadBalance(invokers, invocation);RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);return doInvoke(invocation, invokers, loadbalance);}// 實(shí)例化一個(gè)負(fù)載均衡器,以備后續(xù)使用// org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#initLoadBalance/*** Init LoadBalance.* <p>* if invokers is not empty, init from the first invoke's url and invocation* if invokes is empty, init a default LoadBalance(RandomLoadBalance)* </p>** @param invokers invokers* @param invocation invocation* @return LoadBalance instance. if not need init, return null.*/protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {if (CollectionUtils.isNotEmpty(invokers)) {// 從provider 的 url 地址中取出 loadbalance=xxx 配置,如果沒(méi)有仍使用 random 策略return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl().getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));} else {// 默認(rèn)是 random 策略return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);}}
所以,事實(shí)上,到最終客戶端決定使用哪個(gè)負(fù)載均衡策略時(shí),只是從請(qǐng)求參數(shù)中取出 loadbalance=xxx 的參數(shù),進(jìn)而決定具體實(shí)例。前面所有的配置,也都是為決定這個(gè)參數(shù)做出的努力。
4. dubbo負(fù)載均衡的實(shí)現(xiàn)?
前面我們看到,dubbo中提供了4種負(fù)載均衡策略,功能也是很明了。那么他們都是如何實(shí)現(xiàn)的呢?
先來(lái)看下其繼承圖:

很明顯,多個(gè)負(fù)載均衡器都有一些共同點(diǎn),所以統(tǒng)一使用 AbstractLoadBalance 進(jìn)行抽象模板方法,差異點(diǎn)由各子算法決定即可。
那么抽象類中,到底有多少公用功能被抽取出來(lái)了呢?到底什么是公用的呢?
// org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance#select@Overridepublic <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {// 沒(méi)有可用的提供者,沒(méi)得選if (CollectionUtils.isEmpty(invokers)) {return null;}// 只有一個(gè)提供者,沒(méi)得均衡的,直接用if (invokers.size() == 1) {return invokers.get(0);}// 然后就是各自均衡算法的實(shí)現(xiàn)了return doSelect(invokers, url, invocation);}
好吧,看起來(lái)是我想多了。抽象方法并沒(méi)有太多的職責(zé),僅做普通判空操作而已。不過(guò)它倒是提供幾個(gè)公用方法被調(diào)用,如 getWeight();
事實(shí)上,模板方法更多地存在于集群的抽象調(diào)用方法中。AbstractClusterInvoker 。
整個(gè)負(fù)載均衡的功能,都被統(tǒng)一放在 cluster 模塊下的 loadbalance 包下,一看即明了。

還是來(lái)看具體的實(shí)現(xiàn)好玩些!
4.1. 隨機(jī)負(fù)載均衡的實(shí)現(xiàn)
/*** This class select one provider from multiple providers randomly.* You can define weights for each provider:* If the weights are all the same then it will use random.nextInt(number of invokers).* If the weights are different then it will use random.nextInt(w1 + w2 + ... + wn)* Note that if the performance of the machine is better than others, you can set a larger weight.* If the performance is not so good, you can set a smaller weight.*/public class RandomLoadBalance extends AbstractLoadBalance {// 標(biāo)識(shí)自身public static final String NAME = "random";/*** Select one invoker between a list using a random criteria* @param invokers List of possible invokers* @param url URL* @param invocation Invocation* @param <T>* @return The selected invoker*/@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// Number of invokersint length = invokers.size();// Every invoker has the same weight?boolean sameWeight = true;// the weight of every invokersint[] weights = new int[length];// the first invoker's weightint firstWeight = getWeight(invokers.get(0), invocation);weights[0] = firstWeight;// The sum of weightsint totalWeight = firstWeight;for (int i = 1; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);// save for later useweights[i] = weight;// 計(jì)算出所有權(quán)重和,以便在進(jìn)行隨機(jī)時(shí)設(shè)定范圍totalWeight += weight;if (sameWeight && weight != firstWeight) {sameWeight = false;}}// 針對(duì)各提供供者權(quán)重不一的情況,則找到第一個(gè)大于隨機(jī)數(shù)的提供者即可if (totalWeight > 0 && !sameWeight) {// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.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.// 如果大家權(quán)重都一樣,則直接以個(gè)數(shù)進(jìn)行隨機(jī)即可得到提供者return invokers.get(ThreadLocalRandom.current().nextInt(length));}}
稍微有點(diǎn)小技巧的就是,針對(duì)不一樣權(quán)重的隨機(jī)實(shí)現(xiàn),以相減的方式找到第一個(gè)為負(fù)的提供者即可。注意,此處計(jì)算各提供者權(quán)重的算法,倒成了難點(diǎn)了有木有。
4.2. 輪詢負(fù)載均衡的實(shí)現(xiàn)
/*** Round robin load balance.*/public class RoundRobinLoadBalance extends AbstractLoadBalance {// 自身標(biāo)識(shí)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;}@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {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();Invoker<T> selectedInvoker = null;WeightedRoundRobin selectedWRR = null;for (Invoker<T> invoker : invokers) {String identifyString = invoker.getUrl().toIdentityString();WeightedRoundRobin weightedRoundRobin = map.get(identifyString);int weight = getWeight(invoker, invocation);if (weightedRoundRobin == null) {weightedRoundRobin = new WeightedRoundRobin();weightedRoundRobin.setWeight(weight);map.putIfAbsent(identifyString, weightedRoundRobin);}if (weight != weightedRoundRobin.getWeight()) {//weight changedweightedRoundRobin.setWeight(weight);}// 自增權(quán)重long cur = weightedRoundRobin.increaseCurrent();weightedRoundRobin.setLastUpdate(now);// 獲取最大權(quán)重項(xiàng),并以對(duì)應(yīng)的 invoker 作為本次選擇的實(shí)例if (cur > maxCurrent) {maxCurrent = cur;selectedInvoker = invoker;selectedWRR = weightedRoundRobin;}totalWeight += weight;}// 當(dāng)invoker數(shù)量發(fā)生變化時(shí),需要能感知到,以便清理 map, 避免內(nèi)存泄露if (!updateLock.get() && invokers.size() != map.size()) {if (updateLock.compareAndSet(false, true)) {try {// copy -> modify -> update reference// 超出計(jì)數(shù)周期,則清空原來(lái)的 WeightedRoundRobinConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<>(map);newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);methodWeightMap.put(key, newMap);} finally {updateLock.set(false);}}}if (selectedInvoker != null) {// 將本次選中的invoker, 權(quán)重置為最低, 以便下次不會(huì)再被選中selectedWRR.sel(totalWeight);return selectedInvoker;}// should not happen herereturn invokers.get(0);}}
依次從最大權(quán)重的invoker開始選擇,然后將選中的項(xiàng)放到最后,輪流選中。使用一個(gè) ConcurrentHashMap 來(lái)保存每個(gè)url的權(quán)重信息,且維護(hù)其活躍性。
4.3. 最少活躍調(diào)用數(shù)負(fù)載均衡的實(shí)現(xiàn)
/*** LeastActiveLoadBalance* <p>* Filter the number of invokers with the least number of active calls and count the weights and quantities of these invokers.* If there is only one invoker, use the invoker directly;* if there are multiple invokers and the weights are not the same, then random according to the total weight;* if there are multiple invokers and the same weight, then randomly called.*/public class LeastActiveLoadBalance extends AbstractLoadBalance {// 自身標(biāo)識(shí)public static final String NAME = "leastactive";@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// Number of invokersint length = invokers.size();// The least active value of all invokersint leastActive = -1;// The number of invokers having the same least active value (leastActive)int leastCount = 0;// The index of invokers having the same least active value (leastActive)int[] leastIndexes = new int[length];// the weight of every invokersint[] weights = new int[length];// The sum of the warmup weights of all the least active invokersint totalWeight = 0;// The weight of the first least active invokerint firstWeight = 0;// Every least active invoker has the same weight value?boolean sameWeight = true;// Filter out all the least active invokersfor (int i = 0; i < length; i++) {Invoker<T> invoker = invokers.get(i);// Get the active number of the invokerint active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();// Get the weight of the invoker's configuration. The default value is 100.int afterWarmup = getWeight(invoker, invocation);// save for later useweights[i] = afterWarmup;// If it is the first invoker or the active number of the invoker is less than the current least active numberif (leastActive == -1 || active < leastActive) {// Reset the active number of the current invoker to the least active numberleastActive = active;// Reset the number of least active invokersleastCount = 1;// Put the first least active invoker first in leastIndexesleastIndexes[0] = i;// Reset totalWeighttotalWeight = afterWarmup;// Record the weight the first least active invokerfirstWeight = afterWarmup;// Each invoke has the same weight (only one invoker here)sameWeight = true;// If current invoker's active value equals with leaseActive, then accumulating.} else if (active == leastActive) {// Record the index of the least active invoker in leastIndexes orderleastIndexes[leastCount++] = i;// Accumulate the total weight of the least active invokertotalWeight += afterWarmup;// If every invoker has the same weight?if (sameWeight && i > 0&& afterWarmup != firstWeight) {sameWeight = false;}}}// Choose an invoker from all the least active invokersif (leastCount == 1) {// 如果只有一個(gè)最小則直接返回// If we got exactly one invoker having the least active value, return this invoker directly.return invokers.get(leastIndexes[0]);}if (!sameWeight && totalWeight > 0) {// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on// totalWeight.// 如果權(quán)重不相同且權(quán)重大于0則按總權(quán)重?cái)?shù)隨機(jī)// 并確定隨機(jī)值落在哪個(gè)片斷上(第一個(gè)相減為負(fù)的值)int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);// Return a invoker based on the random value.for (int i = 0; i < leastCount; i++) {int leastIndex = leastIndexes[i];offsetWeight -= weights[leastIndex];if (offsetWeight < 0) {return invokers.get(leastIndex);}}}// If all invokers have the same weight value or totalWeight=0, return evenly.return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);}}
官方解釋:最少活躍調(diào)用數(shù),相同活躍數(shù)的隨機(jī),活躍數(shù)指調(diào)用前后計(jì)數(shù)差,使慢的機(jī)器收到更少。
額,有點(diǎn)難以理解的樣子。
4.4. 一致性哈希負(fù)載均衡的實(shí)現(xiàn)
/*** ConsistentHashLoadBalance*/public class ConsistentHashLoadBalance extends AbstractLoadBalance {public static final String NAME = "consistenthash";/*** Hash nodes name* 通過(guò) 指定 hash.nodes=0,1,2... 可以自定義參與一致性hash的參數(shù)列表*/public static final String HASH_NODES = "hash.nodes";/*** Hash arguments name*/public static final String HASH_ARGUMENTS = "hash.arguments";// 使用selector 保存某個(gè)固定狀態(tài)時(shí) invoker 的映射關(guān)系private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();@SuppressWarnings("unchecked")@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {String methodName = RpcUtils.getMethodName(invocation);String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;int identityHashCode = System.identityHashCode(invokers);ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);// 第一次進(jìn)入或者 identityHashCode 不相等時(shí)(invoker環(huán)境發(fā)生了變化)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);}private static final class ConsistentHashSelector<T> {private final TreeMap<Long, Invoker<T>> virtualInvokers;private final int replicaNumber;private final int identityHashCode;private final int[] argumentIndex;ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {// 存放虛擬節(jié)點(diǎn)this.virtualInvokers = new TreeMap<Long, Invoker<T>>();this.identityHashCode = identityHashCode;URL url = invokers.get(0).getUrl();// hash.nodes 默認(rèn)是 160this.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) {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);}}}}public Invoker<T> select(Invocation invocation) {// 取出參與一致性hash計(jì)算的參數(shù)信息String key = toKey(invocation.getArguments());byte[] digest = md5(key);// 根據(jù)hash值選取 invokerreturn selectForKey(hash(digest, 0));}private String toKey(Object[] args) {StringBuilder buf = new StringBuilder();// 按照指定的參與hash的參數(shù),調(diào)用 toString() 方法,得到參數(shù)標(biāo)識(shí)信息for (int i : argumentIndex) {if (i >= 0 && i < args.length) {buf.append(args[i]);}}return buf.toString();}private Invoker<T> selectForKey(long hash) {// ceilingEntryMap.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);// 如果沒(méi)有找到,取第一個(gè)值if (entry == null) {entry = virtualInvokers.firstEntry();}return entry.getValue();}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;}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();}}}
主要就是取第多少個(gè)參數(shù),參與hashCode的計(jì)算,然后按照一致性hash算法,定位invoker. 它使用一個(gè) TreeMap 來(lái)保存一致性哈希虛擬節(jié)點(diǎn),hashCode->invoker形式存儲(chǔ),使用 ceilingEntry(hash) 的方式獲取最近的虛擬節(jié)點(diǎn)(天然的一致性hash應(yīng)用)。
值得一提的是,一致性哈希負(fù)載均衡策略是唯一一個(gè)沒(méi)有使用到權(quán)重項(xiàng)的負(fù)載均衡算法。而前面幾種均衡算法,多少都與權(quán)重相關(guān)。該負(fù)載均衡的應(yīng)用場(chǎng)景嘛,還得自己找了。
5. 更多的負(fù)載均衡策略?
dubbo實(shí)現(xiàn)了4種負(fù)載均衡策略,是否就只能是這么多呢?一個(gè)好的設(shè)計(jì)是不會(huì)的,對(duì)擴(kuò)展開放?;赿ubbo的SPI機(jī)制,可以自行實(shí)現(xiàn)任意的負(fù)載均衡策略!
1. 實(shí)現(xiàn) LoadBalance 接口;
2. 添加資源文件 添加文件:src/main/resource/META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.LoadBalance;
demo=my=com.demo.dubbo.DemoLoadBalance3. 設(shè)置負(fù)載均衡策略為自己實(shí)現(xiàn)的demo;
6. 有了負(fù)載均衡就可以保證高可用了嗎?
當(dāng)然不能。負(fù)載均衡只是保證了在發(fā)生調(diào)用的時(shí)候,可以將流量按照既定規(guī)定均攤到各機(jī)器上。然而,均攤是不是最合理的方式卻是不一定的。另外,如果發(fā)生異常,此次負(fù)載均衡就失敗了,從而成功躲過(guò)了高可用。
事實(shí)上,dubbo用三種方式協(xié)同保證了高可用:
1. 負(fù)載均衡
2. 集群容錯(cuò)
3. 服務(wù)路由
以下故事描述摘自官網(wǎng):
這3個(gè)概念容易混淆。他們都描述了怎么從多個(gè) Provider 中選擇一個(gè)來(lái)進(jìn)行調(diào)用。那他們到底有什么區(qū)別呢?下面我來(lái)舉一個(gè)簡(jiǎn)單的例子,把這幾個(gè)概念闡述清楚吧。
有一個(gè)Dubbo的用戶服務(wù),在北京部署了10個(gè),在上海部署了20個(gè)。一個(gè)杭州的服務(wù)消費(fèi)方發(fā)起了一次調(diào)用,然后發(fā)生了以下的事情:
根據(jù)配置的路由規(guī)則,如果杭州發(fā)起的調(diào)用,會(huì)路由到比較近的上海的20個(gè) Provider。
根據(jù)配置的隨機(jī)負(fù)載均衡策略,在20個(gè) Provider 中隨機(jī)選擇了一個(gè)來(lái)調(diào)用,假設(shè)隨機(jī)到了第7個(gè) Provider。
結(jié)果調(diào)用第7個(gè) Provider 失敗了。
根據(jù)配置的Failover集群容錯(cuò)模式,重試其他服務(wù)器。
重試了第13個(gè) Provider,調(diào)用成功。
上面的第1,2,4步驟就分別對(duì)應(yīng)了路由,負(fù)載均衡和集群容錯(cuò)。Dubbo中,先通過(guò)路由,從多個(gè) Provider 中按照路由規(guī)則,選出一個(gè)子集。再根據(jù)負(fù)載均衡從子集中選出一個(gè) Provider 進(jìn)行本次調(diào)用。如果調(diào)用失敗了,根據(jù)集群容錯(cuò)策略,進(jìn)行重試或定時(shí)重發(fā)或快速失敗等??梢钥吹紻ubbo中的路由,負(fù)載均衡和集群容錯(cuò)發(fā)生在一次RPC調(diào)用的不同階段。最先是路由,然后是負(fù)載均衡,最后是集群容錯(cuò)。

騰訊、阿里、滴答后臺(tái)面試題匯總——(含回答)
歷史最全面試面試題!
最新阿里內(nèi)推Java話題
JVM難學(xué)那是因?yàn)槟銢](méi)認(rèn)真閱讀這篇文章?

關(guān)注作者微信公眾號(hào) — 《JAVA爛豬皮》
了解更多java典型知識(shí)以及最新面試寶


看這篇記得給作者點(diǎn)贊+在看哦~~~大家的支持,是源源不斷出文的動(dòng)力
