13 Stimmen

Dasselbe konsistente Hashing-Algorithmus-Implementierung für Java und Python Programm

Wir haben eine App, bei der das Python-Modul Daten in Redis-Shards schreiben wird und das Java-Modul Daten aus den Redis-Shards lesen wird, daher muss ich den genau gleichen konsistenten Hashing-Algorithmus für Java und Python implementieren, um sicherzustellen, dass die Daten gefunden werden können.

Ich habe recherchiert und verschiedene Implementierungen ausprobiert, aber festgestellt, dass die Java- und Python-Implementierungen immer unterschiedlich sind und nicht zusammen verwendet werden können. Ich benötige Ihre Hilfe.

Bearbeitung, Online-Implementierungen, die ich ausprobiert habe:
Java: http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent_hash.html
Python: http://techspot.zzzeek.org/2012/07/07/the-absolutely-simplest-consistent-hashing-example/
http://amix.dk/blog/post/19367

Bearbeitung, angehängter Java- (Google Guava lib verwendet) und Python-Code, den ich geschrieben habe. Der Code basiert auf den oben genannten Artikeln.

import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;
import com.google.common.hash.HashFunction;

public class ConsistentHash<T> {
    private final HashFunction hashFunction;
    private final int numberOfReplicas;
    private final SortedMap<Long, T> circle = new TreeMap<Long, T>();

    public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
            Collection<T> nodes) {
        this.hashFunction = hashFunction;
        this.numberOfReplicas = numberOfReplicas;

        for (T node : nodes) {
            add(node);
        }
    }

    public void add(T node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            circle.put(hashFunction.hashString(node.toString() + i).asLong(),
                    node);
        }
    }

    public void remove(T node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            circle.remove(hashFunction.hashString(node.toString() + i).asLong());
        }
    }

    public T get(Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        long hash = hashFunction.hashString(key.toString()).asLong();
        if (!circle.containsKey(hash)) {
            SortedMap<Long, T> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }
}

Test Code:

        ArrayList<String> al = new ArrayList<String>(); 
        al.add("redis1");
        al.add("redis2");
        al.add("redis3");
        al.add("redis4");

        String[] userIds = 
        {"-84942321036308",
        "-76029520310209",
        "-68343931116147",
        "-54921760962352"
        };
        HashFunction hf = Hashing.md5();

        ConsistentHash<String> consistentHash = new ConsistentHash<String>(hf, 100, al); 
        for (String userId : userIds) {
            System.out.println(consistentHash.get(userId));
        }

Python-Code:

import bisect
import md5

class ConsistentHashRing(object):
    """Implementiere einen konsistenten Hashing-Ring."""

    def __init__(self, replicas=100):
        """Erstelle einen neuen konsistenten Hashing-Ring.

        :param replicas: Anzahl der Replikate.

        """
        self.replicas = replicas
        self._keys = []
        self._nodes = {}

    def _hash(self, key):
        """Gegeben einen Zeichenfolgen-Schlüssel, gib einen Hash-Wert zurück."""

        return long(md5.md5(key).hexdigest(), 16)

    def _repl_iterator(self, nodename):
        """Gegeben einen Knotennamen, gib einen Iterierbaren von Replika-Hashes zurück."""

        return (self._hash("%s%s" % (nodename, i))
                for i in xrange(self.replicas))

    def __setitem__(self, nodename, node):
        """Füge einen Knoten hinzu, gegeben seinen Namen.

        Der angegebene Knotenname wird
        unter den Replikateanzahl gehasht.

        """
        for hash_ in self._repl_iterator(nodename):
            if hash_ in self._nodes:
                raise ValueError("Knotenname %r ist "
                            "bereits vorhanden" % nodename)
            self._nodes[hash_] = node
            bisect.insort(self._keys, hash_)

    def __delitem__(self, nodename):
        """Entferne einen Knoten, gegeben seinen Namen."""

        for hash_ in self._repl_iterator(nodename):
            # löst KeyError für nicht vorhandenen Knotennamen aus
            del self._nodes[hash_]
            index = bisect.bisect_left(self._keys, hash_)
            del self._keys[index]

    def __getitem__(self, key):
        """Gib einen Knoten zurück, gegeben einem Schlüssel.

        Der Knotenreplikat mit einem Hash-Wert, der am nächsten
        aber nicht kleiner als der des gegebenen
        Namens ist, wird zurückgegeben. Wenn der Hash des
        gegebenen Namens größer ist als der größte
        Hash, wird der Knoten mit dem niedrigsten gehashten Wert zurückgegeben.

        """
        hash_ = self._hash(key)
        start = bisect.bisect(self._keys, hash_)
        if start == len(self._keys):
            start = 0
        return self._nodes[self._keys[start]]

Test Code:

import ConsistentHashRing

if __name__ == '__main__':
    server_infos = ["redis1", "redis2", "redis3", "redis4"];
    hash_ring = ConsistentHashRing()
    test_keys = ["-84942321036308",
        "-76029520310209",
        "-68343931116147",
        "-54921760962352",
        "-53401599829545"
        ];

    for server in server_infos:
        hash_ring[server] = server

    for key in test_keys:
        print str(hash_ring[key])

1voto

Lasst uns das klarstellen: Die gleiche binäre Eingabe an die gleiche Hash-Funktion (SHA-1, MD5, ...) in verschiedenen Umgebungen/Implementierungen (Python, Java, ...) wird den gleichen binären Ausgabewert ergeben. Das liegt daran, dass diese Hash-Funktionen gemäß den Standards implementiert sind.

Daher werden Sie die Quellen der Probleme entdecken, die Sie haben, wenn Sie diese Fragen beantworten:

  • geben Sie dieselbe binäre Eingabe für beide Hash-Funktionen an (z. B. MD5 in Python und Java)?

  • interpretieren Sie die binäre Ausgabe beider Hash-Funktionen gleich (z. B. MD5 in Python und Java)?

Die Antwort von @lvc enthält viele weitere Einzelheiten zu diesen Fragen.

0voto

Q i Punkte 320

Für die Java-Version würde ich empfehlen, MD5 zu verwenden, das ein 128-Bit-Zeichenketten-Ergebnis generiert und dann in BigInteger umgewandelt werden kann (Integer und Long reichen nicht aus, um 128-Bit-Daten zu halten).

Hier finden Sie einen Beispielscode:

private static class HashFunc {

    static MessageDigest md5;

    static {
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            //
        }
    }

    public synchronized int hash(String s) {
        md5.update(StandardCharsets.UTF_8.encode(s));
        return new BigInteger(1, md5.digest()).intValue();
    }
}

Beachten Sie:

Die java.math.BigInteger.intValue() wandelt dieses BigInteger in ein int um. Diese Umwandlung entspricht einer Verengung der primitiven Umwandlung von long in int. Wenn dieser BigInteger zu groß ist, um in ein int zu passen, werden nur die niederwertigen 32 Bits zurückgegeben. Diese Umwandlung kann Informationen über die Gesamtmagnitude des BigInteger-Werts verlieren und ein Ergebnis mit dem entgegengesetzten Vorzeichen zurückgeben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X