RedisCluster模式hashtag使用

基础补充文档:

Redis集群模式介绍:

https://www.cnblogs.com/zhonglongbo/p/13128955.html

Redis主流集群模式:

主从模式、哨兵模式、集群模式

遇到的问题:

使用Rediscluster模式集群,出现单点热点key,既集群中N个数据节点中,单一节点承接了这个场景下的所有流量;导致单节点cpu飙升到60%,其余节点cpu10%;

这个场景是geohash运算,属于读流量中的cpu密集型操作;

为了分散读流量到各个节点,我们选择了,基于当前key数据生成N-1个副本的策略;

Rediscluster 集群模式的工作原理:

分片策略:

Redis 集群将数据划分为16384(2的14次方)个哈希槽(slots),每个节点负责其中一部分槽位。当客户端发起请求时,根据键(key)的CRC16值进行哈希计算,然后对16384取模,得到对应的槽位索引,从而确定请求应该发送到哪个节点。

以下图为例,该集群有4个 Redis 节点,每个节点负责集群中的一部分数据,数据量可以不均匀。比如性能好的实例节点可以多分担一些压力。


哈希槽(slots)的划分

这个前面已经说过了,我们会将整个Redis数据库划分为16384个哈希槽,你的Redis集群可能有n个实例节点,每个节点可以处理0个 到至多 16384 个槽点,这些节点把 16384个槽位瓜分完成。

而你实际存储的Redis键值信息也必然归属于这 16384 个槽的其中一个。slots 与 Redis Key 的映射是通过以下两个步骤完成的:

使用 CRC16 算法计算键值对信息的Key,会得出一个 16 bit 的值。

将 第1步中得到的 16 bit 的值对 16384 取模,得到的值会在 0 ~ 16383 之间,映射到对应到哈希槽中。

当然,可能在一些特殊的情况下,你想把某些key固定到某个slot上面,也就是同一个实例节点上。这时候可以用hash tag能力,强制 key 所归属的槽位等于 tag 所在的槽位。

其实现方式为在key中加个{},例如test_key{1}。使用hash tag后客户端在计算key的crc16时,只计算{}中数据。如果没使用hash tag,客户端会对整个key进行crc16计算。下面演示下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 后会对应到通一个hash slot:1024中。

哈希槽(slots)的映射

一种是初始化的时候均匀分配 ,使用 cluster create 创建,会将 16384 个slots 平均分配在我们的集群实例上,比如你有n个节点,那每个节点的槽位就是 16384 / n 个了 。

另一种是通过 CLUSTER MEET 命令将 node1、node2、ndoe3、node4 4个节点联通成一个集群,刚联通的时候因为还没分配哈希槽,还是处于offline状态。我们使用 cluster addslots 命令来指定。

指定的好处就是性能好的实例节点可以多分担一些压力。

可以通过 addslots 命令指定哈希槽范围,比如下图中,我们哈希槽是这么分配的:实例 1 管理 0 ~ 7120 哈希槽,实例 2 管理 7121~9945 哈希槽,实例 3 管理 9946 ~ 13005 哈希槽,实例 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 实例之间的映射关系如下:


key testkey_1 和 testkey_2 经过 CRC16 计算后再对slots的总个数 16384 取模,结果分别匹配到了 cache1 和 cache3 上。

补充知识点:为什么选择0-16383即16384个槽位?

计算公式 HASH_SLOT = RCR16(key) mod 16384

如果槽位为65536(2^16),发送心跳信息的消息头达8k,发送的心跳包过于庞大。

在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb 

在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb 

因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。

redis的集群主节点数量基本不可能超过1000个。

集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。

槽位越小,节点少的情况下,压缩比高,容易传输

Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。 

通过java代码计算槽位与节点的数据:

        // 设定一个容器对象

        HashMap<@Nullable Object, @Nullable Object> objectObjectHashMap = Maps.newHashMap();

        // 初始化设定有32个节点的RedisCluster集群

        int n = 32;

        for (int i = 0; i < 200; i++) {

            // 利用JedisClusterCRC16 计算 key的hashtag:"123:{1}" 属于哪一个slot

            int slot = JedisClusterCRC16.getSlot("123:{" + i + "}");

            for (int j = 0; j < n; j++) {

                // 计算当前slot 属于哪一个 node;

                if (slot > j * (16383 / n) && slot < (j + 1) * (16383 / n)) {

                    // 以下计算,只记录每个节点从小到大第一个明确的hashtag分配的数据

                    if (Objects.isNull(objectObjectHashMap.get(j + 1))) {

                        objectObjectHashMap.put(j + 1, i + "---" + slot);

                        if (objectObjectHashMap.size() >= n) {

                            outer:

                            break;

                        }

                    }

                }

            }

        }

        // 随机获取当前 key hashtag对应的 node和slot

        // 下面的代码也可用于 随机分发查询流量,用于组装一个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));

            }

        }

补充知识点:hashtag

在Redis中,hashtag(哈希标签)是一个用于数据分片的机制,它允许将多个键映射到同一个哈希槽中。这是通过在键的名称中包含一对大括号({})来实现的,大括号内可以包含任意字符串。Redis集群使用CRC16算法对键进行哈希,然后对16384取模,以此确定键应该存储在哪个槽位上。

使用hashtag的目的是为了在集群环境中能够对一组相关的键执行原子操作。例如,如果你有一个用户信息存储在Redis中,可能包含用户的姓名、邮箱、年龄等多个字段,你可以使用hashtag将这些字段组织在一起,如下所示:

user{123}:username

user{123}:email

user{123}:age

在这个例子中,user{123}是哈希标签,它确保所有以user{123}:开头的键都会被分配到同一个哈希槽中。这意味着,即使用户的数据分布在不同的Redis节点上,你也可以通过一个命令来同时操作这些键,例如使用DEL命令删除一个用户的所有信息:

DEL user{123}:username user{123}:email user{123}:age

这种方式在Redis集群中特别有用,因为它允许跨多个节点执行命令,而不需要客户端知道每个键具体存储在哪个节点上。Redis集群会自动将命令路由到正确的节点,并执行相应的操作。

需要注意的是,hashtag只在Redis集群模式下有效,它不会影响非集群模式下的键。此外,hashtag的使用也需要谨慎,因为它可能会导致数据倾斜,如果不正确地使用,可能会将大量的键映射到同一个槽位,从而影响集群的负载均衡。

Redis源码关于hashtag计算:

源码:

源码有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;

    // 若无{,则s等于keylen

    for (s = 0; s < keylen; s++)

        // 遇到第一个{跳出

        if (key[s] == '{') break;

    // 若key中无{,则s等于keylen,整个key参与hash

    // 0x3FFF对应10进制为16383

    // 16383对应二进制为14个1

    // 按位与运算时只取crc16结果的低14位

    if (s == keylen) return crc16(key,keylen) & 0x3FFF;


    // 若key中有{,查看是否有}

    // 若key中无},则e等于keylen,整个key参与hash

    for (e = s+1; e < keylen; e++)

        // 遇到第一个}跳出

        if (key[e] == '}') break;


    // key中无},整个key参与hash

    // key中有},但{}之间为空,整个key参与hash

    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;

    // {}中间部分参与hash

    // key+s+1 指针操作,向右移动s+1

    // e-s-1为{}中间字符串的长度

    return crc16(key+s+1,e-s-1) & 0x3FFF;

}

hashtag场景注意事项介绍:

1.仅{...}里的部分参与hash。

2.如果有多个花括号,从左向右,取第一个花括号中的内容进行hash。

3.如果第一个花括号中内容为空如:a{}c{d},则整个key参与hash。

4.相同的hashtag被分配到相同的节点,相同的槽。

hash算法采用crc16。crc16算法为redis自己封装的,源码位置:https://github.com/redis/redis/blob/6.2.6/src/crc16.c


hashtag使用中的缺点:

在Redis集群中使用hashtag虽然提供了一定的便利性,但也存在一些缺点。以下是根据搜索结果得出的hashtag的主要缺点:

1. 数据倾斜:

使用hashtag可能会导致数据集中在集群的某个实例中,造成数据倾斜。例如,如果大量使用相同的hashtag,可能会导致所有相关的数据都被存储在同一个节点上。这种情况可能会影响集群的负载均衡,使得单个节点承受过大的压力,而其他节点资源未被充分利用。

2. 影响集群性能:

当大量数据因为hashtag而被集中存储在同一节点时,可能会影响该节点的性能,尤其是在高并发场景下,节点可能会成为瓶颈,导致响应速度变慢。

3. 迁移和扩展困难:

在使用hashtag后,如果需要对集群进行扩展或缩减,数据迁移可能会变得复杂。因为需要确保hashtag相关的数据在迁移过程中保持一致性,这可能需要额外的人工干预和复杂的数据迁移策略。

4. 限制批量操作:

在Redis集群中,批量操作(如pipeline)要求所有涉及的key必须位于同一个槽位中。如果使用hashtag导致数据分布在不同的槽位,将无法执行批量操作,这限制了某些操作的执行。

5. 增加复杂性:

引入hashtag机制增加了Redis集群使用和管理的复杂性。开发者和运维人员需要对hashtag的工作原理有深入的理解,才能有效地避免潜在的问题。

6. 热点问题:

hashtag可能导致某些key成为热点key,即频繁访问的key。当这些热点key集中在同一节点时,可能会导致该节点过载,影响整个集群的性能和稳定性。

综上所述:

在使用hashtag时,需要权衡其带来的便利性和可能引发的问题,合理规划数据分片策略,以确保集群的健康运行和数据的均衡分布。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容