Ниже я предложу два решения: первое я сочинил сам, второе "подсмотрел" в исходниках jedis - популярного "драйвера" для подключения java-приложений к noSQL хранилищу Redis.
Простое решение
Если вы давно читаете мой блог, то уже догадались, что моё решение будет простым до вульгарности. Так и есть:
- Берём хэш ключа
- Приводим первый байт хэша к int. Берём модуль числа.
- Если число больше 9 берём остаток от деления числа на 10. В любом случае у нас теперь число из диапазона 0...9
- Делим 10 на число нод. Получаем коэффициент как double.
- Делим число полученное их хэша ключа на найденный коэффициент и приводим результат к int. Результат и будет номером ноды для данного ключа.
public static int getShardNumber(int shardsCount, String key) throws Exception { if (shardsCount<1) throw new Exception("invalid shards count"); MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(key.getBytes("UTF-8")); byte[] bb = md.digest(); int n = Math.abs((int) bb[0]); if (n>=10) n = n%10; double delimiter = (double)10/shardsCount; return (int) (n/delimiter); }
Красивое решение
Оно тоже основано на хэшировании ключей. Но тут разработчики уже используют алгоритм MD5 и приводят результат к long, используя несколько первых байт хэша. Использовать этот алгоритм сложнее: сначала мы генерируем "карту" которая ставит каждой из нод в соответствие 160 чисел полученных из хэшей. Эта карта служит потом основой для получения номера ноды для произвольного ключа.
public static TreeMap<Long, Integer> nodes; public static void buildShardMap(int shardsCount) { nodes = new TreeMap<Long, Integer>(); for (int i = 0; i<shardsCount; i++) { for (int n = 0; n < 160; n++) { nodes.put(hash("SHARD-" + i + "-NODE-" + n), i); } } } public static long hash(String key) { try { if (md5Holder.get() == null) { md5Holder.set(MessageDigest.getInstance("MD5")); } } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("no md5 algorythm found"); } MessageDigest md5 = md5Holder.get(); md5.reset(); md5.update(SafeEncoder.encode(key)); byte[] bKey = md5.digest(); return ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16) | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF); } public static int getShardNumber(String key) { SortedMap<Long, Integer> tail = nodes.tailMap(hash(key)); if (tail.isEmpty()) { return nodes.get(nodes.firstKey()); } rerturn tail.get(tail.firstKey()); }
Сравниваем
Первое решение:
- Проще: нет предварительного этапа, не нужно сохранять состояние между обращениями
- Быстрее. Разница, впрочем, незначительная, особенно при большом числе обращений
Второе решение:
- Обеспечивает более качественное распределение ключей по нодам. В первом алгортиме для некоторых наборов ключей самая нагруженная из нод может быть заполнена почти вдвое больше чем самая ненагруженная. При использовании второго алгоритма "дисперсия" заметно меньше.
а почему решили не пользоваться https://github.com/youngking/redis-shard ?
ОтветитьУдалитьВторое решение как раз оттуда. Я упростил его, чтобы нагляднее представить общие черты и отличия, но общая логика не изменилась
УдалитьСвежие новости о криптовалютах и обзоры на самые прибыльные криптобиржи 2023 года на сайте coins-review.com. Подробные обзоры с мельчайшими подробностями и инструкции, чтобы ты мог успешно работать с криптовалютами. При ознакомлении с детальными обзорами на наши топовые криптовалютные биржи и последующей регистрации - приветственный бонус от 5 до 100 USDT и скидки на торговые комиссии!
ОтветитьУдалить