package com.lambdaworks.redis.cluster; import java.nio.ByteBuffer; import java.util.*; import com.lambdaworks.codec.CRC16; import com.lambdaworks.redis.codec.RedisCodec; /** * Utility to calculate the slot from a key. * * @author Mark Paluch * @since 3.0 */ public class SlotHash { /** * Constant for a subkey start. */ public static final byte SUBKEY_START = (byte) '{'; /** * Constant for a subkey end. */ public static final byte SUBKEY_END = (byte) '}'; /** * Number of redis cluster slot hashes. */ public static final int SLOT_COUNT = 16384; private SlotHash() { } /** * Calculate the slot from the given key. * * @param key the key * @return slot */ public static final int getSlot(String key) { return getSlot(key.getBytes()); } /** * Calculate the slot from the given key. * * @param key the key * @return slot */ public static final int getSlot(byte[] key) { return getSlot(ByteBuffer.wrap(key)); } /** * Calculate the slot from the given key. * * @param key the key * @return slot */ public static final int getSlot(ByteBuffer key) { byte[] input = new byte[key.remaining()]; key.duplicate().get(input); byte[] finalKey = input; int start = indexOf(input, SUBKEY_START); if (start != -1) { int end = indexOf(input, start + 1, SUBKEY_END); if (end != -1 && end != start + 1) { finalKey = new byte[end - (start + 1)]; System.arraycopy(input, start + 1, finalKey, 0, finalKey.length); } } return CRC16.crc16(finalKey) % SLOT_COUNT; } private static int indexOf(byte[] haystack, byte needle) { return indexOf(haystack, 0, needle); } private static int indexOf(byte[] haystack, int start, byte needle) { for (int i = start; i < haystack.length; i++) { if (haystack[i] == needle) { return i; } } return -1; } /** * Partition keys by slot-hash. The resulting map honors order of the keys. * * @param codec codec to encode the key * @param keys iterable of keys * @param <K> Key type. * @param <V> Value type. * @result map between slot-hash and an ordered list of keys. * */ static <K, V> Map<Integer, List<K>> partition(RedisCodec<K, V> codec, Iterable<K> keys) { Map<Integer, List<K>> partitioned = new HashMap<>(); for (K key : keys) { int slot = getSlot(codec.encodeKey(key)); if (!partitioned.containsKey(slot)) { partitioned.put(slot, new ArrayList<>()); } Collection<K> list = partitioned.get(slot); list.add(key); } return partitioned; } /** * Create mapping between the Key and hash slot. * * @param partitioned map partitioned by slothash and keys * @param <K> */ static <K> Map<K, Integer> getSlots(Map<Integer, ? extends Iterable<K>> partitioned) { Map<K, Integer> result = new HashMap<>(); for (Map.Entry<Integer, ? extends Iterable<K>> entry : partitioned.entrySet()) { for (K key : entry.getValue()) { result.put(key, entry.getKey()); } } return result; } }