由于项目特殊需求,必须稳定key到特定的分片。因为目前的redis集群是通过客户端来实现的,为此研究了jedis客户端。
具体的原理参照这篇文章http://blog.csdn.net/guanxinquan/article/details/10231899说的挺明白,很感谢这位兄弟。主要涉及到treeMap(红黑树算法)
原理是将每个redis的实例映射到空间的一部分上,即生成n倍的虚拟节点,然后根据hash(key)算法的落点去映射空间,找到最近的节点。
下面是我的Java代码:依赖jedis
package com.hash;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import redis.clients.util.MurmurHash;
/**
* 生成可以稳定到分片的key
* @author sugongp
* @创建时间 2014-4
*/
public class MurmurHashTest {
//jedis中使用的murmur hash
MurmurHash murmur = new MurmurHash();
//每个value都相当于是一个分片编号
TreeMap<Long, Integer> tm = new TreeMap<Long, Integer>();
//计数器
static Map<String,Integer> countMap = new HashMap<String,Integer>();
//可认为是实际的server分片
List<Shard> shards = null;
//实际server端分片数量,根据实际情况而定,这里以server为16个分片为例
int serverShards =16;
//key=shard编号 value 第一个放入shard的key
static Map<Integer, String> keyShardMap = new HashMap<Integer, String>();
void init(){
shards = new ArrayList<Shard>();
Shard sd = null;
for(int i=0; i<serverShards; i++){
sd = new Shard();
/**
* 默认权重是1,如果修改,即使将全部的shard都修改为10
* 那么key映射到的shard也会随之改变
*/
sd.setWeiht(1);
//默认情况下没有名字,name也会影响hash分片的结果
//sd.setName("shard"+i);
shards.add(sd);
}
}
void start(){
int i=0;
//遍历分片
for (Shard sd : shards) {
if(sd.getName() == null) {
//将每个分片,分散的映射到treeMap空间,即在红黑树上生成了大量的虚拟分片
for (int n = 0; n < 160 * sd.getWeiht(); n++) {
tm.put(murmur.hash("SHARD-" + i + "-NODE-" + n), i);
}
}else {
for (int n = 0; n < 160 * sd.getWeiht(); n++) {
tm.put(murmur.hash(sd.getName() + "*" + sd.getWeiht() + n), i);
}
}
i++;
}
}
void test(){
String key;
//随机生成一万个key
for(int i=0; i<10000; i++){
key = "key"+i;
SortedMap<Long, Integer> sm = tm.tailMap(murmur.hash(key));
int d ;
if(sm.isEmpty()) {
//找到了她的shard
d = tm.get(tm.firstKey());
System.out.println(String.format("%s = shard%s" ,key, d));
count(d);
if(!keyShardMap.containsKey(d)){
keyShardMap.put(d, key);
}
continue;
}
d = tm.get(sm.firstKey());
System.out.println(String.format("%s = shard%s" ,key, d));
count(d);
if(!keyShardMap.containsKey(d)){
keyShardMap.put(d, key);
}
}
}
static void count(int d){
if(countMap.containsKey("shard" + d)){
int c = countMap.get("shard" + d);
countMap.put("shard" + d, ++c);
}else {
countMap.put("shard" + d, 1);
}
}
static void print(){
System.out.println(String.format("----------%d个分片中key的数量统计-------------",countMap.size()));
for(String mkey : countMap.keySet()){
System.out.println(String.format("分片%s中key的数量%s个", mkey ,countMap.get(mkey)));
}
System.out.println(String.format("-----------%d个分片中稳定的key统计-------------",keyShardMap.size()));
for(int i : keyShardMap.keySet()){
System.out.println(String.format("第一个稳定到分片%s的key是%s", i, keyShardMap.get(i)));
}
}
public static void main(String[] args) {
MurmurHashTest m = new MurmurHashTest();
m.init();
m.start();
m.test();
print();
}
}
依赖的类
package com.hash;
public class Shard {
String name;
int weiht;
public int getWeiht() {
return weiht;
}
public void setWeiht(int weiht) {
this.weiht = weiht;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
程序执行结果:
----------16个分片中key的数量统计-------------
分片shard13中key的数量606个
分片shard14中key的数量581个
分片shard15中key的数量553个
分片shard0中key的数量645个
分片shard1中key的数量633个
分片shard11中key的数量673个
分片shard4中key的数量559个
分片shard5中key的数量596个
分片shard12中key的数量646个
分片shard2中key的数量712个
分片shard10中key的数量699个
分片shard3中key的数量611个
分片shard8中key的数量591个
分片shard9中key的数量649个
分片shard6中key的数量647个
分片shard7中key的数量599个
-----------16个分片中稳定的key统计-------------
keyShardMap size = 16
第一个稳定到分片0的key是key4
第一个稳定到分片1的key是key0
第一个稳定到分片2的key是key14
第一个稳定到分片3的key是key10
第一个稳定到分片4的key是key5
第一个稳定到分片5的key是key9
第一个稳定到分片6的key是key23
第一个稳定到分片7的key是key32
第一个稳定到分片8的key是key12
第一个稳定到分片9的key是key62
第一个稳定到分片10的key是key3
第一个稳定到分片11的key是key60
第一个稳定到分片12的key是key18
第一个稳定到分片13的key是key51
第一个稳定到分片14的key是key1
第一个稳定到分片15的key是key35