redis的bigkey

模拟数据量大的操作

批量写入100w条写操作命令到文件,可能要等待好几秒

for((i=1;i<=100*10000;i++)); do echo "set k$i v$i" >> /tmp/redisTest.txt ;done;

 

通过redis管道批量插入100w条数据

cat /tmp/redisTest.txt | redis-cli -h 127.0.0.1 -p 6379 -a 123456 --pipe

 

这时就有100w数据了

dbsize

 

生产上会禁用keys *,因为redis工作线程是单线程,所以会造成卡顿。

 

禁用危险命令

修改配置文件

vim redis.conf

在如图配置

rename-command后接命令再接字符串,表示禁用该命令

可以看到已经禁掉了

 

搜索命令

生产上禁用keys *,而会选择使用scan

官网:

SCAN | Redis

Redis SCAN 命令 递增地遍历key空间

Redis SCAN 命令及其相关命令 SSCAN、HSCAN、 ZSCAN 命令都是用于增量遍历集合中的元素。

SCAN 用于遍历当前数据库中的键。

  • SSCAN 用于遍历集合键中的元素。
  • HSCAN 用于遍历哈希键中的键值对。
  • ZSCAN 用于遍历有序集合中的元素(包括元素成员和元素分值)。

语法:

scan cursor [MATCH pattern] [COUNT count] [TYPE type]
  • cursor:表示迭代的起始位置,从0开始,如果cursor为0,表示开始新一轮的迭代。
  • MATCH pattern:可选参数,用于匹配指定模式的key。
  • COUNT count:可选参数,指定每次迭代返回的key数量。不一定真的会返回指定的数量。

SCAN命令会返回一个包含两个元素的数组:

  1. 下一个迭代的游标位置。如果cursor为0,表示全部已迭代。
  2. 包含迭代返回的key的数组。

下一次使用上一次返回的游标

 

注意:

SCAN命令不保证按照特定的顺序遍历集合中的元素,也不是从第一位遍历到末尾。考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏,而采用了高位进位加法来遍历。

 

查看bigkey

阿里云redis开发规范对BigKey要求:

  • 避免存储过大的字符串数据,尤其是超过10KB的大字符串。
  • 对于可能变成BigKey的键,合理设置过期时间,否则过期自动删除del可能阻塞。
  • 非字符串的bigkey,不要使用del删除,使用hscan, sscan, zscan方式渐进式删除

阿里云Redis将占用较多内存的键定义为BigKey。

  • string是value,最大512MB但是≥10KB就是bigkeylist
  • hash,set和zset,个数超过5000就是bigkey

 

查看bigkey,给出每种数据类型的键值个数+平均大小

redis-cli -h 127.0.0.1 -p 6379 -a 123456 --bigkeys

 

查看bigkey,给出一个 key 和它的值在 RAM 中所占用的字节数。返回的结果是 key 的值以及为管理该 key 分配的内存总字节数。

memory usage key [SAMPLES count]

 

删除bigkey

非字符串的bigkey,不要使用del删除,使用hscan, sscan, zscan方式渐进式删除

比如:

String类型

del key [key ...]

或异步

unlink key [key ...]

 

hash类型

hash使用hscan渐进式删除。

HSCAN 用于遍历哈希键中的键值对。

hscan key cursor [MATCH pattern] [COUNT count]

如加入依赖

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

示例代码如下:

package com.dreams.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.List;
import java.util.Map;

public class DeleteBigKeyExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("your_redis_address", 6379);
        jedis.auth("your_redis_password");
        // 要删除的BigKey名称
        String bigKey = "your_bigkey_name";
        // 使用HSCAN命令遍历大键的所有字段
        String cursor = "0";
        ScanParams scanParams = new ScanParams().count(100);
        do {
            ScanResult<Map.Entry<String, String>> scanResult = jedis.hscan(bigKey, cursor, scanParams);
            List<Map.Entry<String, String>> entryList = scanResult.getResult();
            if (entryList != null && !entryList.isEmpty()) {
                for (Map.Entry<String, String> entry : entryList) {
                    // 逐个删除字段
                    jedis.hdel(bigKey, entry.getKey());
                }
            }
            cursor = scanResult.getCursor();
        } while (!cursor.equals("0"));
        // 最后删除大键本身
        jedis.del(bigKey);
        System.out.println("Deleted BigKey: " + bigKey);
        jedis.close();
    }
}

 

list类型

使用ltrim渐进式逐步删除,直到全部删除完成,

ltrim让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

ltrim key start stop

 

示例代码如下:

package com.dreams.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.List;

public class ListProgressiveDeleteExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("your_redis_address", 6379);
        jedis.auth("your_redis_password");
        long threshold = 1024 * 1024 * 10; // 假设阈值为10MB,超过某个阈值时,我们可以采取渐进式删除策略
        String cursor = "0";
        ScanParams scanParams = new ScanParams().count(100);
        do {
            ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
            cursor = scanResult.getCursor();
            List<String> resultList = scanResult.getResult();
            for (String key : resultList) {
                long size = jedis.memoryUsage(key); // 计算键的大小
                if (size > threshold && jedis.type(key).equals("list")) { // 符合条件的大Key
                    System.out.println("Deleting key: " + key);
                    long length = jedis.llen(key); // 获取列表长度
                    jedis.ltrim(key, 0, length / 2); // 删除前一半元素
                }
            }
        } while (!cursor.equals("0"));
        jedis.close();
    }
}

 

Set类型

使用sscan每次获取部分元素,再使用srem命令删除每个元素

sscan key cursor [MATCH pattern] [COUNT count]
srem key member [member ...]

示例代码如下:

package com.dreams.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.List;
import java.util.Set;

public class ProgressiveDeleteExample {
    public static void main(String[] args) {
        String host = "your_redis_host";
        int port = 6379;
        String password = "your_redis_password";
        String bigKey = "your_big_set_key";
        Jedis jedis = new Jedis(host, port);
        jedis.auth(password);
        ScanParams params = new ScanParams();
        params.match("*"); // 匹配所有键
        params.count(100); // 每次返回 100 个元素进行处理
        String cursor = "0";
        do {
            ScanResult<String> scanResult = jedis.sscan(bigKey, cursor, params);
            List<String> elements = scanResult.getResult();
            if (elements != null && !elements.isEmpty()) {
                for (String element : elements) {
                    // 处理每个元素,例如记录日志、更新统计信息等
                    // ...
                    // 删除当前元素
                    jedis.srem(bigKey, element);
                }
            }
            cursor = scanResult.getCursor();
        } while (!cursor.equals("0"));
        jedis.close();
    }
}

 

zset类型

使用zscan每次获取部分元素,再使用ZREM,RANGEBYRANK命令删除每个元素

 

示例代码如下:

package com.dreams.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.Tuple;

import java.util.List;

public class ProgressiveDeleteZSet {
    public static void main(String[] args) {
        String host = "your_redis_host";
        int port = 6379;
        String password = "your_redis_password";
        int batchSize = 1000; // 每次循环删除的元素数量
        String bigKey = "your_big_zset_key";
        Jedis jedis = new Jedis(host, port);
        jedis.auth(password);
        long size = jedis.zcard(bigKey);
        String cursor = "0";
        while (true) {
            ScanResult<Tuple> scanResult = jedis.zscan(bigKey, cursor, new ScanParams().count(batchSize));
            List<Tuple> list = scanResult.getResult();
            if (list != null && !list.isEmpty()) {
                for (Tuple tuple : list) {
                    System.out.println("Deleting member: " + tuple.getElement());
                    jedis.zrem(bigKey, tuple.getElement());
                }
            }
            cursor = scanResult.getCursor();
            if (cursor.equals("0")) {
                break;
            }
        }
        jedis.del(bigKey);
    }
}

 

bigkey调优

打开配置文件

vim redis.conf

优化如下:

lazyfree-lazy-server-del:当这个选项被设置为”yes”时,服务器将会尽量避免在主线程中一次性删除大的数据结构而阻塞服务器,它会将删除操作转移到后台线程中进行。

replica-lazy-flush :当这个选项被设置为”yes”时,从节点在把数据写到磁盘上之前,会尽可能地缓存更多的数据,以提高写入磁盘的效率。

lazyfree-lazy-user-del:当这个选项被设置为”yes”时,对于将用户代码 DEL 调用替换为 UNLINK 调用并不容易的情况下,可以修改 DEL 命令的默认行为,使其行为与 UNLINK 完全相同。

 

参考

SCAN | Redis

Redis SCAN 命令 递增地遍历key空间

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇