• Jedis中的一致性hash


    Jedis中的一致性hash

    本文仅供大家参考,不保证正确性,有问题请及时指出

    一致性hash就不多说了,网上有很多说的很好的文章,这里说说Jedis中的Shard是如何使用一致性hash的,也为大家在实现一致性hash提供些思路。

    首先是hash函数,在Jedis中有两种Hash算法可供选择,分别是MurMurHashMD5. 按照Jedis的说法MurMur Hash更快,效果更好些。

    MurmurHash.java

    package redis.clients.util;

    import java.nio.ByteBuffer;

    import java.nio.ByteOrder;

    publicclass MurmurHash implements Hashing {

        publicstaticint hash(byte[] data, int seed) {

            return hash(ByteBuffer.wrap(data), seed);

        }

        publicstaticint hash(byte[] data, int offset, int length, int seed) {

            return hash(ByteBuffer.wrap(data, offset, length), seed);

        }

        publicstaticint hash(ByteBuffer buf, int seed) {

            ByteOrder byteOrder = buf.order();

            buf.order(ByteOrder.LITTLE_ENDIAN);

     

            int m = 0x5bd1e995;

            int r = 24;

     

            int h = seed ^ buf.remaining();

     

            int k;

            while (buf.remaining() >= 4) {

                k = buf.getInt();

     

                k *= m;

                k ^= k >>> r;

                k *= m;

     

                h *= m;

                h ^= k;

            }

            if (buf.remaining() > 0) {

                ByteBuffer finish = ByteBuffer.allocate(4).order(

                        ByteOrder.LITTLE_ENDIAN);

                finish.put(buf).rewind();

                h ^= finish.getInt();

                h *= m;

            }

     

            h ^= h >>> 13;

            h *= m;

            h ^= h >>> 15;

     

            buf.order(byteOrder);

            return h;

        }

     

        publicstaticlong hash64A(byte[] data, int seed) {

            return hash64A(ByteBuffer.wrap(data), seed);

        }

     

        publicstaticlong hash64A(byte[] data, int offset, int length, int seed) {

            return hash64A(ByteBuffer.wrap(data, offset, length), seed);

        }

     

        publicstaticlong hash64A(ByteBuffer buf, int seed) {

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

             finish.put(buf).rewind();

                h ^= finish.getLong();

                h *= m;

            }

            h ^= h >>> r;

            h *= m;

            h ^= h >>> r;

            buf.order(byteOrder);

            return h;

        }

     

        publiclong hash(byte[] key) {

            return hash64A(key, 0x1234ABCD);

        }

     

        publiclong hash(String key) {

            return hash(SafeEncoder.encode(key));

        }

    }

    MD5java提供的类中即可获得,但是需要将MD5值转换为long类型的常量。

    publicinterface Hashing {

        publicstaticfinal Hashing MURMUR_HASH = new MurmurHash();

        public ThreadLocal<MessageDigest> md5Holder = new ThreadLocal<MessageDigest>();

        publicstaticfinal Hashing MD5 = new Hashing() {

            publiclong hash(String key) {

                return hash(SafeEncoder.encode(key));

            }

            publiclong hash(byte[] key) {

                try {

                    if (md5Holder.get() == null) {

                        md5Holder.set(MessageDigest.getInstance("MD5"));

                    }

                } catch (NoSuchAlgorithmException e) {

                    thrownew IllegalStateException("++++ no md5 algorythm found");

                }

                MessageDigest md5 = md5Holder.get();

                md5.reset();

                md5.update(key);

                byte[] bKey = md5.digest();

                long res = ((long) (bKey[3] & 0xFF) << 24)

                        | ((long) (bKey[2] & 0xFF) << 16)

                        | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF);

                return res;

            }

    };

    }

    MD5中使用了ThreadLocal,这个就不多说了,网上也有很多相关的blog。或者查看我《如何写性能测试程序》那篇博客。

     

    下面主要说说,Jedis中的一致性Hash部分。

     

    Shard中定义了如下属性:

        private TreeMap<Long, S> nodes;

        privatefinal Hashing algo;

        privatefinal Map<ShardInfo<R>, R> resources = new LinkedHashMap<ShardInfo<R>, R>();

    nodes属性类型为TreeMap<Long,S>,表示主机映射。每个主机被Hash后,对应多个Long值,而nodes维护了这些Long值对应的主机。

    algohash算法,是MurMur或者MD5.

    resources 完成主机信息和实际链接(Jedis)间的映射。

    初始化主机hash如下:

       privatevoid 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());

            }

        }

    for循环中,遍历主机列表(shards.get(i)),之后对每个主机按照单权重160的比例计算shard值,将shard值和主机信息(shardInfo)放到nodes中,将主机信息(shardInfo)和其对应的链接资源(Jedis)映射放入到resources中。

    Weight是权重,用于调节单个主机被映射值个数,如果weight1,那么当前主机将被映射为160个值,weight2,当前主机将被映射为320个值,因此weight2的节点被访问到的概率就会高一些。

    获取某个key对应的主机链接:

       public S getShardInfo(byte[] key) {

            SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));

            if (tail.isEmpty()) {

                returnnodes.get(nodes.firstKey());

            }

            return tail.get(tail.firstKey());

        }

    在获取key时,先根据keyhashalgo.hash(key)),然后获取node中值大于algo.hash(key)的子树,子树的头结点即为整个子树的最小值,因此选中这个节点做为key的映射节点,取出key值对应的valuehostInfo),之后根据hostInfo,通过resources,获取最终的链接。

       public R getShard(byte[] key) {

            returnresources.get(getShardInfo(key));

        }

     

    当然,一致性hash在存储时,一个key可以对应多个节点,一般这多个节点都是在nodesMapTree)中连续的,因此,也可以改进这个算法,以便实现多节点存储同一个key的方式。

     

    参考:jedis-2.1.0.jar

     

  • 相关阅读:
    Docker容器查看ip地址
    抽象工厂模式
    idea插件
    作业统计
    tarjan强连通图分量
    Android动画浅析
    位运算
    mongodb笔记
    依赖倒置原则
    单一职责原则
  • 原文地址:https://www.cnblogs.com/pangblog/p/3279864.html
Copyright © 2020-2023  润新知