日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis集群一致性Hash效果的代码演示

發布時間:2025/3/8 数据库 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis集群一致性Hash效果的代码演示 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在微服務領域,使用Redis做緩存可并不是一件容易的事情。

像新浪、推特這樣的應用,許許多多的熱點數據全都存放在Redis這一層,打到DB層的請求并不多,可以說非常依賴緩存了。如果緩存掛掉,流量全部穿透到DB層,其必然不堪其重,整個系統也會隨之癱瘓,后果非常嚴重。 由于緩存數據量很大,Redis快正是快在其基于內存的快速存取,而計算機的內存資源又是十分有限的,故分布式緩存集群面臨著伸縮性的要求。

一致性Hash存在的意義

Redis集群中的各實例之間是并不知道對方的,需要在客戶端實現路由法來將key路由到不同的redis節點。

該路由算法是關鍵,它必須讓新上線的緩存服務器對整個分布式緩存集群影響最小,使得擴容后,整個緩存服務器集群中已經緩存的數據盡可能還被訪問到。

若是使用一般的對key進行一次hash的算法,則會導致擴容后命中率極低。 如下表所示,當集群由3個節點擴容到4個節點時,會有75%的key無法命中。

hash(key)hash(key)/3hash(key)/4是否命中
111
222
303
410
521
602
713
820
901
1012
1123
1200

這可太糟糕了,當服務器數量為100臺時,再增加一臺新服務器,不能命中率將達到99%,這和整個緩存服務掛了一個效果。

而一致性Hash正是為了解決這個問題而出現的,該路由算法通過引入一個一致性Hash環,以及進一步增加虛擬節點層,來實現盡可能高的命中率。 使用該算法,當節點由n擴容為n+1時,命中率可保持在n/(n+1)左右。

關于該算法的具體原理與網上已經有一些說得很透徹的文章,本文不再贅述。 下面主要從代碼實現及運行的方式來對此算法的效果進行展示。

本機部署多個Redis節點

要對一致性Hash進行驗證,要做好準備工作,首先要有一個Redis集群。 這里我通過使用在本機上部署多個Redis實例指向不同端口來模擬這一形態。

建立項目目錄:$ mkdir redis-conf 將redis的配置copy一份過來并復制為5份,分別命名為redis-6379.conf~redis-6383.conf。

需要對其內容進行一些修改才能正常啟動,分別找到配置文件中的如下兩行并對數字進行相應修改。

port 6379 pidfile /var/run/redis_6379.pid 復制代碼

然后就可以分別啟動了:redis-server ./redis-6379 &

可以使用redis-cli -p 6379來指定連接的redis-server。 不妨進行一次嘗試,比如在6379設置key 1 2,而到6380 get 1只能得到nil,說明它們是各自工作的,已經滿足可以測試的條件。

代碼實現

思路是這樣的: 部署4個節點,從6379到6382,通過一致性Hash算法,將key: 0~99999共100000個key分別set到這4個服務器上,然后再部署一個節點6383,這時再從0到99999開始get一遍,統計get到的次數來驗證命中率是否為期望的80%左右(4/5)。

一致性Hash算法的實現嚴重借鑒了這篇文章,使用紅黑樹來做數據結構,來實現log(n)的查找時間復雜度,使用FNV1_32_HASH哈希算法來盡可能使key與節點分布得更加均勻,引入了虛擬節點,來做負載均衡。

建議讀者詳細看下這篇文章,里面的講解非常詳細易懂。

下面是我改寫過后的代碼:

package org.guerbai.io.jedistry;import redis.clients.jedis.Jedis; import java.util.*;class JedisProxy {private static String[][] redisNodeList = {{"localhost", "6379"},{"localhost", "6380"},{"localhost", "6381"},{"localhost", "6382"},};private static Map<String, Jedis> serverConnectMap = new HashMap<>();private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();private static final int VIRTUAL_NODES = 100;static{for (String[] str: redisNodeList){addServer(str[0], str[1]);}System.out.println();}private static int getHash(String str){final int p = 16777619;int hash = (int)2166136261L;for (int i = 0; i < str.length(); i++)hash = (hash ^ str.charAt(i)) * p;hash += hash << 13;hash ^= hash >> 7;hash += hash << 3;hash ^= hash >> 17;hash += hash << 5;// 如果算出來的值為負數則取其絕對值if (hash < 0)hash = Math.abs(hash);return hash;}private static String getServer(String node){// 得到帶路由的結點的Hash值int hash = getHash(node);// 得到大于該Hash值的所有MapSortedMap<Integer, String> subMap =virtualNodes.tailMap(hash);// 第一個Key就是順時針過去離node最近的那個結點if (subMap.isEmpty()) {subMap = virtualNodes.tailMap(0);}Integer i = subMap.firstKey();// 返回對應的虛擬節點名稱,這里字符串稍微截取一下String virtualNode = subMap.get(i);return virtualNode.substring(0, virtualNode.indexOf("&&"));}public static void addServer(String ip, String port) {for (int i = 0; i < VIRTUAL_NODES; i++){String virtualNodeName = ip + ":" + port + "&&VN" + String.valueOf(i);int hash = getHash(virtualNodeName);System.out.println("虛擬節點[" + virtualNodeName + "]被添加, hash值為" + hash);virtualNodes.put(hash, virtualNodeName);}serverConnectMap.put(ip+":"+port, new Jedis(ip, Integer.parseInt(port)));}public String get(String key) {String server = getServer(key);Jedis serverConnector = serverConnectMap.get(server);if (serverConnector.get(key) == null) {System.out.println(key + "not in host: " + server);}return serverConnector.get(key);}public void set(String key, String value) {String server = getServer(key);Jedis serverConnector = serverConnectMap.get(server);serverConnector.set(key, value);System.out.println("set " + key + " into host: " + server);}public void flushdb() {for (String str: serverConnectMap.keySet()) {System.out.println("清空host: " + str);serverConnectMap.get(str).flushDB();}}public float targetPercent(List<String> keyList) {int mingzhong = 0;for (String key: keyList) {String server = getServer(key);Jedis serverConnector = serverConnectMap.get(server);if (serverConnector.get(key) != null) {mingzhong++;}}return (float) mingzhong / keyList.size();}}public class ConsistencyHashDemo {public static void main(String[] args) {JedisProxy jedis = new JedisProxy();jedis.flushdb();List<String> keyList = new ArrayList<>();for (int i=0; i<100000; i++) {keyList.add(Integer.toString(i));jedis.set(Integer.toString(i), "value");}System.out.println("target percent before add a server node: " + jedis.targetPercent(keyList));JedisProxy.addServer("localhost", "6383");System.out.println("target percent after add a server node: " + jedis.targetPercent(keyList));} } 復制代碼

以上代碼對參考文章進行了一些改進。

首先,參考文章的getServer方法會有些問題,當key大于最大的虛擬節點hash值時tailMap方法會返回空,找不到節點會報錯,其實這時應該去找hash值最小的一個虛擬節點。我加了處理,把這個環連上了。

下面getHash方法為FNV1_32_HASH算法,可以不用太在意。

VIRTUAL_NODES的值比較重要,當節點數目較少時,虛擬節點數目越大,命中率越高。

在程序設計上也有很大的不同,我寫了JedisProxy類,來做為client訪問Redis的中間層,在該類的static塊中利用服務器節點生成虛擬節點構造好紅黑樹,getServer里根據tailMap方法取出實際節點的地址,再由實際節點的地址直接拿到jedis對象,提供簡單的get與set方法,先根據key拿特定的jedis對象,再進行get, set操作。

addServer靜態方法給了其動態擴容的能力,可以看到在main方法中,通過調用JedisProxy.addServer("localhost", "6383")便直接增加了節點,不需要停應用。 targetPercent方法是用來統計命中率用。

當虛擬節點為5時,命中率約為60%左右,把它加大到100后,可以到達預期的80%的命中率。

好的,完美。

轉載于:https://juejin.im/post/5c52cfcc51882542ff129941

總結

以上是生活随笔為你收集整理的Redis集群一致性Hash效果的代码演示的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。