Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis BigKey

Redis中BigKey的隐患问题小结

作者:雾缘枯

文章介绍Redis中BigKey的概念及其对性能的影响,包括内存占用、网络阻塞、CPU耗尽和集群不稳定等问题,对Redis中BigKey的隐患问题感兴趣的朋友一起看看吧

一、什么是 BigKey?

1、BigKey的定义

BigKey是指那些在 Redis 中存储了大量数据,或者其序列化后占用大量内存空间的键。它不仅仅是一个值很长的字符串,更常见的是指那些包含巨多元素的集合类型(如 HashListSetZSet)。

想象一下:

这些,都是 BigKey。它们就像 Redis 内存中的“巨无霸”,吞噬着宝贵的资源。

2、BigKey 为什么是性能杀手?

BigKey 绝不仅仅是占用更多内存那么简单。它会引发一系列连锁反应,严重影响 Redis 性能和稳定性:

二、如何发现 Redis 中的 BigKey? 

1、使用redis-cli --bigkeys命令(推荐)

这是 Redis 官方推荐且最简单直接的方法。它会遍历 Redis 中的所有 Key,计算每个 Key 的内存大小或元素数量,并按类型进行统计,最后列出每个类型中最大的 N 个 Key。它通过 SCAN 命令分批次遍历,不会阻塞 Redis 服务

redis-cli -h <host> -p <port> --bigkeys -i 0.01

命令说明

  • -h <host>: Redis 服务器地址。

  • -p <port>: Redis 服务器端口。

  • --bigkeys: 启用 BigKey 扫描模式。

  • -i 0.01 (可选): 指定 SCAN 命令的间隔时间,单位为秒。这可以减小对 Redis 服务器的压力,但会延长扫描时间。默认不设置或设置为 0,表示尽可能快地扫描。

2. 使用 RDB 工具进行离线分析

Redis 的 RDB 文件是内存数据的二进制快照。通过分析 RDB 文件,我们可以离线地获取 Redis 中所有 Key 的详细信息(包括大小和类型),而不会对在线的 Redis 服务造成任何影响。这对于生产环境来说是一个非常安全的分析方式。

常用工具:

使用方式(以 redis-rdb-tools 为例):

  1. 生成 RDB 文件: 在 Redis 命令行中执行 BGSAVE 命令,生成最新的 RDB 文件。

  2. 拷贝 RDB 文件: 将生成的 RDB 文件拷贝到分析工具所在的机器。

  3. 运行分析命令:

rdb --command bigkeys /path/to/dump.rdb
# 或者生成 JSON 格式报告进行更详细分析
rdb -c json /path/to/dump.rdb > dump.json

3. 实时监控与自定义脚本

对于需要实时或近实时发现 BigKey 的场景,结合 Redis 的监控数据和自定义脚本是更灵活的选择。

Java 伪代码示例(使用 Jedis 客户端):

添加 Jedis 依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>5.1.0</version> 
</dependency>

Java 代码

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.ScanParams;
import redis.clients.jedis.resps.ScanResult;
import java.util.Set;
public class RedisBigKeyScanner {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String REDIS_PASSWORD = null; // 如果有密码则填写
    // 定义 BigKey 的阈值
    // 字符串 10MB
    private static final long BIG_STRING_THRESHOLD_BYTES = 10 * 1024 * 1024; 
    // 集合类型 10万元素
    private static final long BIG_COLLECTION_THRESHOLD_ELEMENTS = 100000;  
    public static void main(String[] args) {
        // 使用 try-with-resources 确保 Jedis 连接被正确关闭
        try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {
            // 如果 Redis 服务器有密码,进行认证
            if (REDIS_PASSWORD != null && !REDIS_PASSWORD.isEmpty()) {
                jedis.auth(REDIS_PASSWORD);
            }
            System.out.println("Scanning for BigKeys...");
            // 初始化 SCAN 命令的游标,从头开始扫描
            String cursor = ScanParams.SCAN_POINTER_START;
            // 设置每次 SCAN 命令返回的 Key 数量
            ScanParams scanParams = new ScanParams().count(1000);
            // 循环执行 SCAN 命令,直到游标回到起点(表示所有 Key 都已遍历)
            do {
                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
                // 更新游标
                cursor = scanResult.getCursor();
                // 获取当前批次扫描到的 Key 集合
                Set<String> keys = scanResult.getResult();
                // 遍历当前批次获取到的所有 Key
                for (String key : keys) {
                    String keyType = jedis.type(key);
                    // 根据 Key 类型,使用不同的命令来判断是否是 BigKey
                    switch (keyType) {
                        case "string":
                            // 获取字符串的长度(字节数)
                            long stringSize = jedis.strlen(key);
                            if (stringSize > BIG_STRING_THRESHOLD_BYTES) {
                                System.out.printf("  [BIG KEY] String: %s (Size: %.2f MB)%n", key, (double) stringSize / (1024 * 1024));
                            }
                            break;
                        case "list":
                            // 获取列表的元素数量
                            long listLength = jedis.llen(key);
                            if (listLength > BIG_COLLECTION_THRESHOLD_ELEMENTS) {
                                System.out.printf("  [BIG KEY] List: %s (Elements: %d)%n", key, listLength);
                            }
                            break;
                        case "hash":
                            // 获取哈希表的字段数量
                            long hashFields = jedis.hlen(key);
                            if (hashFields > BIG_COLLECTION_THRESHOLD_ELEMENTS) {
                                System.out.printf("  [BIG KEY] Hash: %s (Fields: %d)%n", key, hashFields);
                            }
                            break;
                        case "set":
                            // 获取集合的成员数量
                            long setMembers = jedis.scard(key);
                            if (setMembers > BIG_COLLECTION_THRESHOLD_ELEMENTS) {
                                System.out.printf("  [BIG KEY] Set: %s (Members: %d)%n", key, setMembers);
                            }
                            break;
                        case "zset":
                            // 获取有序集合的成员数量
                            long zsetMembers = jedis.zcard(key);
                            if (zsetMembers > BIG_COLLECTION_THRESHOLD_ELEMENTS) {
                                System.out.printf("  [BIG KEY] ZSet: %s (Members: %d)%n", key, zsetMembers);
                            }
                            break;
                        // 可以根据需要添加其他 Redis 数据类型的判断或跳过
                        default:
                            break;
                    }
                }
            // 当游标回到起始点 "0" 时,表示遍历完成
            } while (!cursor.equals(ScanParams.SCAN_POINTER_START));
            System.out.println("BigKey scan complete.");
        } catch (Exception e) {
            System.err.println("Error connecting to Redis or during scan: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

4. 业务层面排查

有时 BigKey 的产生源于业务逻辑的缺陷,比如某个业务 ID 对应的 Key 不断积累数据,从未清理。

到此这篇关于Redis中BigKey的隐患的文章就介绍到这了,更多相关Redis BigKey内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文