jiaxp 发表于 2017-12-21 12:27:40

C# servicestack.redis 互通 java jedis

  拥抱变化,如今也走上了.net/java通吃的时代,下面就讲讲如何让.net/java都能正常访问分片的redis吧。
  有几个关键点:一致性环哈希、哈希算法、序列化、反序列化
  后两个都比较直接,只要选择一种跨语言的序列化方式就行了,如:json, protobuf, ace等,本文全略了
  本文是基于jedis的一致性环哈希来修改的,.net选的是servicestack.redis组件来修改
  无奈两个组件都有各自的一致性环哈希算法,不兼容,那就选一个作为标准,修改另一个咯。本文选择jedis的一致性环哈希作为标准,进而修改.net来适应jedis
  jedis的逻辑是给每个redis节点构造160个虚拟节点,放入一颗二叉树中(key/value:key是一个long值,根据哈希算法算出来的一个long、value是节点id,是个string)。
  OK,逻辑清楚了,那就简单了,给c#端写个一模一样的一致性环哈希算法。
  

public>
{  

private object nodes_lock = new object();  

private RedBlackTreeMap<long, string> nodes = new RedBlackTreeMap<long, string>();  

private IHash hashAlgo = new MD5_LongSUM_Multiply_Hash();  

  

public void AddTarget(int index, string shard)  
{
  

lock (nodes_lock)  
{
  

for (int n = 0; n < 160; ++n)  
{
  

var hashKey = "SHARD-" + index + "-NODE-" + n;  

  

long hashValue = this.hashAlgo.Hash(hashKey);  

  
nodes.SetOrAddValue(hashValue, shard);
  
}
  
}
  
}
  

  

  

public string GetShardInfo(string key)  
{
  

long searchHashKey = this.hashAlgo.Hash(key);  

  

long nearestKey;  

string shard;  

  

lock (nodes_lock)  
{
  

if (this.nodes.NearestGreater(searchHashKey, out nearestKey))  
{
  
shard
= this.nodes.GetValue(nearestKey);  

return shard;  
}
  

  

if (this.nodes.Least(out searchHashKey, out shard))  

return shard;  
}
  

  

throw new Exception("GetShardInfo exception");  
}
  
}
  

  其中RedBlackTreeMap这个是TreeLib中的组件,需要在nuget上引用。

  

MD5_LongSUM_Multiply_Hash,这是个MD5算法,输入为string,输出为long。  此处由于考虑到输出不是string,因此自己又改了改,让他输出long
  

  

public>
{  

public long Hash(string key)  
{
  

var md5= Md5Hash(key);  

  

if (string.IsNullOrEmpty(md5))  
Log.GetLog().Info(
"Hash, md5 is null or empty");  

  

var convertedKeyBytes = Encoding.UTF8.GetBytes(md5);  

  

long value = 1;  

  

foreach(var b in convertedKeyBytes)  
value
*= b*-1;  

  

return value;  
}
  

  

private string Md5Hash(string input)  
{
  
MD5CryptoServiceProvider md5Hasher
= new MD5CryptoServiceProvider();  

  

if (string.IsNullOrEmpty(input))  
Log.GetLog().Info(
"Md5Hash, input is null or empty");  

  

byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));  
StringBuilder sBuilder
= new StringBuilder();  

for (int i = 0; i < data.Length; i++)  
{
  
sBuilder.Append(data.ToString(
"x2"));  
}
  

return sBuilder.ToString();  
}
  
}
  

  剩下的就是java端的这个输入string,输出long的算法,需要和.net的输入输出一致了。
  那就也写一个哈希算法,让他输入string,输出long,和.net的一致,这里只要java/.net用同一种md5算法,后续的md5变成long就很容易了。
  

import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;  

import redis.clients.util.Hashing;  

import redis.clients.util.SafeEncoder;  

  

import java.io.UnsupportedEncodingException;  

  

/**  
* Created by z on 2017/4/12.
  

*/
  
public>  

  
MessageDigestPasswordEncoder encoder=new MessageDigestPasswordEncoder("MD5");
  

  
public long hash(String key) {
  
return this.hash(SafeEncoder.encode(key));
  
}
  

  
public long hash(byte[] bytes) {
  

  
String converted_str= null;
  
try {
  
converted_str = new String(bytes, "UTF8");
  
} catch (UnsupportedEncodingException e) {
  
e.printStackTrace();
  
}
  

  
String result=encoder.encodePassword(converted_str, null);
  

  
try {
  
bytes=result.getBytes("UTF8");
  
} catch (UnsupportedEncodingException e) {
  
e.printStackTrace();
  
}
  

  
long value = 1;
  
for(byte b : bytes)
  
value *= b*-1;
  
return value;
  
}
  
}
  

  

<dependency>  
<groupId>org.springframework.security</groupId>
  
<artifactId>spring-security-core</artifactId>
  
</dependency>
  

  OK,核心的就这些了。
页: [1]
查看完整版本: C# servicestack.redis 互通 java jedis