基礎(chǔ)補(bǔ)充文檔:
Redis集群模式介紹:
https://www.cnblogs.com/zhonglongbo/p/13128955.html
Redis主流集群模式:
主從模式、哨兵模式、集群模式
遇到的問(wèn)題:
使用Rediscluster模式集群,出現(xiàn)單點(diǎn)熱點(diǎn)key,既集群中N個(gè)數(shù)據(jù)節(jié)點(diǎn)中么库,單一節(jié)點(diǎn)承接了這個(gè)場(chǎng)景下的所有流量;導(dǎo)致單節(jié)點(diǎn)cpu飆升到60%甘有,其余節(jié)點(diǎn)cpu10%诉儒;
這個(gè)場(chǎng)景是geohash運(yùn)算,屬于讀流量中的cpu密集型操作亏掀;
為了分散讀流量到各個(gè)節(jié)點(diǎn)忱反,我們選擇了,基于當(dāng)前key數(shù)據(jù)生成N-1個(gè)副本的策略滤愕;
Rediscluster?集群模式的工作原理:
分片策略:
Redis 集群將數(shù)據(jù)劃分為16384(2的14次方)個(gè)哈希槽(slots)温算,每個(gè)節(jié)點(diǎn)負(fù)責(zé)其中一部分槽位。當(dāng)客戶端發(fā)起請(qǐng)求時(shí)间影,根據(jù)鍵(key)的CRC16值進(jìn)行哈希計(jì)算注竿,然后對(duì)16384取模,得到對(duì)應(yīng)的槽位索引魂贬,從而確定請(qǐng)求應(yīng)該發(fā)送到哪個(gè)節(jié)點(diǎn)巩割。
以下圖為例,該集群有4個(gè) Redis 節(jié)點(diǎn)付燥,每個(gè)節(jié)點(diǎn)負(fù)責(zé)集群中的一部分?jǐn)?shù)據(jù)宣谈,數(shù)據(jù)量可以不均勻。比如性能好的實(shí)例節(jié)點(diǎn)可以多分擔(dān)一些壓力键科。
哈希槽(slots)的劃分
這個(gè)前面已經(jīng)說(shuō)過(guò)了闻丑,我們會(huì)將整個(gè)Redis數(shù)據(jù)庫(kù)劃分為16384個(gè)哈希槽,你的Redis集群可能有n個(gè)實(shí)例節(jié)點(diǎn)勋颖,每個(gè)節(jié)點(diǎn)可以處理0個(gè) 到至多 16384 個(gè)槽點(diǎn)梆掸,這些節(jié)點(diǎn)把 16384個(gè)槽位瓜分完成。
而你實(shí)際存儲(chǔ)的Redis鍵值信息也必然歸屬于這 16384 個(gè)槽的其中一個(gè)牙言。slots 與 Redis Key 的映射是通過(guò)以下兩個(gè)步驟完成的:
使用 CRC16 算法計(jì)算鍵值對(duì)信息的Key酸钦,會(huì)得出一個(gè) 16 bit 的值。
將 第1步中得到的 16 bit 的值對(duì) 16384 取模,得到的值會(huì)在 0 ~ 16383 之間卑硫,映射到對(duì)應(yīng)到哈希槽中徒恋。
當(dāng)然,可能在一些特殊的情況下欢伏,你想把某些key固定到某個(gè)slot上面入挣,也就是同一個(gè)實(shí)例節(jié)點(diǎn)上。這時(shí)候可以用hash tag能力硝拧,強(qiáng)制 key 所歸屬的槽位等于 tag 所在的槽位径筏。
其實(shí)現(xiàn)方式為在key中加個(gè){},例如test_key{1}障陶。使用hash tag后客戶端在計(jì)算key的crc16時(shí)滋恬,只計(jì)算{}中數(shù)據(jù)。如果沒使用hash tag抱究,客戶端會(huì)對(duì)整個(gè)key進(jìn)行crc16計(jì)算恢氯。下面演示下hash tag使用:
127.0.0.1:6380> cluster keyslot user:case{1}
(integer) 1024
127.0.0.1:6380> cluster keyslot user:favor
(integer) 1023
127.0.0.1:6380> cluster keyslot user:info{1}
(integer) 1024
如上,使用hash tag 后會(huì)對(duì)應(yīng)到通一個(gè)hash slot:1024中鼓寺。
哈希槽(slots)的映射
一種是初始化的時(shí)候均勻分配 勋拟,使用 cluster create 創(chuàng)建,會(huì)將 16384 個(gè)slots 平均分配在我們的集群實(shí)例上妈候,比如你有n個(gè)節(jié)點(diǎn)敢靡,那每個(gè)節(jié)點(diǎn)的槽位就是 16384 / n 個(gè)了 。
另一種是通過(guò) CLUSTER MEET 命令將 node1苦银、node2醋安、ndoe3、node4 4個(gè)節(jié)點(diǎn)聯(lián)通成一個(gè)集群墓毒,剛聯(lián)通的時(shí)候因?yàn)檫€沒分配哈希槽,還是處于offline狀態(tài)亲怠。我們使用?cluster addslots?命令來(lái)指定所计。
指定的好處就是性能好的實(shí)例節(jié)點(diǎn)可以多分擔(dān)一些壓力。
可以通過(guò) addslots 命令指定哈希槽范圍团秽,比如下圖中主胧,我們哈希槽是這么分配的:實(shí)例 1 管理 0 ~ 7120 哈希槽,實(shí)例 2 管理 7121~9945 哈希槽习勤,實(shí)例 3 管理 9946 ~ 13005 哈希槽踪栋,實(shí)例 4 管理 13006 ~ 16383 哈希槽。
redis-cli -h 192.168.0.1 –p 6379 cluster addslots 0,7120
redis-cli -h 192.168.0.2 –p 6379 cluster addslots 7121,9945
redis-cli -h 192.168.0.3 –p 6379 cluster addslots 9946,13005
redis-cli -h 192.168.0.4 –p 6379 cluster addslots 13006,16383
slots 和 Redis 實(shí)例之間的映射關(guān)系如下:
key?testkey_1?和?testkey_2?經(jīng)過(guò) CRC16 計(jì)算后再對(duì)slots的總個(gè)數(shù) 16384 取模图毕,結(jié)果分別匹配到了 cache1 和 cache3 上夷都。
補(bǔ)充知識(shí)點(diǎn):為什么選擇0-16383即16384個(gè)槽位?
計(jì)算公式 HASH_SLOT = RCR16(key) mod 16384
如果槽位為65536(2^16)予颤,發(fā)送心跳信息的消息頭達(dá)8k囤官,發(fā)送的心跳包過(guò)于龐大冬阳。
在消息頭中最占空間的是myslots[CLUSTER_SLOTS/8]。 當(dāng)槽位為65536時(shí)党饮,這塊的大小是: 65536÷8÷1024=8kb?
在消息頭中最占空間的是myslots[CLUSTER_SLOTS/8]肝陪。 當(dāng)槽位為16384時(shí),這塊的大小是: 16384÷8÷1024=2kb?
因?yàn)槊棵腌娦趟常瑀edis節(jié)點(diǎn)需要發(fā)送一定數(shù)量的ping消息作為心跳包氯窍,如果槽位為65536,這個(gè)ping消息的消息頭太大了蹲堂,浪費(fèi)帶寬狼讨。
redis的集群主節(jié)點(diǎn)數(shù)量基本不可能超過(guò)1000個(gè)。
集群節(jié)點(diǎn)越多贯城,心跳包的消息體內(nèi)攜帶的數(shù)據(jù)越多熊楼。如果節(jié)點(diǎn)過(guò)1000個(gè),也會(huì)導(dǎo)致網(wǎng)絡(luò)擁堵能犯。因此redis作者不建議redis cluster節(jié)點(diǎn)數(shù)量超過(guò)1000個(gè)鲫骗。 那么,對(duì)于節(jié)點(diǎn)數(shù)在1000以內(nèi)的redis cluster集群踩晶,16384個(gè)槽位夠用了执泰。沒有必要拓展到65536個(gè)。
槽位越小渡蜻,節(jié)點(diǎn)少的情況下术吝,壓縮比高,容易傳輸
Redis主節(jié)點(diǎn)的配置信息中它所負(fù)責(zé)的哈希槽是通過(guò)一張bitmap的形式來(lái)保存的茸苇,在傳輸過(guò)程中會(huì)對(duì)bitmap進(jìn)行壓縮排苍,但是如果bitmap的填充率slots / N很高的話(N表示節(jié)點(diǎn)數(shù)),bitmap的壓縮率就很低学密。 如果節(jié)點(diǎn)數(shù)很少淘衙,而哈希槽數(shù)量很多的話,bitmap的壓縮率就很低腻暮。?
通過(guò)java代碼計(jì)算槽位與節(jié)點(diǎn)的數(shù)據(jù):
? ? ? ? // 設(shè)定一個(gè)容器對(duì)象
? ? ? ? HashMap<@Nullable Object, @Nullable Object> objectObjectHashMap = Maps.newHashMap();
? ? ? ? // 初始化設(shè)定有32個(gè)節(jié)點(diǎn)的RedisCluster集群
? ? ? ? int n = 32;
? ? ? ? for (int i = 0; i < 200; i++) {
? ? ? ? ? ? // 利用JedisClusterCRC16 計(jì)算 key的hashtag:"123:{1}" 屬于哪一個(gè)slot
? ? ? ? ? ? int slot = JedisClusterCRC16.getSlot("123:{" + i + "}");
? ? ? ? ? ? for (int j = 0; j < n; j++) {
? ? ? ? ? ? ? ? // 計(jì)算當(dāng)前slot 屬于哪一個(gè) node彤守;
? ? ? ? ? ? ? ? if (slot > j * (16383 / n) && slot < (j + 1) * (16383 / n)) {
? ? ? ? ? ? ? ? ? ? // 以下計(jì)算,只記錄每個(gè)節(jié)點(diǎn)從小到大第一個(gè)明確的hashtag分配的數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? if (Objects.isNull(objectObjectHashMap.get(j + 1))) {
? ? ? ? ? ? ? ? ? ? ? ? objectObjectHashMap.put(j + 1, i + "---" + slot);
? ? ? ? ? ? ? ? ? ? ? ? if (objectObjectHashMap.size() >= n) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? outer:
? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 隨機(jī)獲取當(dāng)前 key hashtag對(duì)應(yīng)的 node和slot
? ? ? ? // 下面的代碼也可用于 隨機(jī)分發(fā)查詢流量哭靖,用于組裝一個(gè)key
? ? ? ? if(!objectObjectHashMap.isEmpty()) {
? ? ? ? ? ? for (int j = 0; j < 100; j++) {
? ? ? ? ? ? ? ? ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
? ? ? ? ? ? ? ? int i = threadLocalRandom.nextInt(n);
? ? ? ? ? ? ? ? System.out.println(i+1);
? ? ? ? ? ? ? ? System.out.println(objectObjectHashMap.get(++i));
? ? ? ? ? ? }
? ? ? ? }
補(bǔ)充知識(shí)點(diǎn):hashtag
在Redis中具垫,hashtag(哈希標(biāo)簽)是一個(gè)用于數(shù)據(jù)分片的機(jī)制,它允許將多個(gè)鍵映射到同一個(gè)哈希槽中试幽。這是通過(guò)在鍵的名稱中包含一對(duì)大括號(hào)({})來(lái)實(shí)現(xiàn)的筝蚕,大括號(hào)內(nèi)可以包含任意字符串。Redis集群使用CRC16算法對(duì)鍵進(jìn)行哈希,然后對(duì)16384取模饰及,以此確定鍵應(yīng)該存儲(chǔ)在哪個(gè)槽位上蔗坯。
使用hashtag的目的是為了在集群環(huán)境中能夠?qū)σ唤M相關(guān)的鍵執(zhí)行原子操作。例如燎含,如果你有一個(gè)用戶信息存儲(chǔ)在Redis中宾濒,可能包含用戶的姓名、郵箱屏箍、年齡等多個(gè)字段绘梦,你可以使用hashtag將這些字段組織在一起,如下所示:
user{123}:username
user{123}:email
user{123}:age
在這個(gè)例子中赴魁,user{123}是哈希標(biāo)簽卸奉,它確保所有以u(píng)ser{123}:開頭的鍵都會(huì)被分配到同一個(gè)哈希槽中。這意味著颖御,即使用戶的數(shù)據(jù)分布在不同的Redis節(jié)點(diǎn)上榄棵,你也可以通過(guò)一個(gè)命令來(lái)同時(shí)操作這些鍵,例如使用DEL命令刪除一個(gè)用戶的所有信息:
DEL user{123}:username user{123}:email user{123}:age
這種方式在Redis集群中特別有用潘拱,因?yàn)樗试S跨多個(gè)節(jié)點(diǎn)執(zhí)行命令疹鳄,而不需要客戶端知道每個(gè)鍵具體存儲(chǔ)在哪個(gè)節(jié)點(diǎn)上。Redis集群會(huì)自動(dòng)將命令路由到正確的節(jié)點(diǎn)芦岂,并執(zhí)行相應(yīng)的操作瘪弓。
需要注意的是,hashtag只在Redis集群模式下有效禽最,它不會(huì)影響非集群模式下的鍵腺怯。此外,hashtag的使用也需要謹(jǐn)慎川无,因?yàn)樗赡軙?huì)導(dǎo)致數(shù)據(jù)傾斜呛占,如果不正確地使用,可能會(huì)將大量的鍵映射到同一個(gè)槽位懦趋,從而影響集群的負(fù)載均衡晾虑。
Redis源碼關(guān)于hashtag計(jì)算:
源碼:
源碼有2處。
第一處:
https://github.com/redis/redis/blob/6.2.6/src/redis-cli.c
line:3282
方法:clusterManagetKeyHashSlot
第二處:
https://github.com/redis/redis/blob/6.2.6/src/cluster.c
line:749
方法:keyHashSlot
// 源碼位置
// https://github.com/redis/redis/blob/6.2.6/src/cluster.c
unsigned int keyHashSlot(char *key, int keylen) {
? ? // s代表{在key中的位置愕够,e代表}在key中的位置
? ? int s, e;
? ? // 若無(wú){,則s等于keylen
? ? for (s = 0; s < keylen; s++)
? ? ? ? // 遇到第一個(gè){跳出
? ? ? ? if (key[s] == '{') break;
? ? // 若key中無(wú){佛猛,則s等于keylen惑芭,整個(gè)key參與hash
? ? // 0x3FFF對(duì)應(yīng)10進(jìn)制為16383
? ? // 16383對(duì)應(yīng)二進(jìn)制為14個(gè)1
? ? // 按位與運(yùn)算時(shí)只取crc16結(jié)果的低14位
? ? if (s == keylen) return crc16(key,keylen) & 0x3FFF;
? ? // 若key中有{,查看是否有}
? ? // 若key中無(wú)}继找,則e等于keylen遂跟,整個(gè)key參與hash
? ? for (e = s+1; e < keylen; e++)
? ? ? ? // 遇到第一個(gè)}跳出
? ? ? ? if (key[e] == '}') break;
? ? // key中無(wú)},整個(gè)key參與hash
? ? // key中有},但{}之間為空,整個(gè)key參與hash
? ? if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
? ? // {}中間部分參與hash
? ? // key+s+1 指針操作,向右移動(dòng)s+1
? ? // e-s-1為{}中間字符串的長(zhǎng)度
? ? return crc16(key+s+1,e-s-1) & 0x3FFF;
}
hashtag場(chǎng)景注意事項(xiàng)介紹:
1.僅{...}里的部分參與hash幻锁。
2.如果有多個(gè)花括號(hào)凯亮,從左向右,取第一個(gè)花括號(hào)中的內(nèi)容進(jìn)行hash哄尔。
3.如果第一個(gè)花括號(hào)中內(nèi)容為空如:a{}c28oce6y假消,則整個(gè)key參與hash。
4.相同的hashtag被分配到相同的節(jié)點(diǎn)岭接,相同的槽富拗。
hash算法采用crc16。crc16算法為redis自己封裝的鸣戴,源碼位置:https://github.com/redis/redis/blob/6.2.6/src/crc16.c
hashtag使用中的缺點(diǎn):
在Redis集群中使用hashtag雖然提供了一定的便利性啃沪,但也存在一些缺點(diǎn)。以下是根據(jù)搜索結(jié)果得出的hashtag的主要缺點(diǎn):
1.?數(shù)據(jù)傾斜:
使用hashtag可能會(huì)導(dǎo)致數(shù)據(jù)集中在集群的某個(gè)實(shí)例中窄锅,造成數(shù)據(jù)傾斜创千。例如,如果大量使用相同的hashtag入偷,可能會(huì)導(dǎo)致所有相關(guān)的數(shù)據(jù)都被存儲(chǔ)在同一個(gè)節(jié)點(diǎn)上追驴。這種情況可能會(huì)影響集群的負(fù)載均衡,使得單個(gè)節(jié)點(diǎn)承受過(guò)大的壓力盯串,而其他節(jié)點(diǎn)資源未被充分利用氯檐。
2.?影響集群性能:
當(dāng)大量數(shù)據(jù)因?yàn)閔ashtag而被集中存儲(chǔ)在同一節(jié)點(diǎn)時(shí),可能會(huì)影響該節(jié)點(diǎn)的性能体捏,尤其是在高并發(fā)場(chǎng)景下冠摄,節(jié)點(diǎn)可能會(huì)成為瓶頸,導(dǎo)致響應(yīng)速度變慢几缭。
3.?遷移和擴(kuò)展困難:
在使用hashtag后河泳,如果需要對(duì)集群進(jìn)行擴(kuò)展或縮減,數(shù)據(jù)遷移可能會(huì)變得復(fù)雜年栓。因?yàn)樾枰_保hashtag相關(guān)的數(shù)據(jù)在遷移過(guò)程中保持一致性拆挥,這可能需要額外的人工干預(yù)和復(fù)雜的數(shù)據(jù)遷移策略。
4.?限制批量操作:
在Redis集群中某抓,批量操作(如pipeline)要求所有涉及的key必須位于同一個(gè)槽位中纸兔。如果使用hashtag導(dǎo)致數(shù)據(jù)分布在不同的槽位,將無(wú)法執(zhí)行批量操作否副,這限制了某些操作的執(zhí)行汉矿。
5.?增加復(fù)雜性:
引入hashtag機(jī)制增加了Redis集群使用和管理的復(fù)雜性。開發(fā)者和運(yùn)維人員需要對(duì)hashtag的工作原理有深入的理解备禀,才能有效地避免潛在的問(wèn)題洲拇。
6.?熱點(diǎn)問(wèn)題:
hashtag可能導(dǎo)致某些key成為熱點(diǎn)key奈揍,即頻繁訪問(wèn)的key。當(dāng)這些熱點(diǎn)key集中在同一節(jié)點(diǎn)時(shí)赋续,可能會(huì)導(dǎo)致該節(jié)點(diǎn)過(guò)載男翰,影響整個(gè)集群的性能和穩(wěn)定性。
綜上所述:
在使用hashtag時(shí)纽乱,需要權(quán)衡其帶來(lái)的便利性和可能引發(fā)的問(wèn)題蛾绎,合理規(guī)劃數(shù)據(jù)分片策略,以確保集群的健康運(yùn)行和數(shù)據(jù)的均衡分布迫淹。