37 Redis 数据倾斜的成因和应对方法


前言

在切片集群中,数据会按照一定的分布规则分散到不同的实例上保存。比如在使用 Redis Cluster 或 Codis 时,数据都会先按照 CRC 算法的计算值对 Slot(逻辑槽)取模, 所有的 Slot 又会由运维管理员分配到不同的实例上,数据就被保存到相应的实例上了。

虽然这种方法实现起来比较简单,但是很容易导致一个问题:数据倾斜。

数据倾斜有两类:

  1. 数据量倾斜:实例上的数据分布不均衡,某个实例上的数据特别多。
  2. 数据访问倾斜:虽然每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁。

如果发生了数据倾斜,那么保存了大量数据,或者是保存了热点数据的实例的处理压力就会增大,速度变慢,甚至还可能会引起这个实例的内存资源耗尽,从而崩溃。

一、数据量倾斜的成因和应对方法

当数据量倾斜发生时,数据在切片集群的多个实例上分布不均衡,大量数据集中到了一个或几个实例上,如下图所示:
在这里插入图片描述

数据量倾斜主要有三个原因,分别是某个实例上保存了 bigkey、Slot 分配不均衡以及 Hash Tag。

bigkey 导致倾斜

bigkey 的 value 值很大(String 类型),或者是 bigkey 保存了大量集合元素(集合类型),会导致这个实例的数据量增加, 内存资源消耗也相应增加。而且 bigkey 的操作一般都会造成实例 IO 线程阻塞,如果 bigkey 的访问量比较大,就会影响到这个实例上的其它请求被处理的速度。

为了避免 bigkey 造成的数据倾斜,在业务层生成数据时,不把过多的数据保存在同一个键值对中。

如果 bigkey 正好是集合类型,可以把 bigkey 拆分成很多个小的集合类型数据,分散保存在不同的实例上。

假设 Hash 类型集合 user:info 保存了 100 万个用户的信息,是一个 bigkey。可以按照用户 ID 的范围,把这个集合拆分成 10 个小集合,每个小集合只保存 10 万个用户的信息。这样可以把一个 bigkey 化整为零、分散保存了,避免了 bigkey 给单个切片实例带来的访问压力。
需要注意的是,当 bigkey 访问量较大时,也会造成数据访问倾斜。

Slot 分配不均衡导致倾斜

如果没有均衡地分配 Slot,就会有大量的数据被分配到同一个 Slot 中,而同一个 Slot 只会在一个实例上分布,会导致大量数据被集中到一个实例上,造成数据倾斜。

假设 Redis Cluster 一共有 16384 个 Slot,假设集群一共有 5 个实例,其中实例 1 的硬件配置较高,运维人员在给实例分配 Slot 时,可能会给实例 1 多分配些 Slot,把实例 1 的资源充分利用起来。

但是不知道数据和 Slot 的对应关系,这种做法就可能会导致大量数据正好被映射到实例 1 上的 Slot,造成数据倾斜,给实例 1 带来访问压力。

可以通过运维规范,在分配之前避免把过多的 Slot 分配到同一个实例。如果是已经分配好 Slot 的集群,可以先查看 Slot 和实例的具体分 配关系,从而判断是否有过多的 Slot 集中到了同一个实例。如果有的话,就将部分 Slot 迁移到其它实例,从而避免数据倾斜。

不同集群上查看 Slot 分配情况的方式不同:如果是 Redis Cluster,就用 CLUSTER SLOTS 命令;如果是 Codis,就可以在 codis dashboard 上查看。

比如执行 CLUSTER SLOTS 命令查看 Slot 分配情况。命令返回结果显示,Slot 0 到 Slot 4095 被分配到了实例 192.168.10.3 上,而 Slot 12288 到 Slot 16383 被分配到 了实例 192.168.10.5 上:

127.0.0.1:6379> cluster slots 
1) 1) (integer) 0 
	2) (integer) 4095 
	3) 1) "192.168.10.3" 
	   2) (integer) 6379 
2) 1) (integer) 12288 
	2) (integer) 16383 
	3) 1) "192.168.10.5" 
	   2) (integer) 6379 

如果某一个实例上有太多的 Slot,可以使用迁移命令把这些 Slot 迁移到其它实例上。在 Redis Cluster 中,可以使用 3 个命令完成 Slot 迁移:

  1. CLUSTER SETSLOT:使用不同的选项进行三种设置,分别是设置 Slot 要迁入的目标实例,Slot 要迁出的源实例,以及 Slot 所属的实例。
  2. CLUSTER GETKEYSINSLOT:获取某个 Slot 中一定数量的 key。
  3. MIGRATE:把一个 key 从源实例实际迁移到目标实例。

假设要把 Slot 300 从源实例(ID 为 3)迁移到目标实例(ID 为 5)分成 5 步:

  1. 先在目标实例 5 上执行下面的命令,将 Slot 300 的源实例设置为实例 3,表示要从实例 3 上迁入 Slot 300。:
CLUSTER SETSLOT 300 IMPORTING 3
  1. 在源实例 3 上,把 Slot 300 的目标实例设置为 5,表示 Slot 300 要迁出到实例 5 上:
CLUSTER SETSLOT 300 MIGRATING 5
  1. 从 Slot 300 中获取 100 个 key。因为 Slot 中的 key 数量可能很多,所以需要在客户端上多次执行下面的这条命令,分批次获得并迁移 key:
CLUSTER GETKEYSINSLOT 300 100
  1. 把刚才获取的 100 个 key 中的 key1 迁移到目标实例 5 上(IP 为 192.168.10.5),同时把要迁入的数据库设置为 0 号数据库,把迁移的超时时间设置为timeout。重复执行 MIGRATE 命令,把 100 个 key 都迁移完:
MIGRATE 192.168.10.5 6379 key1 0 timeout
  1. 重复执行第 3 和第 4 步,直到 Slot 中的所有 key 都迁移完成:

从 Redis 3.0.6 开始,也可以使用 KEYS 选项,一次迁移多个 key(key1、2、3),这样可以提升迁移效率:

MIGRATE 192.168.10.5 6379 "" 0 timeout KEYS key1 key2 key3

Codis 执行下面的命令进行数据迁移。把 dashboard 组件的连接地址设置为 ADDR,并且把 Slot 300 迁移到编号为 6 的 codis server group 上:

codis-admin --dashboard=ADDR -slot-action --create --sid=300 --gid=6

Hash Tag 导致倾斜

Hash Tag 是指加在键值对 key 中的一对花括号{}。这对括号会把 key 的一部分括起来, 客户端在计算 key 的 CRC16 值时,只对 Hash Tag 花括号中的 key 内容进行计算。如果没用 Hash Tag 的话,客户端计算整个 key 的 CRC16 的值。

假设 key 是 user:profile:3231,把其中的 3231 作为 Hash Tag,key 就变成了 user:profile:{3231}。当客户端计算这个 key 的 CRC16 值时,就只会计算 3231 的 CRC16 值。否则客户端会计算整个“user:profile:3231”的 CRC16 值。

使用 Hash Tag 的好处是,如果不同 key 的 Hash Tag 内容都是一样的,那么这些 key 对应的数据会被映射到同一个 Slot 中,同时会被分配到同一个实例上。

使用 Hash Tag 后,数据被映射到相同 Slot 的情况:

在这里插入图片描述

user:profile:{3231}和 user:order:{3231}的 Hash Tag 一样,都是 3231,它们的 CRC16 计算值对 16384 取模后的值也是一样的,所以就对应映射到了相同的 Slot 1024 中。user:profile:{5328}和 user:order:{5328}也是相同的映射结果。

Hash Tag 主要是用在 Redis Cluster 和 Codis 中,支持事务操作和范围查询。因为 Redis Cluster 和 Codis 本身并不支持跨实例的事务操作和范围查询,当业务应用有这些需求时,就只能先把这些数据读取到业务层进行事务处理,或者是逐个查询每个实例,得到范围查询的结果。

这样操作起来非常麻烦,所以使用 Hash Tag 把要执行事务操作或是范围查询的数据映射到同一个实例上,能很轻松地实现事务或范围查询了。

但是使用 Hash Tag 的潜在问题是大量的数据可能被集中到一个实例上,导致数据倾斜,集群中的负载不均衡。应对这种问题,需要在范围查询、事务执行的需求和数据倾斜带来的访问压力之间,进行取舍了。

建议如果使用 Hash Tag 进行切片的数据会带来较大的访问压力,就优先考虑避免数据倾斜,最好不要使用 Hash Tag 进行数据切片。因为事务和范围查询都还可以放在客户端来执行,而数据倾斜会导致实例不稳定,造成服务不可用。

二、数据访问倾斜的成因和应对方法

发生数据访问倾斜的根本原因,是实例上存在热点数据(比如新闻应用中的热点新闻内容、电商促销活动中的热门商品信息,等等)。
一旦热点数据被存在了某个实例中,这个实例的请求访问量就会远高于其它实例, 面临巨大的访问压力,如下图所示:
在这里插入图片描述

和数据量倾斜不同,热点数据通常是一个或几个数据,所以直接重新分配 Slot 并不能解决热点数据的问题。

热点数据以服务读操作为主,采用热点数据多副本的方法来应对。 把热点数据复制多份,在每一个数据副本的 key 中增加一个随机前缀,让它和其它副本数据不会被映射到同一个 Slot 中。热点数据既有多个副本可以同时服务请求,同时这些副本数据的 key 又不一样,会被映射到不同的 Slot 中。在给这些 Slot 分配实例时,要注意把它们分配到不同的实例上,热点数据的访问压力就被分散到不同的实例上了。

需要注意热点数据多副本方法只能针对只读的热点数据。如果热点数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会带来额外的开销。对于有读有写的热点数据,就要给实例本身增加资源了,例如使用配置更高的机器, 来应对大量的访问压力。

总结

数据倾斜的两种情况:数据量倾斜和数据访问倾斜。

造成数据量倾斜的原因主要有三个:

  1. 数据中有 bigkey,导致某个实例的数据量增加;
  2. Slot 手工分配不均,导致某个或某些实例上有大量数据;
  3. 使用了 Hash Tag,导致数据集中到某些实例上。

造成数据访问倾斜的主要原因是有热点数据存在,导致大量访问请求集中到了热点数据所在的实例上。

应对数据倾斜问题的四个方法,也分别对应了造成数据倾斜的四个原因。

在这里插入图片描述

如果已经发生了数据倾斜,通过数据迁移来缓解数据倾斜的影响。Redis Cluster 和 Codis 集群都提供了查看 Slot 分配和手工迁移 Slot 的命令。

集群的实例资源配置建议:在构建切片集群时,尽量使用大小配置相同的实例(例如实例内存配置保持相同),避免因实例资源不均衡而在不同实例上分配不同数量的 Slot。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海陆云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值