玖叶教程网

前端编程开发入门

常见Redis大key热key的解决方案(redis 大key解决方案)

前言:

redis是我们项目经常使用的中间件,redis通常作为缓存运行在内存中,速度很快。redis对应五种类型的存储方案,分别是:string、list、set、zset和hash类型。但是通常redis存在大key问题影响查询的性能。今天聊聊redis中dakey的解决方案。

1.什么是Redis大Key

在Redis中,"big key" 指的是一个占用较多内存空间的键。当一个键的值占用的内存超过了Redis的配置阈值时,它就被认为是一个"big key"。大的键可以导致一些性能问题,因为它们需要更多的内存和网络带宽来存储和传输。

"big key" 可能会对Redis的性能产生负面影响,因为它们可能导致内存碎片,增加数据传输的成本,降低缓存的效率等。一些常见导致大键问题的场景包括:

  1. 字符串过大: 当字符串值的大小远远超过了Redis的配置限制时,它可能成为一个大键。
  2. Hash、Set、List等数据结构的元素过多:当一个Hash、Set或List中的元素数量非常庞大时,也可能导致它成为一个大键。

为了避免"big key"问题,可以采取以下一些措施:

  1. 合理设置内存限制:在Redis的配置中,可以设置最大使用的内存限制,通过合理设置该值,可以防止大键问题对系统造成过大的影响。
  2. 合理使用数据结构: 选择合适的数据结构来存储数据,避免在单个键中存储过多的元素。
  3. 定期清理不需要的数据: 对于不再需要的大键,可以定期清理或采取适当的缓存策略,以确保内存得到有效利用。
  4. 使用分布式缓存: 在某些情况下,将大量数据分布到多个Redis节点上,以减轻单个节点的负担。

了解和监控大键问题对于维护Redis性能是非常重要的。可以使用Redis的监控工具,如Redis的INFO命令或第三方监控工具,来跟踪内存使用情况和识别潜在的大键。

例如使用 RedisDesktopManager 工具中的“大 key”功能,或使用 KeyDBA 工具等。这些工具可以扫描整个 Redis 数据库,并显示大小最大的 key,从而帮助您确定哪些 key是大key。

redis中有常见的几种数据结构,每种结构对大key的定义不同,比如:

  • value是String类型时,size超过10KB
  • value是ZSET、Hash、List、Set等集合类型时,它的成员数量超过1w个

2.针对于redis不同的数据结构怎么解决大key问题

String

处理大键(big key)问题对于字符串(String)数据结构可以采取以下一些方案:

1.数据分割:如果字符串的值非常大,可以考虑将其分割为多个小的字符串,并使用多个键来存储这些小字符串片段。

# 原始大字符串
SET user:1:description "Very long description..."
# 分割后的小字符串片段
SET user:1:description:part1 "Very long"
SET user:1:description:part2 " description..."

2.使用压缩算法:对于文本型的数据,可以考虑使用Redis支持的压缩功能,如zstd压缩算法,以减小存储空间。

# 使用压缩算法
SET user:1:description "Compressed description..." ZSTD

注意:需要在客户端应用中进行解压缩,因为Redis本身并不会自动解压缩存储的值。

3.使用其他数据结构: 如果字符串的值具有结构化信息,可以考虑使用其他数据结构,如Hash或List。这样可以更好地组织数据并避免一个键变得过大。

# 使用Hash
HSET user:1:info description "Very long description..."

4.数据清理:定期清理不再需要的字符串数据,以确保只保留必要的信息。

List

处理大键(big key)的问题对于列表(List)结构可以采取以下一些策略:

1.分割列表:如果列表中包含的元素过多,可以考虑将列表分割成多个小的列表。每个小列表只包含部分元素,这样可以降低单个列表的长度。

# 原始大列表
LPUSH user:1:activity_log item1
LPUSH user:1:activity_log item2
LPUSH user:1:activity_log item3
# 分割后的小列表
LPUSH user:1:activity_log:part1 item1
LPUSH user:1:activity_log:part1 item2
LPUSH user:1:activity_log:part2 item3

2.定期修剪:定期检查列表,删除掉不再需要的元素,以保持列表的合理大小。可以使用LTRIM命令来截取列表,保留指定范围内的元素。

# 定期修剪列表
LTRIM user:1:activity_log 0 99

上述命令保留了列表中的前100个元素,删除了其余的元素。

3.使用其他数据结构:如果列表中的元素有一定的关联性或结构化信息,考虑使用其他数据结构,如有序集合(Sorted Set)或哈希(Hash)。这样可以更灵活地管理数据,避免一个大列表的问题。

# 使用有序集合
ZADD user:1:activity_log timestamp1 item1
ZADD user:1:activity_log timestamp2 item2

4.使用Stream数据类型:如果数据是事件流的形式,可以考虑使用Redis Streams,它是一种支持持久化、多消费者、多生产者的数据结构,能够有效地处理事件流。

# 使用Stream
XADD user:1:activity_log * type item1
XADD user:1:activity_log * type item2

选择适当的方法取决于应用的具体需求。在设计时,需要根据数据的特性和访问模式选择合适的数据结构和拆分策略,以避免大键问题。

set

对于集合(Set)数据结构,处理大键(big key)问题可以采取以下一些方案:

1.分割集合:如果集合中元素很多,可以考虑将其分割成多个小的集合。每个小集合只包含部分元素,这样可以降低单个集合的大小。

# 原始大集合
SADD user:1:interests interest1
SADD user:1:interests interest2
SADD user:1:interests interest3
# 分割后的小集合
SADD user:1:interests:part1 interest1
SADD user:1:interests:part2 interest2
SADD user:1:interests:part2 interest3

2.使用有序集合(Sorted Set):如果集合中的元素有一定的顺序关系,可以考虑使用有序集合来存储数据。这样可以更方便地处理一部分数据,而不是将所有元素存储在一个集合中。

# 使用有序集合
ZADD user:1:interests 1 interest1
ZADD user:1:interests 2 interest2
ZADD user:1:interests 3 interest3

3.使用HyperLogLog:如果集合的目的是为了计算基数(元素的不重复数量),可以考虑使用HyperLogLog数据结构。它可以在极大规模的数据集合上估计基数,而且占用的内存相对较小。

# 使用HyperLogLog
PFADD user:1:interests interest1 interest2 interest3

4.定期清理不需要的元素:定期检查集合,删除掉不再需要的元素,以保持集合的合理大小。可以使用SREM命令来移除指定的元素。

# 定期清理集合元素
SREM user:1:interests unwanted_interest

选择合适的方案取决于应用的具体需求。在设计时,需要根据数据的特性和访问模式选择合适的数据结构和拆分策略,以避免大键问题。

Hash

针对Hash数据结构的大键问题,以下是一些具体的方案:

1.字段分组:如果一个哈希中的字段数量庞大,可以考虑将字段进行适当的分组,然后使用多个哈希键来存储这些分组。例如,将字段按照某种规则划分为多个小组,然后每个小组使用一个独立的哈希键存储。

# 原始大哈希
HSET user:1 name John
HSET user:1 email [email protected]
HSET user:1 age 30
# 分组后的哈希
HSET user:1:info name John
HSET user:1:info email [email protected]
HSET user:1:info age 30

2.分散数据:将大哈希拆分为多个小的哈希,每个小哈希存储一部分字段。这样可以将数据分散到多个哈希键中,降低单个哈希键的大小。

# 原始大哈希
HSET user:1 name John
HSET user:1 email [email protected]
HSET user:1 age 30
# 分散后的哈希
HSET user:1:info name John
HSET user:1:contact email [email protected]
HSET user:1:profile age 30

3.使用多个哈希键:将相关的信息存储在多个独立的哈希键中,而不是一个大哈希中。这样可以避免一个键变得过大。

# 使用多个哈希键
HSET user:1:info name John
HSET user:1:contact email [email protected]
HSET user:1:profile age 30

4.定期清理不需要的字段:定期检查哈希中的字段,清理掉不再需要的字段。这可以通过HDEL命令来实现。

# 定期清理不需要的字段
HDEL user:1:info unwanted_field

选择合适的方案取决于具体的业务需求和数据访问模式。这些方案可以根据应用程序的情况来调整和组合,以解决大Hash键问题。

Sorted Set

对于有序集合(Sorted Set)数据结构,处理大键(big key)问题可以采取以下一些方案:

1.分割有序集合:将一个大的有序集合分割成多个小的有序集合,每个小集合只包含部分元素。这样可以降低单个有序集合的大小。

# 原始大有序集合
ZADD user:1:scores 100 "Alice"
ZADD user:1:scores 200 "Bob"
ZADD user:1:scores 300 "Charlie"
# 分割后的小有序集合
ZADD user:1:scores:part1 100 "Alice"
ZADD user:1:scores:part2 200 "Bob"
ZADD user:1:scores:part3 300 "Charlie"

2.按分数范围存储:如果有序集合的元素有一定的范围,可以将元素按照分数范围存储在不同的有序集合中,以减小每个有序集合的大小。

# 按分数范围存储
ZADD user:1:scores:0to100 100 "Alice"
ZADD user:1:scores:101to200 200 "Bob"
ZADD user:1:scores:201to300 300 "Charlie"

3.定期修剪:定期检查有序集合,删除掉不再需要的元素,以保持有序集合的合理大小。可以使用ZREMRANGEBYRANKZREMRANGEBYSCORE命令来移除一定范围内的元素。

# 定期修剪有序集合
ZREMRANGEBYRANK user:1:scores 0 99

4.使用其他数据结构:如果有序集合不再适用,可以考虑使用其他数据结构,如哈希(Hash)或字符串(String),具体取决于数据的特性和访问模式。

# 使用哈希或字符串
HSET user:1:info score 100

选择合适的方案取决于应用的具体需求。在设计时,需要根据数据的特性和访问模式选择合适的数据结构和拆分策略,以避免大键问题。

热key解决方案

增加 Redis 实例复本数量

对于出现热 Key 的 Redis 实例,我们可以通过水平扩容增加副本数量,将读请求的压力分担到不同副本节点上。

二级缓存(本地缓存)

当出现热 Key 以后,把热 Key 加载到系统的 JVM 中。后续针对这些热 Key 的请求,会直接从 JVM 中获取,而不会走到 Redis 层。这些本地缓存的工具很多,比如 Ehcache,或者 Google Guava 中 Cache 工具,或者直接使用 HashMap 作为本地缓存工具都是可以的。

使用本地缓存需要注意两个问题:

  • 如果对热 Key 进行本地缓存,需要防止本地缓存过大,影响系统性能;
  • 需要处理本地缓存和 Redis 集群数据的一致性问题。

热 Key 备份

那么如何将对某个热 Key 的请求打散到不同实例上呢?

可以通过热 Key 备份的方式,基本的思路就是,我们可以给热 Key 加上前缀或者后缀,把一个热 Key 的数量变成 Redis 实例个数 N 的倍数 M,从而由访问一个 Redis Key 变成访问 N * M 个 Redis Key。 N * M 个 Redis Key 经过分片分布到不同的实例上,将访问量均摊到所有实例。

// N 为 Redis 实例个数,M 为 N 的 2倍
const M = N * 2
//生成随机数
random = GenRandom(0, M)
//构造备份新 Key
bakHotKey = hotKey + "_" + random
data = redis.GET(bakHotKey)
if data == NULL {
    data = redis.GET(hotKey)
    if data == NULL {
        data = GetFromDB()
        // 可以利用原子锁来写入数据保证数据一致性
        redis.SET(hotKey, data, expireTime)
        redis.SET(bakHotKey, data, expireTime + GenRandom(0, 5))
    } else {
        redis.SET(bakHotKey, data, expireTime + GenRandom(0, 5))
    }
}

代码中,通过一个大于等于1 小于 M 的随机数,得到一个 bakHotKey,程序会优先访问 bakHotKey,在得不到数据的情况下,再访问原来的 hotkey,并将 hotkey 的内容写回 bakHotKey。值得注意的是,bakHotKey 的过期时间是 hotkey 的过期时间加上一个较小的随机正整数,这是通过坡度过期的方式,保证在 hotkey 过期时,所有 bakHotKey 不会同时过期而造成缓存雪崩。

每天一个知识点,我们一起学习进步~

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言