提取jedis源碼的一致性hash代碼作爲通用工具類

一致性Hash熱點

一致性Hash算法是來解決熱點問題,如果虛擬節點設置過小熱點問題仍舊存在。
關於一致性Hash算法的原理我就不說了,網上有很多人提供自己編寫的一致性Hash算法的代碼示例,我在跑網上的代碼示例發現還是有熱點問題。爲此我翻閱了Jedis的ShardedJedis類的源碼把它的一致性Hash算法提取出來,作爲自己的一個工具類,以後自己工程開發中用起來也放心些,畢竟jedis的代碼經受了大家的驗證。

提取jedis的一致性hash代碼作爲通用工具類

看看人家碼神寫的代碼,這泛型,這繼承,這多態用的,寫的真是好,代碼通用性真是沒話說。
在Sharded方法中:
1 ,定義了一個TreeMap ,TreeMap 用於存儲虛擬節點(在初始化方法中,將每臺服務器節點採用hash算法劃分爲160個(默認的,DEFAULT_WEIGHT)虛擬節點(當然也可以配置劃分權重)
2 ,定義一個LinkedHashMap,用於存儲每一個Redis服務器的物理連接,其中shardInfo的createResource就是物理連接信息 。
3,對於key採用與初始化時同樣的hash(MurmurHash或者MD5)算法,然後從TreeMap獲取大於等於鍵hash值得節點,取最鄰近節點;
4,當key的hash值大於虛擬節點hash值得最大值時(也就是tail爲空),取第一個虛擬節點。
相關完整的源碼可以查看我的github的intsmaze-hash這個model,傳送點https://github.com/intsmaze/intsmaze。

package cn.intsmaze.hash.shard;
public class Sharded<R, S extends ShardInfo<R>> {

    public static final int DEFAULT_WEIGHT = 1;

    private TreeMap<Long, S> nodes;

    private final Hashing algo;

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


    public Sharded(List<S> shards) {
        this(shards, Hashing.MURMUR_HASH); // MD5 is really not good as we works
        // with 64-bits not 128
    }

    public Sharded(List<S> shards, Hashing algo) {
        this.algo = algo;
        this.shards=shards;
        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.getTableName() == 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.getTableName() + "*" + shardInfo.getWeight() + n), shardInfo);
            }
            resources.put(shardInfo, shardInfo.createResource());//調用IntsmazeShardInfo的createResource()方法 如果我們的實現不需要控制遠程的連接,那麼這個方法就不沒什麼用
        }
    }

    /**
     * 這個是找到key對應的節點後,不是僅僅返回屬於的節點名稱而是返回對應的實例連接
     * @param key
     * @return
     */
    public R getShardByResources(String key) {
        return resources.get(getShardInfo(key));
    }

    /**
     * 這個是找到key對應的節點後,返回屬於的節點名稱
     * @param key
     * @return
     */
    public S getShard(String key) {
        return getShardInfo(key);
    }

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

    public S getShardInfo(String key) {
        return getShardInfo(SafeEncoder.encode(key));
    }

}

package cn.intsmaze.hash.shard;
public class IntsmazeShardedConnection extends Sharded<Intsmaze, IntsmazeShardInfo>{

    public IntsmazeShardedConnection(List<IntsmazeShardInfo> shards) {
        super(shards);
    }

    public String getTable(String key) {
        IntsmazeShardInfo intsmazeShardInfo = getShard(key);
        return intsmazeShardInfo.getTableName();
    }
}

package cn.intsmaze.hash.shard;
public class IntsmazeShardInfo extends ShardInfo<Intsmaze> {

    private String host;

    private int port;

    public IntsmazeShardInfo(String host, String tableName) {
        super(Sharded.DEFAULT_WEIGHT, tableName);
        URI uri = URI.create(host);
        this.host = uri.getHost();
        this.port = uri.getPort();
    }


    @Override
    public Intsmaze createResource() {
        return new Intsmaze(this);
    }

}

package cn.intsmaze.hash.shard;
public abstract class ShardInfo<T> {
    private int weight;

    private String tableName;

    public ShardInfo() {
    }

    public ShardInfo(int weight,String tableName) {
        this.weight = weight;
        this.tableName=tableName;
    }

    protected abstract T createResource();

}



package cn.intsmaze.hash.shard;
public class Test {

    private static IntsmazeShardedConnection sharding;

    public static void setUpBeforeClass() throws Exception {
        List<IntsmazeShardInfo> shards = Arrays.asList(
                new IntsmazeShardInfo("localhost:6379", "intsmaze-A"),
                new IntsmazeShardInfo("localhost::6379", "intsmaze-B"),
                new IntsmazeShardInfo("localhost::6379", "intsmaze-C"),
                new IntsmazeShardInfo("localhost::6379", "intsmaze-D"),
                new IntsmazeShardInfo("localhost::6379", "intsmaze-E"));
        sharding = new IntsmazeShardedConnection(shards);
    }

    public void shardNormal() {
        Map<String,Long> map=new HashMap<String,Long>();
        for (int i = 0; i < 10000000; i++) {

            String result = sharding.getTable("sn" + i);

            Long num=map.get(result);
            if(num==null)
            {
                map.put(result,1L);
            }
            else
            {
                num=num+1;
                map.put(result,num);
            }
        }
        Set<Map.Entry<String, Long>> entries = map.entrySet();
        Iterator<Map.Entry<String, Long>> iterator = entries.iterator();
        while(iterator.hasNext())
        {
            Map.Entry<String, Long> next = iterator.next();
            System.out.println(next.getKey()+"--->>>"+next.getValue());
        }
    }

    public static void main(String[] args) throws Exception {
        Test t=new Test();
        t.setUpBeforeClass();
        t.shardNormal();
    }

}

沒有熱點問題

把jedis的源碼提取出來後,跑了一下,發現沒有熱點問題,原理不是採用算法的問題,而是一個物理節點對應的虛擬節點的數量的問題導致使用hash算法後,還是有熱點問題。jedis源碼物理節點對應虛擬節點時160,而網上大部分代碼都是10以下,所以導致了熱點問題,這也告訴我們,實現一致性Hash算法時,不要太吝嗇,虛擬節點設置的大點,熱點問題就不會再有。

相關完整的源碼可以查看我的github的intsmaze-hash這個model,傳送點https://github.com/intsmaze/intsmaze。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章