赞
踩
总之,相比于普通的哈希算法,一致性哈希算法对于节点的动态增删,具有一定的容错性和可扩展性。
- /**
- * MurMurHash算法,是非加密HASH算法,性能很高,
- * 比传统的CRC32,MD5,SHA-1(这两个算法都是加密HASH算法,复杂度本身就很高,带来的性能上的损害也不可避免)
- * 等HASH算法要快很多,而且据说这个算法的碰撞率很低.
- * http://murmurhash.googlepages.com/
- */
- private Long hash(String key) {
-
- ByteBuffer buf = ByteBuffer.wrap(key.getBytes());
- int seed = 0x1234ABCD;
-
- ByteOrder byteOrder = buf.order();
- buf.order(ByteOrder.LITTLE_ENDIAN);
-
- long m = 0xc6a4a7935bd1e995L;
- int r = 47;
-
- long h = seed ^ (buf.remaining() * m);
-
- long k;
- while (buf.remaining() >= 8) {
- k = buf.getLong();
-
- k *= m;
- k ^= k >>> r;
- k *= m;
-
- h ^= k;
- h *= m;
- }
-
- if (buf.remaining() > 0) {
- ByteBuffer finish = ByteBuffer.allocate(8).order(
- ByteOrder.LITTLE_ENDIAN);
- // for big-endian version, do this first:
- // finish.position(8-buf.remaining());
- finish.put(buf).rewind();
- h ^= finish.getLong();
- h *= m;
- }
-
- h ^= h >>> r;
- h *= m;
- h ^= h >>> r;
-
- buf.order(byteOrder);
- return h;
- }
- package redis.cn;
- import java.nio.charset.Charset;
- import java.util.List;
- import java.util.SortedMap;
- import java.util.TreeMap;
- import com.google.common.hash.HashFunction;
- import com.google.common.hash.Hashing;
- public class ConsistentHash {
- // ------------------ 一致性哈希算法的java实现 ------------------
- private SortedMap<Long,String> ketamaNodes = new TreeMap<Long,String>();
- private int numberOfReplicas = 1024;
- // 这里使用了谷歌的jar包 -- guava-18.0.jar
- private HashFunction hashFunction = Hashing.md5();
- private List<String> nodes;
- private volatile boolean init = false; //标志是否初始化完成
- // 有参数构造函数
- public ConsistentHash(int numberOfReplicas,List<String> nodes){
- this.numberOfReplicas = numberOfReplicas;
- this.nodes = nodes;
- init();
- }
- // 根据key的哈希值,找到最近的一个节点(服务器)
- public String getNodeByKey(String key){
- if(!init)
- throw new RuntimeException("init uncomplete...");
- // 注意,这里是NIO包 java.nio.charset.Charset
- byte[] digest = hashFunction.hashString(key, Charset.forName("UTF-8")).asBytes();
- long hash = hash(digest,0);
- //如果找到这个节点,直接取节点,返回
- if(!ketamaNodes.containsKey(hash)){
- //得到大于当前key的那个子Map,然后从中取出第一个key,就是大于且离它最近的那个key
- SortedMap<Long,String> tailMap = ketamaNodes.tailMap(hash);
- if(tailMap.isEmpty()){
- hash = ketamaNodes.firstKey();
- }else{
- hash = tailMap.firstKey();
- }
-
- }
- return ketamaNodes.get(hash);
- }
- // 新增节点
- public synchronized void addNode(String node){
- init = false;
- nodes.add(node);
- init();
- }
-
- private void init(){
- //对所有节点,生成numberOfReplicas个虚拟节点
- for(String node:nodes){
- //每四个虚拟节点为1组
- for(int i=0;i<numberOfReplicas/4;i++){
- //为这组虚拟结点得到惟一名称
- byte[] digest = hashFunction.hashString(node+i, Charset.forName("UTF-8")).asBytes();
- //Md5是一个16字节长度的数组,将16字节的数组每四个字节一组,分别对应一个虚拟结点,这就是为什么上面把虚拟结点四个划分一组的原因
- for(int h=0;h<4;h++){
- Long k = hash(digest,h);
- ketamaNodes.put(k,node);
- }
- }
- }
- init = true;
- }
-
- public void printNodes(){
- for(Long key:ketamaNodes.keySet()){
- System.out.println(ketamaNodes.get(key));
- }
- }
- // 哈希算法
- public static long hash(byte[] digest, int nTime)
- {
- long rv = ((long)(digest[3 + nTime * 4] & 0xFF) << 24)
- | ((long)(digest[2 + nTime * 4] & 0xFF) << 16)
- | ((long)(digest[1 + nTime * 4] & 0xFF) << 8)
- | ((long)digest[0 + nTime * 4] & 0xFF);
- return rv;
- }
- }
- //少量优化性能
- public ThreadLocal<MessageDigest> md5Holder = new ThreadLocal<MessageDigest>();
- public static final Hashing MD5 = new Hashing() {
- public long hash(String key) {
- return hash(SafeEncoder.encode(key));
- }
- // sharding使用的哈希算法是MD5
- public long hash(byte[] key) {
- try {
- if (md5Holder.get() == null) {
- md5Holder.set(MessageDigest.getInstance("MD5"));
- }
- }
- catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("++++ no md5 algorythm found");
- }
- MessageDigest md5 = md5Holder.get();
- md5.reset();
- md5.update(key);
- //获得MD5字节序列
- byte[] bKey = md5.digest();
- //前四个字节作为计算参数,最终获得一个32位int值.
- //此种计算方式,能够确保key的hash值更加“随机”/“离散”
- //如果hash值过于密集,不利于一致性hash的实现(特别是有“虚拟节点”设计时)
- long res = ((long) (bKey[3] & 0xFF) << 24)
- | ((long) (bKey[2] & 0xFF) << 16)
- | ((long) (bKey[1] & 0xFF) << 8)
- | (long) (bKey[0] & 0xFF);
- return res;
- }
- };
- //shards列表为客户端提供了所有redis-server配置信息,包括:ip,port,weight,name
- //其中weight为权重,将直接决定“虚拟节点”的“比例”(密度),权重越高,在存储是被hash命中的概率越高
- //--其上存储的数据越多。
- //其中name为“节点名称”,jedis使用name作为“节点hash值”的一个计算参数。
- //---
- //一致性hash算法,要求每个“虚拟节点”必须具备“hash值”,每个实际的server可以有多个“虚拟节点”(API级别)
- //其中虚拟节点的个数= “逻辑区间长度” * weight,每个server的“虚拟节点”将会以“hash”的方式分布在全局区域中
- //全局区域总长为2^32.每个“虚拟节点”以hash值的方式映射在全局区域中。
- // 环形:0-->vnode1(:1230)-->vnode2(:2800)-->vnode3(400000)---2^32-->0
- //所有的“虚拟节点”将按照其”节点hash“顺序排列(正序/反序均可),因此相邻两个“虚拟节点”之间必有hash值差,
- //那么此差值,即为前一个(或者后一个,根据实现而定)“虚拟节点”所负载的数据hash值区间。
- //比如hash值为“2000”的数据将会被vnode1所接受。
- private void initialize(List<S> shards){
- //虚拟节点,采取TreeMap存储:排序,二叉树
- nodes = new TreeMap<Long, S>();
- for (int i = 0; i != shards.size(); ++i) {
- final S shardInfo = shards.get(i);
- if (shardInfo.getName() == null)
- //当没有设置“name”是,将“SHARD-NODE”作为“虚拟节点”hash值计算的参数
- //"逻辑区间步长"为160,为什么呢??
- //最终多个server的“虚拟节点”将会交错布局,不一定非常均匀。
- for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
- nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
- }
- else
- for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
- nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);
- }
- resources.put(shardInfo, shardInfo.createResource());
- }
- }
- public R getShard(String key) {
- return resources.get(getShardInfo(key));
- }
- public S getShardInfo(byte[] key) {
- //获取>=key的“虚拟节点”的列表
- SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
- //如果不存在“虚拟节点”,则将返回首节点。
- if (tail.size() == 0) {
- return nodes.get(nodes.firstKey());
- }
- //如果存在,则返回符合(>=key)条件的“虚拟节点”的第一个节点
- return tail.get(tail.firstKey());
- }
- package redis.cn;
- import java.util.ArrayList;
- import java.util.List;
- import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
- import redis.clients.jedis.JedisShardInfo;
- import redis.clients.jedis.ShardedJedis;
- import redis.clients.jedis.ShardedJedisPool;
- /**
- * @author yangcq
- * @category jedis也是一致性哈希算法的一种实现。搭建redis分布式集群,可以使用jedis。
- */
- public class ShardedRedis {
-
- // 除了jdk自带的工具包以后,还需要导入下面2个jar包
- // commons-pool2-2.0.jar
- // jedis-2.4.2.jar
-
- public static void main(String[] args){
- // jedis配置参数
- GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
- genericObjectPoolConfig.setMaxTotal(1000);
- genericObjectPoolConfig.setMaxIdle(500);
-
- List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>();
- JedisShardInfo jedisShardInfo1 = new JedisShardInfo("127.0.0.1",1234);
- JedisShardInfo jedisShardInfo2 = new JedisShardInfo("127.0.0.1",1235);
- JedisShardInfo jedisShardInfo3 = new JedisShardInfo("127.0.0.1",1236);
- jedisShardInfoList.add(jedisShardInfo1);
- jedisShardInfoList.add(jedisShardInfo2);
- jedisShardInfoList.add(jedisShardInfo3);
-
- ShardedJedisPool shardedJedisPool = new ShardedJedisPool(genericObjectPoolConfig,jedisShardInfoList);
-
- set("key1","value1",shardedJedisPool);
- set("key2","value2",shardedJedisPool);
- set("key3","value3",shardedJedisPool);
- set("key4","value4",shardedJedisPool);
- set("key5","value5",shardedJedisPool);
-
- // jedis隐藏了实现一致性哈希算法的细节,只是给我们提供了简单的接口调用,就可以实现redis分布式集群的搭建
- // 那么jedis到底是如何实现一致性哈希算法的呢?
- }
-
- public static void set(String key,String value,ShardedJedisPool pool){
- // 从共享资源池中获取redis实例
- ShardedJedis shardedJedis = pool.getResource();
- // 赋值
- shardedJedis.set(key,value);
- pool.returnResource(shardedJedis);
- }
- }
- public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) {
- this.algo = algo;
- this.tagPattern = tagPattern;
- initialize(shards);
- }
-
- //初始化哈希环
- private void initialize(List<S> shards) {
- nodes = new TreeMap<Long, S>();
-
- for (int i = 0; i != shards.size(); ++i) {
- final S shardInfo = shards.get(i);
- if (shardInfo.getName() == null)
- for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
- nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n),
- shardInfo);
- }
- else
- for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
- nodes.put(
- this.algo.hash(shardInfo.getName() + "*"
- + shardInfo.getWeight() + n), shardInfo);
- }
- resources.put(shardInfo, shardInfo.createResource());
- }
- }
-
- //将key,value存储到相应的shard
- public String set(String key, String value) {
- Jedis j = getShard(key);
- return j.set(key, value);
- }
-
- public R getShard(String key) {
- return resources.get(getShardInfo(key));
- }
-
- //根据key获取shard
- public S getShardInfo(byte[] key) {
- SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
- if (tail.isEmpty()) {
- return nodes.get(nodes.firstKey());
- }
- return tail.get(tail.firstKey());
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。