содержание
- содержание
- задний план
- Метод распределения
- Принцип последовательного хеширования
- Java реализует согласованный клиент кеша алгоритма хеширования
задний план
Поскольку бизнес-система становится все больше и больше, нам нужно больше кэширования для доступа к API, и использование Redis — хорошее решение.
Однако производительности одного Redis недостаточно, и рано или поздно он уйдет в кластер, так как же нам эффективно использовать кластер Redis для кэширования?
Когда приходит запрос, как мы решаем, на каком сервере Redis кэшировать содержимое этого запроса?Давайте сделаем это один за другим.
Метод распределения
Случайно назначен
Предположим, у нас есть X серверов, когда приходит запрос, мы получаем0-X, а затем кэшировать содержимое на этом сервере.
Это, очевидно, не является необязательным.Когда мы хотим запросить, мы не знаем, где мы находимся, мы можем только пройти по серверу один за другим, пока не получим его.
хэш по модулю
Другой распространенный способ заключается в集群数量Выполняем хэш по модулю, например, у нас сейчас 3 сервера, затем хешируем запрошенный ключ, после чего получаемhashcodeПо модулю 3, а полученное число — это сервер, на котором должен храниться ключ.
Хоть это и решает вышеописанную проблему приобретения, но масштабируемость крайне плохая.Представьте, что сейчас нам нужно добавить новую машину, то есть количество машин подошло4, то результат по модулю 4 сводится к3Результаты по модулю в основном разные, то есть нам нужно выполнить новый расчет хеш-функции для всех ключей и сохранить их снова.
Согласованный хэш
Это также находится в центре нашего внимания сегодня, это было предложено Массачусетским технологическим институтом в 1997 году. Мы объясним это отдельно ниже.
Принцип последовательного хеширования
На самом деле, непротиворечивый хеш — это тоже хэш по модулю, но он всегда правильный.2的32次方-1Возьми по модулю.
Согласованное хеширование вводит一致性hash环концепция, скоро(0-2^32-1)Все целые числа в середине соединяются встык, образуя кольцо, как показано ниже:
Затем сопоставьте все узлы с кольцом, предполагая, что у нас есть 3 узла, N1, N2.N3, Затем следующий рисунок:
После этого все ключи, которые мы будем хранить, также сопоставляются с кольцом, допустим, у нас есть 6 ключей.
После этого поверните ключ по часовой стрелке и сохраните его на первом попавшемся сервере, какая от этого польза?
Это масштабируемость. Когда вставляется новый узел, затрагивается лишь небольшое количество ключей, и очень мало ключей нужно пересчитывать. Давайте добавим узел и попробуем:
Можно обнаружить, что только данные N3 необходимо перенести с узла N2 на узел N4.
Разве это не выглядит красиво, какие преимущества и какие недостатки?
Конечно есть недостатки.
-
Вышеприведенная картинка представляет собой идеальное состояние, которое в основном равномерно распределено, но при реальном использовании, если вы используете имя машины в кластере (что, скорее всего, будет похожим) для хэширования, результаты могут быть очень похожими. скажем, он не разбросан, как на рисунке, а собран вместе, и ключи разбросаны, что приведет к тому, что большое количество ключей попадет на один или несколько серверов, а некоторые из них будут простаивать. неуравновешенный.
-
Все ключи redis являются строками, а строки
hashcodeМетод может возвращать отрицательное значение, а последовательное хеш-кольцо имеет только положительные значения, поэтому нам нужно использовать другой алгоритм хеширования (можно также грубо взять абсолютное значение).
Используйте виртуальные узлы для решения проблемы неравномерного хеширования
Неравномерный хэш в основном возникает, когда узлов мало, тогда мы можем смоделировать некоторые узлы вручную, что является так называемым виртуальным узлом, Например, у нас есть только 3 узла, но мы определяем правило, такое как A-1, A -2, A-3, все три узла могут быть сопоставлены кольцу, но мы все сохраняем в A, когда действительно сохраняем их.
Пока у нас достаточно виртуальных узлов, мы можем сделать их максимально равномерно распределенными по кольцу.
Суммировать
Алгоритм последовательного хеширования использует виртуальную кольцевую структуру данных для решения проблемы плохой масштабируемости в простых алгоритмах хеширования и имеет множество применений в распределенном кэшировании и балансировке нагрузки.
Java реализует согласованный клиент кеша алгоритма хеширования
-
предоставляется на Java
ConcurrentSkipListMapкласс, может быть хорошо использован здесь, не только может легко моделировать кольцевую структуру, безопасность параллелизма и использовать структуру таблицы пропускаConcurrentSkipListMapМожет обеспечить хорошую производительность параллелизма. -
Количество виртуальных узлов на самом деле можно приблизительно оценить, поэтому в следующем коде я использую его как переменную, которая вычисляется из количества текущих узлов при инициализации.Конкретной реализации метода расчета у меня, конечно, нет. дизайн В чем причина этого?Я хочу, чтобы количество виртуальных узлов было как можно лучше.Если узлов слишком много, я все равно использую фиксированные виртуальные узлы, что не сильно улучшит единообразие, но вызовет потерю производительности , и т.д.
-
Код в основном предоставляет следующие методы:
- Инициализировать, использовать строку конфигурации Redis
- При добавлении и удалении узлов их виртуальные узлы будут работать вместе.
- Операции получения и установки Jedis, конечно, не только будут иметь эти два метода на практике, здесь выполняется только моделирование, и нет реализации для дополнительных методов.
Ну, больше никакой ерунды, все в комментариях!
package util;
import redis.clients.jedis.Jedis;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* Created by pfliu on 2019/05/19.
*/
public class ConsistentHashRedis {
// 用跳表模拟一致性hash环,即使在节点很多的情况下,也可以有不错的性能
private final ConcurrentSkipListMap<Integer, String> circle;
// 虚拟节点数量
private final int virtual_size;
public ConsistentHashRedis(String configs) {
this.circle = new ConcurrentSkipListMap<>();
String[] cs = configs.split(",");
this.virtual_size = getVirtualSize(cs.length);
for (String c : cs) {
this.add(c);
}
}
/**
* 将每个节点添加进环中,并且添加对应数量的虚拟节点
*/
private void add(String c) {
if (c == null) return;
for (int i = 0; i < virtual_size; ++i) {
String virtual = c + "-N" + i;
int hash = getHash(virtual);
circle.put(hash, virtual);
}
}
// 根据字符串获取hash值,这里使用简单粗暴的绝对值.
private int getHash(String s) {
return Math.abs(s.hashCode());
}
// 计算当前需要多少个虚拟节点,这里没有计算,直接使用了150.
private int getVirtualSize(int length) {
return 150;
}
/**
* 对外提供的set方法
*/
public void set(String key, String v) {
getJedisFromCircle(key).set(key, v);
}
public String get(String k) {
return getJedisFromCircle(k).get(k);
}
/**
* 从环中取到适合当前key的jedis.
*/
private Jedis getJedisFromCircle(String key) {
int keyHash = getHash(key);
ConcurrentNavigableMap<Integer, String> tailMap = circle.tailMap(keyHash);
String config = tailMap.isEmpty() ? circle.firstEntry().getValue() : tailMap.firstEntry().getValue();
// 注意,由于使用了虚拟节点,所以这里要做 虚拟节点 -> 真实节点的映射
String[] cs = config.split("-");
return new Jedis(cs[0]);
}
/**
* 对外暴露的添加节点接口
*/
public boolean addJedis(String cs) {
add(cs);
return true;
}
/**
* 对外暴露的删除节点节点
*/
public boolean deleteJedis(String cs) {
delete(cs);
return true;
}
/**
* 从环中删除一个节点极其虚拟节点
*/
private void delete(String cs) {
if (cs == null) return;
for (int i = 0; i < virtual_size; ++i) {
String virtual = cs + "-N" + i;
int hash = getHash(virtual);
circle.remove(hash, virtual);
}
}
}
Заканчивать.
ChangeLog
2019-05-19 Завершено** Все вышесказанное является личным мнением, если есть какие-либо ошибки, пожалуйста, исправьте меня в области комментариев. **
Добро пожаловать на перепечатку, пожалуйста, подпишите и сохраните исходную ссылку.
Контактный адрес электронной почты: huyanshi2580@gmail.com
Дополнительные заметки об обучении см. в личном блоге ------>Хуян тен