All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alibaba.spring.boot.rsocket.broker.cluster.KetamaConsistentHash Maven / Gradle / Ivy

package com.alibaba.spring.boot.rsocket.broker.cluster;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class KetamaConsistentHash {
    /**
     * number of virtual node replicas of the physical node
     */
    private final int numberOfVirtualNodeReplicas;
    private final SortedMap circle = new TreeMap();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public KetamaConsistentHash(int numberOfVirtualNodeReplicas, List nodes) {
        this.numberOfVirtualNodeReplicas = numberOfVirtualNodeReplicas;
        add(nodes);
    }

    public synchronized void add(T node) {
        w.lock();
        try {
            addNode(node);
        } finally {
            w.unlock();
        }
    }

    public synchronized void add(List nodes) {
        w.lock();
        try {
            for (T node : nodes) {
                addNode(node);
            }
        } finally {
            w.unlock();
        }
    }

    public synchronized void remove(List nodes) {
        w.lock();
        try {
            for (T node : nodes) {
                removeNode(node);
            }
        } finally {
            w.unlock();
        }
    }

    public synchronized void remove(T node) {
        w.lock();
        try {
            removeNode(node);
        } finally {
            w.unlock();
        }
    }

    public T get(Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        long hash = getKetamaKey(key.toString());
        r.lock();
        try {
            if (!circle.containsKey(hash)) {
                SortedMap tailMap = circle.tailMap(hash);
                hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
            }
            return circle.get(hash);
        } finally {
            r.unlock();
        }
    }

    private void addNode(T node) {
        for (int i = 0; i < numberOfVirtualNodeReplicas; i++) {
            byte[] digest = md5(node + "-" + i);
            for (int h = 0; h < 4; h++) {
                circle.put(getKetamaKey(digest, h), node);
            }
        }
    }

    private void removeNode(T node) {
        for (int i = 0; i < numberOfVirtualNodeReplicas; i++) {
            byte[] digest = md5(node.toString() + "-" + i);
            for (int h = 0; h < 4; h++) {
                circle.remove(getKetamaKey(digest, h));
            }
        }
    }


    public static long[] getKetamaKeys(String text) {
        long[] pairs = new long[4];
        byte[] digest = md5(text);
        for (int h = 0; h < 4; h++) {
            pairs[h] = getKetamaKey(digest, h);
        }
        return pairs;
    }

    private static long getKetamaKey(final String k) {
        byte[] digest = md5(k);
        return getKetamaKey(digest, 0) & 0xffffffffL;
    }

    private static Long getKetamaKey(byte[] digest, int h) {
        return ((long) (digest[3 + h * 4] & 0xFF) << 24) | ((long) (digest[2 + h * 4] & 0xFF) << 16) | ((long) (digest[1 + h * 4] & 0xFF) << 8) | (digest[h * 4] & 0xFF);
    }


    private static byte[] md5(String text) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            return md.digest(text.getBytes(StandardCharsets.UTF_8));
        } catch (Exception ignore) {
            return text.getBytes(StandardCharsets.UTF_8);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy