Java和Python程序的相同一致哈希算法实现
我们有一个应用程序,Python模块将向redis碎片写入数据,Java模块将从redis碎片读取数据,因此我需要为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
我在谷歌上搜索并尝试了几个实现,但发现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()
不能保证为您提供与Pythonstr
内容相同的字节数组(它们可能使用兼容的编码,但不能保证-即使此修复不会改变情况,一般来说,避免将来出现问题也是一个好主意)
因此,解决这个问题的方法是使用一种类似于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();
}
}