Анализ исходного кода Dubbo (9) Алгоритм балансировки нагрузки

Java

предисловие

Когда в нашем приложении Dubbo есть несколько поставщиков услуг, как потребитель услуг выбирает, к какому из них обратиться? Это включает в себя алгоритмы балансировки нагрузки.

LoadBalance означает балансировку нагрузки на китайском языке, и его обязанность заключается в «балансировке» сетевых запросов или других форм нагрузки на разные машины. Избегайте ситуаций, когда одни серверы в кластере перегружены, а другие простаивают. Благодаря балансировке нагрузки каждый сервер может получить нагрузку, соответствующую его собственной вычислительной мощности. Разгружая высоконагруженные серверы, он также может не тратить ресурсы впустую, убивая двух зайцев одним выстрелом.

В Dubbo предусмотрено 4 реализации балансировки нагрузки:

  • RandomLoadBalance на основе взвешенного случайного алгоритма

  • LeastActiveLoadBalance на основе алгоритма наименее активных вызовов

  • ConsistentHashLoadBalance на основе согласованности хэша

  • RoundRobinLoadBalance на основе взвешенного алгоритма циклического перебора

1. Баланс нагрузки

В Dubbo все классы реализации балансировки нагрузки наследуются от абстрактных классов.AbstractLoadBalance, класс реализуетLoadBalanceинтерфейс.

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
    /**
     * select one invoker in list.
     *
     * @param invokers   invokers.
     * @param url        refer url
     * @param invocation invocation.
     * @return selected invoker.
     */
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

Как видите, аннотация SPI интерфейса указывает реализацию по умолчанию.RandomLoadBalance, но не волнуйтесь, давайте сначала рассмотрим логику абстрактного класса.

1. Выберите услугу

Давайте сначала рассмотрим выбор метода входа для балансировки нагрузки, который имеет относительно простую логику. Проверьте, не пуст ли поставщик услуг; если в списке вызывающих есть только один Invoker, верните его напрямую без балансировки нагрузки; если Invoker несколько, вызовите подкласс для реализации балансировки нагрузки.

public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
	if (invokers == null || invokers.isEmpty())
		return null;
	//如果只有一个服务提供者,直接返回,无需负载均衡
	if (invokers.size() == 1)
		return invokers.get(0);
	return doSelect(invokers, url, invocation);
}

2. Набрать вес

Здесь есть две логики: одна — получить сконфигурированное значение веса, которое по умолчанию равно 100, а другая — пересчитать вес в соответствии со временем работы сервиса.

protected int getWeight(Invoker<?> invoker, Invocation invocation) {
	//获取权重值,默认为100
	int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), "weight",100);
	if (weight > 0) {
		//服务提供者启动时间戳
		long timestamp = invoker.getUrl().getParameter("remote.timestamp", 0L);
		if (timestamp > 0L) {
			//当前时间-启动时间=运行时长
			int uptime = (int) (System.currentTimeMillis() - timestamp);
			//获取服务预热时间 默认10分钟 
			int warmup = invoker.getUrl().getParameter("warmup", 600000 );
			//如果服务运行时间小于预热时间,即服务启动未到达10分钟
			if (uptime > 0 && uptime < warmup) {
				//重新计算服务权重
				weight = calculateWarmupWeight(uptime, warmup, weight);
			}
		}
	}
	return weight;
}

Как и в приведенном выше коде, получите значение веса услуги. Затем определите, меньше ли время запуска службы, чем время прогрева службы, а затем пересчитайте вес. Время прогрева службы по умолчанию составляет 10 минут. Общий процесс выглядит следующим образом:

  • Получите настроенное значение веса, по умолчанию 100
  • Получить отметку времени, когда служба была запущена
  • текущее время - время запуска службы = время работы службы
  • Получите время прогрева службы, по умолчанию 10 минут
  • Определите, меньше ли время работы службы, чем время прогрева, и пересчитайте вес, если условие выполнено

Пересчет веса на самом деле является процессом уменьшения веса.

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);
}

Код выглядит простым, но его не очень легко понять. Мы можем заменить приведенный выше код следующей формулой:uptime / warmup) * weight, то есть процент прогресса * вес.

Допустим, мы установили вес 100 и время разминки 10 минут. Так:

время выполнения формула вес после расчета
1 минута 1/10 * 100 10
2 минуты 2/10 * 100 20
5 минут 5/10 * 100 50
10 минут 10/10 * 100 100

Видно, что веса снижаются до того, как наступает время прогрева службы. Зачем Даббо это делать?

Он в основном используется для обеспечения того, чтобы, когда время работы службы меньше, чем время прогрева службы, служба была понижена, чтобы избежать высокой нагрузки службы в начале запуска. Прогрев службы — это оптимизация, аналогичная прогреву JVM. Основная цель состоит в том, чтобы позволить службе работать с низким энергопотреблением в течение некоторого периода времени после ее запуска, чтобы ее эффективность можно было постепенно повышать до наилучшего состояния.

2. Алгоритм случайного взвешивания

RandomLoadBalance — это конкретная реализация взвешенного случайного алгоритма и реализация по умолчанию алгоритма балансировки нагрузки в Dubbo. Здесь нам нужно сначала разбить сервер по весу, например:

Предположим, есть три сервера: [A, B, C] Их соответствующие веса: [1, 3, 6], с общим весом 10

Тогда мы можем получить:

интервал владелец сервера
0-1 A
1-4 B
4-10 C

Остальное просто, мы получаем общий вес totalWeight, затем генерируем случайное число между [0-totalWeight] и вычисляем, в какой интервал попадет случайное число.

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, 
				URL url, Invocation invocation) {
	
	//服务提供者列表数量
	int length = invokers.size(); 
	//总权重
	int totalWeight = 0; 
	//是否具有相同的权重
	boolean sameWeight = true; 
	
	//循环服务列表,计算总权重和检测每个服务权重是否相同
	for (int i = 0; i < length; i++) {
	
		//获取单个服务的权重值
		int weight = getWeight(invokers.get(i), invocation);
		//累加 计算总权重
		totalWeight += weight; 
		//校验服务权重是否相同
		if (sameWeight && i > 0
				&& weight != getWeight(invokers.get(i - 1), invocation)) {
			sameWeight = false;
		}
	}
	if (totalWeight > 0 && !sameWeight) {
		//获取[0-totalWeight]之间的随机数
		int offset = random.nextInt(totalWeight);
		//计算随机数处于哪个区间,返回对应invoker
		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));
}

Давайте используем приведенный выше пример, чтобы обобщить процесс приведенного выше кода:

  1. Получить количество поставщиков услуг = 3
  2. Накопить, рассчитать общий вес = 10
  3. Проверьте, равны ли веса услуг. 1, 3, 6 по порядку
  4. Получите прямое случайное число от 0 до 10, предполагая, что смещение = 6
  5. Первый цикл, 6-=1>0, условие не установлено, смещение = 5
  6. Второй цикл, 5-=3>0, условие не выполняется, смещение = 2
  7. Третий цикл, 2-=6

Наконец, если все веса одинаковы, напрямую случайным образом возвращайте вызывающий вызов службы.

3. Алгоритм минимального активного числа

Алгоритм балансировки минимальной активной нагрузки соответствует LeastActiveLoadBalance. Чем меньше количество активных вызовов, тем эффективнее поставщик услуг и больше запросов может быть обработано в единицу времени, при этом запросы должны быть в первую очередь переданы поставщику услуг.

Dubbo назначит актив каждому Invoker поставщика услуг, который представляет собой количество активов. Операция автоинкремента выполняется перед вызовом, а операция автодекремента выполняется после завершения вызова. Одни услуги обрабатываются быстро, другие медленно. Чем быстрее число активных элементов, тем меньшее количество активных элементов будет выделено в первую очередь.

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

	//服务提供者列表数量
	int length = invokers.size(); 
	//默认的最小活跃数值
	int leastActive = -1;
	//最小活跃数invoker数量
	int leastCount = 0; 
	
	//最小活跃数invoker索引
	int[] leastIndexs = new int[length];
	//总权重
	int totalWeight = 0; 
	//第一个Invoker权重值 用于比较invoker直接的权重是否相同
	int firstWeight = 0;
	boolean sameWeight = true;
	//循环比对Invoker的活跃数大小
	for (int i = 0; i < length; i++) {
		//获取当前Invoker对象
		Invoker<T> invoker = invokers.get(i);
		//获取活跃数大小
		int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); 
		//获取权重值
		int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), "weight", 100); 
		
		//对比发现更小的活跃数,重置
		if (leastActive == -1 || active < leastActive) {
			//更新最小活跃数
			leastActive = active; 
			//更新最小活跃数 数量为1
			leastCount = 1;
			//记录坐标
			leastIndexs[0] = i; 
			totalWeight = weight; 
			firstWeight = weight; 
			sameWeight = true;
			
		//如果当前Invoker的活跃数 与 最小活跃数相等
		} else if (active == leastActive) { 
			leastIndexs[leastCount++] = i;
			totalWeight += weight;
			if (sameWeight && i > 0
					&& weight != firstWeight) {
				sameWeight = false;
			}
		}
	}
	//如果只有一个Invoker具有最小活跃数,直接返回即可 
	if (leastCount == 1) {
		return invokers.get(leastIndexs[0]);
	}
	//多个Invoker具体相同的最小活跃数,但权重不同,就走权重的逻辑
	if (!sameWeight && totalWeight > 0) {
		int offsetWeight = random.nextInt(totalWeight);
		for (int i = 0; i < leastCount; i++) {
			int leastIndex = leastIndexs[i];
			offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
			if (offsetWeight <= 0)
				return invokers.get(leastIndex);
		}
	}
	//从leastIndexs中随机获取一个返回
	return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}

Приведенный выше код разделен на две части. Первый — определить Invoker с минимальным активным номером путем сравнения, второй — определить Invoker по весу. Подведем итог пошагово:

  • Определить переменные - минимальный размер активного числа, число, массив, значение веса

  • Прокрутите массив Invokers, чтобы получить размер и вес текущего количества активных Invokers.

  • Сравните активное число текущего Invoker, чтобы увидеть, меньше ли оно, чем предыдущее; если условие истинно, сбросьте минимальное активное число; если они равны, суммируйте значение веса и оцените, является ли вес одинаковым

  • Сравнение завершено, если есть только одно минимальное активное число, оно возвращается непосредственно в Invoker.

  • Если несколько инвокеров имеют одинаковый активный номер, но разные веса, просто следуйте логике весов

  • Если ни одно из двух вышеперечисленных условий не выполняется, получите случайное число в пределах минимального диапазона активных номеров и вернитесь в Invoker.

Увидев это, вы задумались над другим вопросом, а именно, где активный номер увеличивается и уменьшается?

Речь идет о фильтре Даббо, который включает в себяActiveLimitFilterэтот класс. В этом классе есть такой фрагмент кода:

//触发active自增操作
RpcStatus.beginCount(url, methodName);
Result result = invoker.invoke(invocation);
//触发active自减操作
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;

Наконец, этот фильтр необходимо добавить вручную, в файле конфигурации мы определяем его следующим образом:<dubbo:consumer filter="activelimit">

4. Алгоритм согласованности хэша

Алгоритм последовательного хэширования был разработан Каргером и его сотрудниками из Массачусетского технологического института в 1997 году. Изначально этот алгоритм был предложен для балансировки нагрузки крупномасштабных систем кэширования.

Его принцип примерно таков:

Сначала построить длину 232Целочисленное кольцо (консистентное хеш-кольцо), а затем в соответствии с именем узла значение хэша (распределенное в 0-232-1) Поместите серверные узлы в это хеш-кольцо. Наконец, значение хэша вычисляется в соответствии со значением ключа данных, и узел сервера, ближайший к значению хэша значения ключа, ищется по часовой стрелке в кольце хэшей, чтобы завершить поиск сопоставления ключа с сервером.

Если вы не знаете об алгоритме последовательного хеширования, вам необходимо самостоятельно дополнить соответствующие знания.

В Dubbo виртуальные узлы введены для решения проблемы перекоса данных. Схема выглядит следующим образом:

Здесь узлы одного цвета принадлежат одному поставщику услуг, например Invoker1-1, Invoker1-2, ..., Invoker1-160. То есть каждый Invoker создаст в общей сложности 160 виртуальных узлов, а общая длина Hash-кольца составит 160*количество узлов.

давайте сначала посмотримConsistentHashLoadBalance.doSelectвыполнить.

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
	
	//请求类名+方法名
	//比如:com.viewscenes.netsupervisor.service.InfoUserService.sayHello
	String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
	//对当前的invokers进行hash取值
	int identityHashCode = System.identityHashCode(invokers);
	ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
	
	//如果ConsistentHashSelector为空 或者 新的invokers hashCode取值不同
	//说明服务提供者列表可能发生变化,需要获取创建ConsistentHashSelector
	if (selector == null || selector.identityHashCode != identityHashCode) {
		selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
		selector = (ConsistentHashSelector<T>) selectors.get(key);
	}
	//选择Invoker
	return selector.select(invocation);
}

Приведенный выше код в основном предназначен для получения ConsistentHashSelector, а затем вызова его метода для выбора возвращаемого Invoker. Еще один момент, который следует отметить, заключается в том, что если список поставщиков услуг изменится, их два значения HashCode будут другими, и объект ConsistentHashSelector будет воссоздан. Суть вопроса в этот момент заключается в том, как создается ConsistentHashSelector?

1. Создайте ConsistentHashSelector

Этот класс имеет несколько свойств, давайте сначала посмотрим.

private static final class ConsistentHashSelector<T> {
	//使用 TreeMap 存储 Invoker 虚拟节点
	private final TreeMap<Long, Invoker<T>> virtualInvokers;
	//虚拟节点数量,默认160
	private final int replicaNumber;
	//服务提供者列表的Hash值
	private final int identityHashCode;
	//参数下标
	private final int[] argumentIndex;
}

Посмотрите на его метод построения, в основном, чтобы создать виртуальный узел Invoker и поместить его в virtualInvokers.

ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {	
	//初始化TreeMap
	this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
	//当前invokers列表的Hash值
	this.identityHashCode = identityHashCode;
	URL url = invokers.get(0).getUrl();
	//获取虚拟节点数,默认为160
	this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
	//默认对第一个参数进行hash取值
	String[] index = Constants.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]);
	}
	//循环创建虚拟节点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);
			}
		}
	}
}

Смысл приведенного выше кода заключается в создании виртуального узла Invoker.

Сначала получите адрес коммуникационного сервера, например192.168.1.1:20880; Тогда сначалаaddress + iВыполните операцию MD5, чтобы получить массив, а затем выполните 4 хэш-операции над некоторыми байтами этого массива, чтобы получить четыре разных положительных целых числа длинного типа; Наконец, отношение сопоставления между хэшем и вызывающей стороной хранится в TreeMap.

На этом этапе, если у нас есть 3 поставщика услуг, подсчитайте, сколько виртуальных узлов будет всего. Ба! Калькуляторы не допускаются, пожалуйста, делайте вычисления в уме. Правильно, 480. Их отношение отображения следующее:

2. Выберите

После создания ConsistentHashSelector пришло время вызвать его методы для выбора Invoker.

public Invoker<T> select(Invocation invocation) {
	String key = toKey(invocation.getArguments());
	byte[] digest = md5(key);
	return selectForKey(hash(digest, 0));
}

Приведенный выше код очень прост, мы разделим его на две части.

2.1 Параметры преобразования

Получите список параметров, а затем передайтеtoKeyметод преобразования в строку. Это кажется простым, но здесь скрыт еще один уровень логики. Он будет принимать только первый параметр, мы видимtoKeyметод.

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 над строковым ключом, а затем получите длинное положительное целое число с помощью хэша. Как правило, этот шаг заключается в преобразовании первого значения параметра в списке параметров в положительное длинное целое число. Тогда одно и то же значение параметра получит одно и то же значение хеш-функции, поэтомуНа логику балансировки нагрузки здесь будет влиять только значение параметра, а запросы с одинаковым значением параметра будут назначаться одному и тому же поставщику услуг.

2.2. Подтвердить

После вычисления хэш-значения все становится просто. Согласно принципу согласованного алгоритма хэширования,在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点. Реализовано на Dubbo, находится вvirtualInvokersВ этом TreeMap верните частичные данные, ключ которых больше или равен значению Hash, а затем возьмите первый.

private Invoker<T> selectForKey(long hash) {
	Map.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();
	if (entry == null) {
		entry = virtualInvokers.firstEntry();
	}
	return entry.getValue();
}  

5. Алгоритм взвешенного опроса

Говоря об опросе, мы все это знаем. Он должен прийти один за другим в соответствии с порядком, беспристрастностью и абсолютной справедливостью. Если купленные серверы имеют примерно одинаковую производительность, то наиболее целесообразным, простым и эффективным является опрос.

Так что же такое взвешенный опрос?

Если производительность нашего сервера отличается, простым опросом это сделать непросто. Небольшой сервер сказал, что не выдерживает такой нагрузки, и запросил понижение версии.

Предположим, у нас есть серверы [A, B, C] с весами [1, 2, 3] соответственно. При наличии 6 запросов результаты их балансировки нагрузки следующие: [A, B, C, B, C, C].

Класс, соответствующий этому алгоритму, естьRoundRobinLoadBalance, прежде чем начать, рассмотрим два его свойства.

sequences

Это номер, который записывает номер вызова службы, этоAtomicPositiveIntegerпример. в соответствии с全限定类名 + 方法名чтобы получить или создать, если он пуст.

AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
	sequences.putIfAbsent(key, new AtomicPositiveInteger());
	sequence = sequences.get(key);
}

Затем перед каждым обращением к сервису выполняйте операцию автоинкремента, чтобы получить текущий номер.int currentSequence = sequence.getAndIncrement();

IntegerWrapper

Это тоже очень просто, это класс-обертка типа int, в основном метод самодекремента.

private static final class IntegerWrapper {
	private int value;

	public IntegerWrapper(int value) {
		this.value = value;
	}
	public int getValue() {
		return value;
	}
	public void setValue(int value) {
		this.value = value;
	}
	public void decrement() {
		this.value--;
	}
}

Тогда давайте посмотрим на метод doSelect, для удобства анализа разберем его.

1. Набрать вес

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();
	//最大权重
	int maxWeight = 0;
	//最小权重
	int minWeight = Integer.MAX_VALUE;
	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) {
			//将Invoker对象和对应的权重大小IntegerWrapper放入Map中
			invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
			weightSum += weight;
		}
	}
}

Приведенный выше код в основном предназначен для получения веса Invoker и расчета общего веса. Основное внимание уделяетсяinvokerToWeightMapПоместите объект Invoker и его соответствующий вес вIntegerWrapper.

2. Получите номер службы поддержки

перед каждым звонкомsequenceЧтобы получить номер вызова службы с помощью автоинкремента, следует отметить, что его ключом является полное имя класса + имя метода.

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

	//全限定类型+方法名
	String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
	//.....	
	AtomicPositiveInteger sequence = sequences.get(key);
	if (sequence == null) {
		sequences.putIfAbsent(key, new AtomicPositiveInteger());
		sequence = sequences.get(key);
	}
	int currentSequence = sequence.getAndIncrement();
}

3. Вес

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
	
	//......
	
	//调用编号
	int currentSequence = sequence.getAndIncrement();
	
	if (maxWeight > 0 && minWeight < maxWeight) {
		//使用调用编号对权重总和进行取余操作
		int mod = currentSequence % weightSum;
		
		//遍历 最大权重大小 次数
		for (int i = 0; i < maxWeight; i++) {
			//遍历invokerToWeightMap 
			for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
				//当前Invoker
				final Invoker<T> k = each.getKey();
				//当前Invoker对应的权重大小
				final IntegerWrapper v = each.getValue();
				
				//取余等于0 且 当前权重大于0 返回Invoker
				if (mod == 0 && v.getValue() > 0) {
					return k;
				}
				//如果取余不等于0 且 当前权重大于0 对权重和取余数--
				if (v.getValue() > 0) {
					v.decrement();
					mod--;
				}
			}
		}
	}
}

Вышеприведенный код представляет собой процесс получения Invoker на основе опроса веса.На самом деле он немного непонятен и его трудно понять, просто взглянув на код. Но если мы отладим его, мы сможем понять его лучше. Смоделируем запущенный процесс на приведенном выше примере, в данный момент есть серверы [A, B, C], веса [1, 2, 3], общий вес 6, максимальный вес 3.

mod = 0: Если условие выполнено, то вернуться на сервер А напрямую

mod = 1: условие может быть выполнено только после того, как оно будет уменьшено на 1, а затем вернуться на сервер B.

mod = 2: Условие может быть выполнено только после того, как оно будет уменьшено в 2 раза, в это время вернитесь на сервер C

mod = 3: Условие может быть выполнено только после уменьшения в 3 раза.После уменьшения вес сервера равен [0, 1, 2], а затем он возвращается на сервер B

mod = 4: Условие может быть выполнено только после уменьшения в 4 раза.После уменьшения вес сервера равен [0, 0, 1], и возвращается сервер C.

mod = 5: вес остался только у сервера C, вернуть C.

Таких вызовов 6, получается результат [A, B, C, B, C, C].

Когда делается 7-й вызов, номер вызова в это время равен 6, и общий вес также равен 6; мод равен 0, и он начинается снова.

4. Опрос

Наконец, если у всех одинаковый вес, нечего сказать, просто опрос.

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

	//.....
	//轮询
	return invokers.get(currentSequence % length);
}