跳至主要內容

Redis-应用篇-④热key和大key

holic-x...大约 17 分钟RedisRedis

Redis-应用篇-④热key和大key

学习核心

  • Hot Key VS Big Key

学习资料

阿里云:云数据库Redis版:性能调优之hot key 和 big keyopen in new window

腾讯云:热key 与 大keyopen in new window

数据分布优化:如何应对数据倾斜?open in new window

Hot Key VS Big Key

1.Hot Key

Hot Key 分析

Big Key定义:通常以其接收到的Key被请求频率来判定,例如:

  • QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000
  • 带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求
  • CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求

问题描述:对于大多数互联网系统,数据是分冷热的。比如最近的新闻、新发表的微博被访问的频率最高,而比较久远的之前新闻、微博被访问的频率就会小很多。而在突发事件发生时,大量用户同时去访问这个突发热点信息,访问这个 Hot key,这个突发热点信息所在的缓存节点就很容易出现过载和卡顿现象,甚至会被 Crash。

原因分析:Hot key 引发缓存系统异常,主要是因为预期外的访问量陡增,即突发热门事件发生时,超大量的请求访问热点事件对应的 key。比如微博中数十万、数百万的用户同时去吃一个新瓜,数十万的访问请求同一个 key,流量集中打在一个缓存节点机器,这个缓存机器很容易被打到物理网卡、带宽、CPU 的极限,从而导致缓存访问变慢、卡顿。

业务场景:引发 Hot key 的业务场景很多,比如明星结婚、离婚、出轨这种特殊突发事件,比如奥运、春节这些重大活动或节日,还比如秒杀、双12、618 等线上促销活动,都很容易出现 Hot key 的情况

解决方案

解决方案:要解决这种极热 key 的问题,首先要找出这些 Hot key ,然后再进行解决

  • 找出Hot Key

    • 提前评估:对于重要节假日、线上促销活动、集中推送这些提前已知的事情,可以提前评估出可能的热 key

    • 实时分析:对于突发事件,无法提前评估,可以通过 Spark,对应流任务进行实时分析,及时发现新发布的热点 key

    • 历史发酵:对于之前已发出的事情,逐步发酵成为热 key 的,则可以通过 Hadoop 对批处理任务离线计算,找出最近历史数据中的高频热 key

  • 针对性解决

    • 方案1:可以将这些热 key 进行分散处理,比如一个热 key 名字叫 hotkey,可以被分散为 hotkey#1、hotkey#2、hotkey#3,……hotkey#n,这 n 个 key 分散存在多个缓存节点,然后 client 端请求时,随机访问其中某个后缀的 hotkey,这样就可以把热 key 的请求打散,避免一个缓存节点过载,如下图所示

      image-20240728091822435

    • 方案2:也可以 key 的名字不变,对缓存提前进行多副本+多级结合的缓存架构设计

    • 方案3:如果热 key 较多,还可以通过监控体系对缓存的 SLA 实时监控,通过快速扩容来减少热 key 的冲击

    • 方案4:业务端还可以使用本地缓存,将这些热 key 记录在本地缓存,来减少对远程缓存的冲击

2.Big Key

Big Key 分析

Big Key定义:通常以Key的大小和Key中成员的数量来综合判定,例如

  • Key本身的数据量过大:一个String类型的Key,它的值为5 MB
  • Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10,000个
  • Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有2,000个但这些成员的Value(值)总大小为100 MB

问题描述:大 key,是指在缓存访问时,部分 Key 的 Value 过大,读写、加载易超时的现象。

原因分析:造成这些大 key 慢查询的原因很多,常见有如下

  • 如果这些大 key 占总体数据的比例很小,存 MC,对应的 slab 较少,导致很容易被频繁剔除,DB 反复加载,从而导致查询较慢
  • 如果业务中这种大 key 很多,而这种 key 被大量访问,缓存组件的网卡、带宽很容易被打满,也会导致较多的大 key 慢查询
  • 如果大 key 缓存的字段较多,每个字段的变更都会引发对这个缓存数据的变更,同时这些 key 也会被频繁地读取,读写相互影响,也会导致慢查现象
  • 大 key 一旦被缓存淘汰,DB 加载可能需要花费很多时间,也会导致大 key 查询慢的问题

业务场景:大 key 的业务场景也比较常见。

​ 比如互联网系统中需要保存用户最新 1万 个粉丝的业务,比如一个用户个人信息缓存,包括基本资料、关系图谱计数、发 feed 统计等。微博的 feed 内容缓存也很容易出现,一般用户微博在 140 字以内,但很多用户也会发表 1千 字甚至更长的微博内容,这些长微博也就成了大 key(user:name、school、desc、age、...、粉丝数、关注数、...)

解决方案

​ 对于大key,给出3种解决方案

  • 方案1:如果数据存在 MC 中,可以设计一个缓存阀值,当 value 的长度超过阀值,则对内容启用压缩,让 KV 尽量保持小的 size,其次评估大 key 所占的比例,在 MC 启动之初,就立即预写足够数据的大 key,让 Mc 预先分配足够多的 trunk size 较大的 slab。确保后面系统运行时,大 key 有足够的空间来进行缓存

  • 方案2:如果数据存在 Redis 中,比如业务数据存 set 格式,大 key 对应的 set 结构有几千几万个元素,这种写入 Redis 时会消耗很长的时间,导致 Redis 卡顿。此时,可以扩展新的数据结构,同时让 client 在这些大 key 写缓存之前,进行序列化构建,然后通过 restore 一次性写入

  • 方案3:将大 key 分拆为多个 key,尽量减少大 key 的存在。同时由于大 key 一旦穿透到 DB,加载耗时很大,所以可以对这些大 key 进行特殊照顾,比如设置较长的过期时间,比如缓存内部在淘汰 key 时,同等条件下,尽量不淘汰这些大 key

image-20240728101820837

数据分布优化

1.数据分布优化:如何应对数据倾斜?

​ 在切片集群中,数据会按照一定的分布规则分散到不同的实例上保存。比如,在使用 Redis Cluster 或 Codis 时,数据都会先按照 CRC 算法的计算值对 Slot(逻辑槽)取模,同时,所有的 Slot 又会由运维管理员分配到不同的实例上。这样,数据就被保存到相应的实例上了。虽然这种方法实现起来比较简单,但是很容易导致一个问题:数据倾斜。

image-20240728110116187

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

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

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

​ 当数据量倾斜发生时,数据在切片集群的多个实例上分布不均衡,大量数据集中到了一个或几个实例上。此处结合三个方面剖析数据量倾斜的成因和解决方案

(1)bigkey导致倾斜

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

​ 为了避免 bigkey 造成的数据量倾斜,一个根本的应对方法是,在业务层生成数据时,要尽量避免把过多的数据保存在同一个键值对中。

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

​ 假设 Hash 类型集合 user:info 保存了 100 万个用户的信息,是一个 bigkey。可以按照用户 ID 的范围,把这个集合拆分成 10 个小集合,每个小集合只保存 10 万个用户的信息(例如小集合 1 保存的是 ID 从 1 到 10 万的用户信息,小集合 2 保存的是 ID 从 10 万零 1 到 20 万的用户)。以此把一个 bigkey 化整为零、分散保存了,避免了 bigkey 给单个切片实例带来的访问压力。

(2)Slot 分配不均衡导致倾斜

集群运维人员按照机器实例性能分配slot,但忽略了实际场景中slot和数据的对应关系,导致大量数据被分配在高性能实例中,而大量请求也会落到该实例导致数据倾斜

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

​ 以 Redis Cluster 为例,剖析 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 迁移。

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

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

【步骤1】先在目标实例 5 上执行下面的命令,将 Slot 300 的源实例设置为实例 3,表示要从实例 3 上迁入 Slot 300

CLUSTER SETSLOT 300 IMPORTING 3

【步骤2】在源实例 3 上,我们把 Slot 300 的目标实例设置为 5,这表示,Slot 300 要迁出到实例 5 上,如下所示:

CLUSTER SETSLOT 300 MIGRATING 5

【步骤3】从 Slot 300 中获取 100 个 key。因为 Slot 中的 key 数量可能很多,所以需要在客户端上多次执行下面的这条命令,分批次获得并迁移 key

CLUSTER GETKEYSINSLOT 300 100

【步骤4】把刚才获取的 100 个 key 中的 key1 迁移到目标实例 5 上(IP 为 192.168.10.5),同时把要迁入的数据库设置为 0 号数据库,把迁移的超时时间设置为 timeout。重复执行 MIGRATE 命令,把 100 个 key 都迁移完

MIGRATE 192.168.10.5 6379 key1 0 timeout

【步骤5】重复执行第 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
(3)Hash Tag 导致倾斜

​ 除了 bigkey 和 Slot 分配不均衡会导致数据量倾斜,还有一个导致倾斜的原因,就是使用了 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 值。如果不使用Hash Tag,客户端会计算整个user:profile:3231的 CRC16 值

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

数据key哈希计算对应的slot
user:profile:{3231}CRC16('3231') mod 163841024
user:profile:{5328}CRC16('5328') mod 163843210
user:order:{3231}CRC16('3231') mod 163841024
user:order:{5328}CRC16('5328') mod 163843210

​ Hash Tag 的应用场景,主要是用在 Redis Cluster 和 Codis 中,支持事务操作和范围查询。因为 Redis Cluster 和 Codis 本身并不支持跨实例的事务操作和范围查询,当业务应用有这些需求时,就只能先把这些数据读取到业务层进行事务处理,或者是逐个查询每个实例,得到范围查询的结果,但这样操作起来非常麻烦。因此可以使用 Hash Tag 把要执行事务操作或是范围查询的数据映射到同一个实例上,这样就能很轻松地实现事务或范围查询了。

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

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

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

​ 发生数据访问倾斜的根本原因,就是实例上存在热点数据(比如新闻应用中的热点新闻内容、电商促销活动中的热门商品信息,等等)

(1)热点数据(hot key)

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

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

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

​ 对于有读有写的热点数据,可采用读写分离策略,或者给实例本身增加资源,例如使用配置更高的机器,来应对大量的访问压力

数据倾斜总结

数据倾斜原因和应对方案

倾斜类型倾斜成因应对方案
数据量倾斜big key:存在big key【1】业务层尽量避免创建big key
【2】将集合类型的big key拆分为多个小集合分散保存
slot 手工分配不均匀指定运维规范,结合实际数据和slot关系分析,避免将过多的slot分配到一个实例中
使用hash tag,导致大量数据集中到一个slot在【支持事务、范围检索】和【数据倾斜问题】之间进行取舍
如果引入Hash Tag会导致数据倾斜,则不使用HashTag,在业务层做事务和范围检索,避免服务不可用
数据访问倾斜hot key:存在热点数据针对只读缓存场景,采用热点数据多副本方法应对

已发生数据倾斜如何做数据迁移

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

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

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3