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])

10voto

lvc Punkte 32687

Sie scheinen gleichzeitig auf zwei Probleme zu stoßen: Encoding-Probleme und Repräsentationsprobleme.

Encoding-Probleme entstehen insbesondere, da Sie anscheinend Python 2 verwenden - der Typ str von Python 2 ähnelt überhaupt nicht dem Typ String von Java und ähnelt tatsächlich eher einem Java-Array von byte. Aber String.getBytes() von Java garantiert nicht, dass Sie ein Byte-Array mit dem gleichen Inhalt wie ein Python str erhalten (sie verwenden wahrscheinlich kompatible Encoding, aber es wird nicht garantiert - selbst wenn dieser Fix nichts ändert, ist es allgemein eine gute Idee, Probleme in Zukunft zu vermeiden).

Der Weg um dieses Problem zu umgehen, besteht darin, einen Python-Typ zu verwenden, der sich wie Java's String verhält, und die entsprechenden Objekte beider Sprachen in Bytes mit derselben Codierung zu konvertieren. Von der Python-Seite aus bedeutet dies, dass Sie den Typ unicode verwenden möchten, der der Standard-String-Literal-Typ ist, wenn Sie Python 3 verwenden, oder fügen Sie dies oben in Ihre .py-Datei ein:

from __future__ import unicode_literals

Wenn keine dieser Optionen möglich ist, geben Sie Ihre Zeichenfolgenliterale auf diese Weise an:

u'Text'

Das u am Anfang zwingt es in Unicode umzuwandeln. Dies kann dann mit seiner Methode encode in Bytes umgewandelt werden, die (wenig überraschend) eine Kodierung benötigt:

u'Text'.encode('utf-8')

Von der Java-Seite gibt es eine überladene Version von String.getBytes, die eine Codierung annimmt - aber diese wird als java.nio.Charset anstelle eines Strings angenommen - also müssen Sie dies tun:

"Text".getBytes(java.nio.charset.Charset.forName("UTF-8"))

Dies wird Ihnen in beiden Sprachen äquivalente Bytefolgen geben, so dass die Hashes dieselbe Eingabe haben und Ihnen die gleiche Antwort geben.

Das andere Problem, das Sie haben könnten, betrifft die Darstellung, je nachdem, welche Hash-Funktion Sie verwenden. Python hashlib (was die bevorzugte Implementierung von MD5 und anderen kryptografischen Hashes seit Python 2.5 ist) ist genau kompatibel mit Javas MessageDigest darin - sie geben beide Bytes zurück, sodass ihre Ausgabe äquivalent sein sollte.

Pythons zlib.crc32 und Javas java.util.zip.CRC32ergeben dagegen beide numerische Ergebnisse - aber Javas ist immer eine unsignierte 64-Bit-Nummer, während Pythons (in Python 2) eine signierte 32-Bit-Nummer ist (in Python 3 ist es jetzt eine unsignierte 32-Bit-Nummer, also verschwindet dieses Problem). Um ein signiertes Ergebnis in ein unsigniertes umzuwandeln, tun Sie: Ergebnis & 0xffffffff, und das Ergebnis sollte mit dem Java-Äquivalent vergleichbar sein.

3voto

Brendan Long Punkte 51048

Gemäß dieser Analyse von Hash-Funktionen:

Murmur2, Meiyan, SBox und CRC32 bieten eine gute Leistung für alle Arten von Schlüsseln. Sie können als universelle Hashfunktionen auf x86 empfohlen werden.

Hardware-beschleunigtes CRC (im Diagramm als iSCSI CRC bezeichnet) ist die schnellste Hash-Funktion auf den aktuellen Core i5/i7-Prozessoren. Allerdings wird die CRC32-Anweisung nicht von AMD und früheren Intel-Prozessoren unterstützt.

Python hat zlib.crc32 und Java hat eine CRC32-Klasse. Da es sich um einen Standardalgorithmus handelt, sollten Sie in beiden Sprachen das gleiche Ergebnis erhalten.

MurmurHash 3 ist verfügbar in Google Guava (eine sehr nützliche Java-Bibliothek) und in pyfasthash für Python.

Beachten Sie, dass dies keine kryptografischen Hash-Funktionen sind, daher sind sie schnell, bieten jedoch keine gleichen Garantien. Wenn diese Hashes für die Sicherheit wichtig sind, verwenden Sie einen kryptografischen Hash.

2voto

basiljames Punkte 4689

Unterschiedliche Sprachimplementierungen eines Hash-Algorithmus machen den Hash-Wert nicht unterschiedlich. Der SHA-1 Hash, ob generiert in Java oder Python, wird der gleiche sein.

2voto

Chris Bode Punkte 1265

Ich bin nicht vertraut mit Redis, aber das Python-Beispiel scheint Schlüssel zu hashen, also gehe ich davon aus, dass wir über eine Art HashMap-Implementierung sprechen.

Dein Python-Beispiel scheint MD5-Hashes zu verwenden, die sowohl in Java als auch in Python gleich sein werden.

Hier ist ein Beispiel für MD5-Hashing in Java:

http://www.dzone.com/snippets/get-md5-hash-few-lines-java

Und in Python:

http://docs.python.org/library/md5.html

Jetzt möchtest du möglicherweise einen schnelleren Hash-Algorithmus finden. MD5 konzentriert sich auf kryptografische Sicherheit, was in diesem Fall nicht wirklich erforderlich ist.

2voto

Chris Bode Punkte 1265

Hier ist eine einfache Hash-Funktion, die das gleiche Ergebnis sowohl in Python als auch in Java für Ihre Schlüssel produziert:

Python

def hash(key):
        h = 0
        for c in key:
                h = ((h*37) + ord(c)) & 0xFFFFFFFF
        return h;

Java

public static int hash(String key) {
    int h = 0;
    for (char c : key.toCharArray())
        h = (h * 37 + c) & 0xFFFFFFFF;
    return h;
}

Sie benötigen keinen kryptographisch sicheren Hash dafür. Das wäre einfach übertrieben.

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