Java和Python程序的相同一致哈希算法实现

Java和Python程序的相同一致哈希算法实现,java,python,consistent-hashing,Java,Python,Consistent Hashing,我们有一个应用程序,Python模块将向redis碎片写入数据,Java模块将从redis碎片读取数据,因此我需要为Java和Python实现完全相同的一致哈希算法,以确保能够找到数据。 我在谷歌上搜索并尝试了几个实现,但发现Java和Python实现总是不同的,不能一起使用。我需要你的帮助 编辑我尝试过的在线实现: Java: 蟒蛇: 编辑、附加Java(使用Google Guava lib)和我编写的Python代码。代码基于上述文章 import java.util.Collecti

我们有一个应用程序,Python模块将向redis碎片写入数据,Java模块将从redis碎片读取数据,因此我需要为Java和Python实现完全相同的一致哈希算法,以确保能够找到数据。

我在谷歌上搜索并尝试了几个实现,但发现Java和Python实现总是不同的,不能一起使用。我需要你的帮助

编辑我尝试过的在线实现:
Java:
蟒蛇:

编辑、附加Java(使用Google Guava lib)和我编写的Python代码。代码基于上述文章

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);
    }
}
测试代码:

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

哈希算法的不同语言实现不会使哈希值不同。
SHA-1
哈希值(无论是用java还是python生成)将是相同的。

根据:

MURROUR2、Meiyan、SBox和CRC32为各种按键提供了良好的性能。可以推荐它们作为x86上的通用哈希函数

硬件加速CRC(表中标记为iSCSI CRC)是最新核心i5/i7处理器上最快的哈希函数。但是,AMD和更早的英特尔处理器不支持CRC32指令

Python有,Java有。因为这是一个标准算法,所以在两种语言中应该得到相同的结果

BurruHash 3是可用的(一个非常有用的Java库)并且适用于Python


请注意,它们不是,所以它们很快,但不提供相同的保证。如果这些散列对安全性很重要,请使用加密散列。

我不熟悉Redis,但Python示例似乎是散列密钥,因此我假设我们讨论的是某种HashMap实现

您的python示例似乎使用了MD5哈希,这在Java和python中都是相同的

以下是Java中MD5哈希的一个示例:

在Python中:


现在,您可能想找到一种更快的散列算法。MD5专注于加密安全性,在本例中并不需要加密安全性。

您似乎同时遇到了两个问题:编码问题和表示问题

编码问题尤其会出现,因为您似乎在使用Python2—Python2的
str
类型与Java的
String
类型完全不同,实际上更像是
字节的Java数组。但是Java的
String.getBytes()
不能保证为您提供与Python
str
内容相同的字节数组(它们可能使用兼容的编码,但不能保证-即使此修复不会改变情况,一般来说,避免将来出现问题也是一个好主意)

因此,解决这个问题的方法是使用一种类似于Java的
字符串的Python类型,并将两种语言的对应对象转换为指定相同编码的字节。从Python方面来说,这意味着您希望使用
unicode
类型,如果您使用的是Python 3,这是默认的字符串文字类型,或者将其放在.py文件顶部附近:

from __future__ import unicode_literals
如果这两个选项都不是选项,请按以下方式指定字符串文字:

u'text'
前面的
u
强制它使用unicode。然后可以使用其
encode
方法将其转换为字节,该方法采用(毫不奇怪)编码:

u'text'.encode('utf-8')
从Java方面看,有一个重载版本的
String.getBytes
,它接受编码,但它将其作为一个字符串而不是字符串,因此,您需要执行以下操作:

"text".getBytes(java.nio.charset.Charset.forName("UTF-8"))
这两种语言的字节序列是相等的,因此散列具有相同的输入,并给出相同的答案

您可能遇到的另一个问题是表示,这取决于您使用的哈希函数。Python(这是md5和其他加密哈希的首选实现,因为Python 2.5)在这方面与Java完全兼容——它们都提供字节,所以它们的输出应该是等效的


另一方面,Python和Java都给出了数字结果——但Java总是一个无符号的64位数字,而Python(在Python 2中)是一个有符号的32位数字(在Python 3中,现在是一个无符号的32位数字,所以这个问题消失了)。要将有符号结果转换为无符号结果,请执行:
result&0xffffffff
,并且结果应与Java结果相当

让我们直截了当地说:在不同的环境/实现(Python、Java等)中,对相同的哈希函数(SHA-1、MD5等)进行相同的二进制输入将产生相同的二进制输出。这是因为这些散列函数是根据

因此,在回答以下问题时,您将发现问题的根源:

  • 您是否为两个哈希函数提供相同的二进制输入(例如Python和Java中的MD5)

  • 您是否对这两个哈希函数的二进制输出(例如Python和Java中的MD5)进行等效解释


@lvc的回答提供了关于这些问题的更多细节。

下面是一个简单的哈希函数,它在python和java上为您的密钥生成相同的结果:

python JAVA
对此,您不需要加密安全的哈希。这太过分了。

对于java版本,我建议使用MD5生成128位字符串结果,然后可以将其转换为BigInteger(Integer和Long不足以容纳128位数据)

此处的示例代码:

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();
    }
}
请注意:

java.math.BigInteger.intValue()将此BigInteger转换为int。此转换类似于从long到int的缩小原语转换。如果此BigInteger太大,无法放入int,则只返回低阶32位。此转换还可能丢失有关BigInteger值的总体大小的信息
def hash(key):
        h = 0
        for c in key:
                h = ((h*37) + ord(c)) & 0xFFFFFFFF
        return h;
public static int hash(String key) {
    int h = 0;
    for (char c : key.toCharArray())
        h = (h * 37 + c) & 0xFFFFFFFF;
    return h;
}
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();
    }
}