如何应对 Redis 集群的数据倾斜问题

前言

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

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

数据倾斜的种类

数据倾斜有两类

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

数据量倾斜的成因及应对方法

当数据量倾斜发生时,数据在切片集群的多个实例上分布不均衡,大量数据集中到了一个或几个实例上。

那么,数据量倾斜是怎么产生的呢?这主要有三个原因,分别是某个实例上保存了 bigkey、Slot 分配不均衡以及 Hash Tag。

bigkey

某个实例上正好保存了 bigkey。bigkey 的 value 值很大(String 类型),或者是 bigkey 保存了大量集合元素(集合类型),会导致这个实例的数据量增加,内存资源消耗也相应增加。

一个根本的应对方法是,我们在业务层生成数据时,要尽量避免把过多的数据保存在同一个键值对中

此外,如果 bigkey 正好是集合类型,我们还有一个方法,就是把 bigkey 拆分成很多个小的集合类型数据,分散保存在不同的实例上。

Slot 分配不均衡

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

为了应对这个问题,我们可以通过运维规范,在分配之前,我们就要避免把过多的 Slot 分配到同一个实例。

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

Slot 迁移

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

在 Redis Cluster 中,我们可以使用 3 个命令完成 Slot 迁移。

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

HashTag

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

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

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

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

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

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

一旦热点数据被存在了某个实例中,那么,这个实例的请求访问量就会远高于其它实例,面临巨大的访问压力。

通常来说,热点数据以服务读操作为主,在这种情况下,我们可以采用热点数据多副本的方法来应对。

这个方法的具体做法是,我们把热点数据复制多份,在每一个数据副本的 key 中增加一个随机前缀,让它和其它副本数据不会被映射到同一个 Slot 中。这样一来,热点数据既有多个副本可以同时服务请求,同时,这些副本数据的 key 又不一样,会被映射到不同的 Slot 中。

在给这些 Slot 分配实例时,我们也要注意把它们分配到不同的实例上,那么,热点数据的访问压力就被分散到不同的实例上了。

热点数据多副本方法只能针对只读的热点数据。如果热点数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会带来额外的开销。

对于有读有写的热点数据,我们就要给实例本身增加资源了,例如使用配置更高的机器,来应对大量的访问压力。

总结

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

猜你喜欢

转载自blog.csdn.net/Java_LingFeng/article/details/128788849