前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何找到Redis大Key?

如何找到Redis大Key?

作者头像
数据库交流
发布2023-11-08 15:17:23
1960
发布2023-11-08 15:17:23
举报
文章被收录于专栏:悦专栏悦专栏

作者简介

马听,多年 DBA 实战经验,对MySQL、 Redis、MongoDB、Go等有一定了解,书籍《MySQL DBA精英实战课》作者,慕课网DBA体系课(https://class.imooc.com/sale/dba)讲师。

有时候,可能因为业务设计不合理,或者其他的一些原因,导致Redis某个key非常大。

而在查询这类大key的时候,往往响应比较慢,并且也会影响其他正常查询。

所以,我们应该关注Redis里面的key详情,及时发现大key并治理,提前预防大key产生的影响。

1 什么是 Bigkey

下面这两种情况,在很多互联网公司都被认为是 Bigkey:

字符串类型超过10KB

当然,这也不是硬性规定,主要看我们的业务场景

非字符串类型元素个数超过5000个

比如哈希、列表、集合、有序集合,元素个数超过 5000 的,就可以认为是Bigkey。

2 Bigkey的危害

Bigkey 存在很多危害,具体体现在以这些方面:

内存空间不均匀

比如在 Redis cluster 或者其他集群架构中,会造成节点的内存使用不均匀。

查询时阻塞

因为 Redis 单线程特性,如果操作某个 Bigkey 耗时比较久,则后面的请求会被阻塞。

过期时阻塞

如果 Bigkey 设置了过期时间,当过期后,这个 key 会被删除,假如没有使用过期异步删除,就会存在阻塞 Redis 的可能性,并且慢查询中查不到(因为这个删除是内部循环事件)。

3 用ChatGPT编写制造Bigkey的程序

构造一些大key,方便我们等下测试。

发送给ChatGPT,提示语为:

帮忙写一个Go程序 往Redis里面写入一些测试的key string类型大小超过10kbkey hash、list、sets、zsets等key,元素超过5000 key的名字都是以large开头的 Redis的IP和端口192.168.12.161:7001,密码:xxx

生成的程序如下:

编辑一个文件,writeBigkey.go

代码语言:javascript
复制
package main

import (
   "context"
   "fmt"
   "log"
   "math/rand"
   "strconv"
   "time"

   "github.com/go-redis/redis/v8"
)

func main() {
   // 创建Redis客户端连接
   client := redis.NewClient(&redis.Options{
      Addr:     "192.168.12.161:7001", // Redis服务器地址
      Password: "xxx",               // 如果有密码,请填写密码
      DB:       0,                // 默认数据库
   })

   // 使用完后关闭连接
   defer client.Close()

   // 写入字符串类型的键,大小大于10KB
   largeStringValue := generateLargeString(10240) // 生成大于10KB的字符串
   err := client.Set(context.Background(), "large_string_key", largeStringValue, 0).Err()
   if err != nil {
      log.Fatalf("Error writing large string key: %v", err)
   }

   // 写入哈希类型的键,元素个数大于5000
   hashData := make(map[string]interface{})
   for i := 0; i < 6000; i++ {
      field := fmt.Sprintf("field%d", i)
      value := fmt.Sprintf("value%d", i)
      hashData[field] = value
   }
   err = client.HMSet(context.Background(), "large_hash_key", hashData).Err()
   if err != nil {
      log.Fatalf("Error writing large hash key: %v", err)
   }

   // 写入列表类型的键,元素个数大于5000
   listData := make([]interface{}, 6000)
   for i := 0; i < 6000; i++ {
      listData[i] = strconv.Itoa(i)
   }
   err = client.RPush(context.Background(), "large_list_key", listData...).Err()
   if err != nil {
      log.Fatalf("Error writing large list key: %v", err)
   }

   fmt.Println("Keys have been written successfully!")
}

// 生成指定大小的随机字符串
func generateLargeString(size int) string {
   rand.Seed(time.Now().UnixNano())
   const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
   result := make([]byte, size)
   for i := range result {
      result[i] = charset[rand.Intn(len(charset))]
   }
   return string(result)
}

运行程序,就可以生成一些大key。

4 找到大key

我们来讲一下几种常见的找打大key的方法

4.1 Redis客户端自带的参数查找Bigkey

执行

代码语言:javascript
复制
redis-cli -p 7001 -a xxx --bigkeys

大致结果和注释如下:

代码语言:javascript
复制
[00.00%] Biggest string found so far '"large_string_key"' with 10240 bytes
[00.00%] Biggest set    found so far '"set_test"' with 1 members
[00.00%] Biggest hash   found so far '"user:01"' with 1 fields
[00.00%] Biggest list   found so far '"large_list_key"' with 6000 items
[00.00%] Biggest hash   found so far '"large_hash_key"' with 6000 fields

-------- summary -------

Sampled 8 keys in the keyspace!
# 一共扫描了 8 个key
Total key length in bytes is 82 (avg len 10.25)
# 所有key的总长度是 82 字节,平均长度是 10.25 字节

Biggest   list found '"large_list_key"' has 6000 items
Biggest   hash found '"large_hash_key"' has 6000 fields
Biggest string found '"large_string_key"' has 10240 bytes
Biggest    set found '"set_test"' has 1 members

 # 每一种key情况的总览,某如某个类型,扫描出多少个key,占比多少,平均大小。
2 lists with 6001 items (25.00% of keys, avg size 3000.50)
2 hashs with 6001 fields (25.00% of keys, avg size 3000.50)
3 strings with 10246 bytes (37.50% of keys, avg size 3415.33)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
1 sets with 1 members (12.50% of keys, avg size 1.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

4.2 通过ChatGPT编写代码查找Bigkey

用ChatGPT编写查询Bigkey的程序:

帮忙写一个Go程序 扫描出Redis里面string类型大小超过10kb的key hash、list、sets、zsets等key,扫描出元素超过5000的key Redis的IP和端口192.168.12.161:7001,密码:xxx

生成的代码如下:

代码语言:javascript
复制
package main

import (
   "context"
   "fmt"
   "github.com/go-redis/redis/v8"
   "log"
)

func main() {
   // 创建Redis客户端连接
   client := redis.NewClient(&redis.Options{
      Addr:     "192.168.12.161:7001", // Redis服务器地址
      Password: "xxx",               // 如果有密码,请填写密码
      DB:       0,                // 默认数据库
   })

   // 使用完后关闭连接
   defer client.Close()

   // 获取所有键
   keys, err := client.Keys(context.Background(), "*").Result()
   if err != nil {
      log.Fatalf("Error getting keys: %v", err)
   }

   // 遍历所有键
   for _, key := range keys {
      keyType, err := client.Type(context.Background(), key).Result()
      if err != nil {
         log.Printf("Error getting key type for key '%s': %v", key, err)
         continue
      }

      switch keyType {
      case "string":
         // 检查字符串类型键的大小
         valueSize, err := client.StrLen(context.Background(), key).Result()
         if err != nil {
            log.Printf("Error getting size for string key '%s': %v", key, err)
            continue
         }
         //fmt.Printf("String key '%s' size: %d bytes\n", key, valueSize)

         if valueSize >= 10240 { // 大于10KB
            fmt.Printf("String key '%s' size: %d bytes\n", key, valueSize)
         }

      case "hash":
         // 检查哈希类型键的元素数量
         hashSize, err := client.HLen(context.Background(), key).Result()
         if err != nil {
            log.Printf("Error getting size for hash key '%s': %v", key, err)
            continue
         }
         if hashSize > 5000 {
            fmt.Printf("Hash key '%s' has %d elements\n", key, hashSize)
         }

      case "list":
         // 检查列表类型键的元素数量
         listSize, err := client.LLen(context.Background(), key).Result()
         if err != nil {
            log.Printf("Error getting size for list key '%s': %v", key, err)
            continue
         }
         if listSize > 5000 {
            fmt.Printf("List key '%s' has %d elements\n", key, listSize)
         }

      case "set":
         // 检查集合类型键的元素数量
         setSize, err := client.SCard(context.Background(), key).Result()
         if err != nil {
            log.Printf("Error getting size for set key '%s': %v", key, err)
            continue
         }
         if setSize > 5000 {
            fmt.Printf("Set key '%s' has %d elements\n", key, setSize)
         }

      case "zset":
         // 检查有序集合类型键的元素数量
         zsetSize, err := client.ZCard(context.Background(), key).Result()
         if err != nil {
            log.Printf("Error getting size for zset key '%s': %v", key, err)
            continue
         }
         if zsetSize > 5000 {
            fmt.Printf("Zset key '%s' has %d elements\n", key, zsetSize)
         }
      }
   }
}

4.3 通过rdbtools来找到Bigkey

大概过程就是:

通过 rdbtools 分析 rdb 生成 csv 文件,再导入 MySQL 或其他数据库中进行分析统计,根据 size_in_bytes 统计 Bigkey。

获取Redis的RDB文件

在Redis中执行

代码语言:javascript
复制
bgsave

创建一个rdb的分析目录

代码语言:javascript
复制
mkdir /data/analyserdb

把rdb复制一份到分析目录

代码语言:javascript
复制
cp /data/redis7001/data/dump.rdb /data/analyserdb/192.168.12.161_dump.rdb

通过rdbtools分析RDB中的key

安装Python3

代码语言:javascript
复制
yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel

cd /usr/src
wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz
tar -xvf Python-3.7.3.tgz
cd Python-3.7.3/
./configure
make && make install

安装 redis-rdb-tools,参考 GitHub(https://github.com/sripathikrishnan/redis-rdb-tools)。

代码语言:javascript
复制
yum install python3-devel
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py
pip3 install python-lzf
pip3 install rdbtools

rdb --help

我们可以通过官网的Python示例获取到大key,比如

https://github.com/sripathikrishnan/redis-rdb-tools

编辑文件analyse_rdb.py

代码语言:javascript
复制
from rdbtools import RdbParser, RdbCallback
from rdbtools.encodehelpers import bytes_to_unicode

class MyCallback(RdbCallback):
    ''' Simple example to show how callback works.
        See RdbCallback for all available callback methods.
        See JsonCallback for a concrete example
    '''

    def __init__(self):
        super(MyCallback, self).__init__(string_escape=None)

    def encode_key(self, key):
        return bytes_to_unicode(key, self._escape, skip_printable=True)

    def encode_value(self, val):
        return bytes_to_unicode(val, self._escape)

    def set(self, key, value, expiry, info):
        print('%s = %s' % (self.encode_key(key), self.encode_value(value)))

    def hset(self, key, field, value):
        print('%s.%s = %s' % (self.encode_key(key), self.encode_key(field), self.encode_value(value)))

    def sadd(self, key, member):
        print('%s has {%s}' % (self.encode_key(key), self.encode_value(member)))

    def rpush(self, key, value):
        print('%s has [%s]' % (self.encode_key(key), self.encode_value(value)))

    def zadd(self, key, score, member):
        print('%s has {%s : %s}' % (str(key), str(member), str(score)))


callback = MyCallback()
parser = RdbParser(callback)
parser.parse('./dump.rdb')

我们再来运行Python代码

代码语言:javascript
复制
python3 analyse_rdb.py >result.txt

也可以使用命令进行 RDB 分析

代码语言:javascript
复制
cd /data/analyserdb

rdb -c memory 192.168.12.161_dump.rdb >192.168.12.161_rdb_result.csv

各个字段的解释如下

  • database(库编号)
  • type(键类型)
  • key(键名字)
  • size_in_bytes(键大小)
  • encoding(键编码)
  • num_elements(元素个数)
  • len_largest_element(最大元素大小)
  • expiry(过期时间)

把分析结果写入MySQL

登录MySQL

创建一个用户

代码语言:javascript
复制
create database rdb;
create user 'u_rdb'@'%' IDENTIFIED WITH mysql_native_password BY '1g18_pDgnd12' ; 
GRANT update,delete,insert,select ON rdb.* TO 'u_rdb'@'%' ;

创建MySQL表

代码语言:javascript
复制
USE rdb;
CREATE TABLE rdb_result (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
  ip_add VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'IP Address',
  key_database INT NOT NULL DEFAULT '0' COMMENT 'Database Number',
  key_type VARCHAR(10) NOT NULL DEFAULT '' COMMENT 'Key Type',
  key_name VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Key Name',
  key_size INT NOT NULL DEFAULT '0' COMMENT 'Key Size',
  key_encoding VARCHAR(10) NOT NULL DEFAULT '' COMMENT 'Key Encoding',
  num_elements INT NOT NULL DEFAULT '0' COMMENT 'Number of Elements',
  len_largest_element INT NOT NULL DEFAULT '0' COMMENT 'Largest Element Size',
  key_expiry VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'Key Expiry Time',
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Record Creation Time',
  update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record Update Time',
  status TINYINT NOT NULL DEFAULT '1' COMMENT '1 represents a valid record, 0 represents an invalid record',
  PRIMARY KEY (id),
  KEY idx_stu_score (key_size)
) ENGINE = INNODB CHARSET = utf8mb4 COMMENT 'Redis RDB Analysis Result Table';

修改MySQL配置文件

代码语言:javascript
复制
local-infile=on

这个参数控制是否允许从客户端本地文件加载数据到 MySQL 服务器中

重启MySQL

登录MySQL

代码语言:javascript
复制
mysql -uroot -p --local-infile=1

导入数据

代码语言:javascript
复制
use rdb;

LOAD DATA LOCAL INFILE '/data/analyserdb/192.168.12.161_rdb_result.csv'
INTO TABLE rdb_result
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
IGNORE 1 LINES
(key_database, key_type, key_name, key_size, key_encoding, num_elements, len_largest_element, key_expiry)
SET ip_add = '192.168.12.161';

比如我们想找到bigkey,可以执行语句

代码语言:javascript
复制
select * from rdb_result where key_size >  10240;

5 优化Bigkey

找到 Bigkey 后,怎么优化呢?

这里介绍几种常见的优化方式:

删除不用的Bigkey

有些 Bigkey 业务不需要使用了,因此可以考虑删除掉。但是要注意的是:如果直接 del,可能会阻塞 Redis 服务。大致有下面几种处理办法:

如果 key 类型为 string,则直接删除;

如果 key 类型为 hash、list、set、sorted set,使用 hscan 命令,每次获取部分(例如100个)field-value,再利用 hdel 删除每个 field;

Redis 在4.0 版本支持 lazy delete free 的模式,删除 Bigkey 不会阻塞 Redis。

控制大小或拆分Bigkey

处理 Bigkey 的另外一种方法就是控制大小,比如 string 减少字符串长度,list、hash、set、zset 等减少成员数。

有时也可以考虑对 Bigkey 进行拆分,具体方法如下:

对于 string 类型的 Bigkey,可以考虑拆分成多个 key-value。

对于 hash 或者 list 类型,可以考虑拆分成多个 hash 或者 list。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 悦专栏 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档