前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:redis中的大key要如何删除?

面试官:redis中的大key要如何删除?

作者头像
xiaoyi
发布2024-04-19 11:12:21
1600
发布2024-04-19 11:12:21
举报
文章被收录于专栏:小义思小义思

大家好,我是小义。今天来聊聊面试中的高频考点:如何处理redis缓存中的大key? 大 key 其实并不是指 key 的值很大,而是 key 对应的 value 很大,占了很大内存。

为什么会有大Key?

出现的原因

了解大Key的成因是解决问题的第一步。大Key的形成可能源于多种因素,包括但不限于:

  • 业务逻辑设计不当:如将所有用户信息存储在一个哈希中。
  • 数据模型未优化:数据结构选择不当,导致存储效率低下。
  • 过期策略设置不合理:如清理不及时,导致列表数据堆积。

大小的标准

那具体多大才算大key呢?参考标准大致如下:

  • String 类型的值大于 1 MB
  • Hash、List、Set、ZSet类型的元素的个数超过 5000个

影响的后果

大key会带来以下四种危害:

  • 资源消耗:大Key会占用较多的内存资源,可能导致其他数据无法被有效缓存,同时在内存不足时可能触发淘汰机制,影响数据的完整性。
  • 性能影响:操作大Key可能导致处理延迟增加,尤其是在高负载情况下,可能会阻塞其他操作,从而影响Redis的整体性能和响应速度。
  • 内存分布不均:在redis cluster集群模式中,大key一般不会分片分布,造成单节点内存占用过高,出现数据倾斜的情况。
  • 数据一致性和恢复问题:在主从复制和数据迁移场景中,大Key可能导致同步和迁移延迟,增加数据丢失的风险,同时可能延长故障恢复时间。

如何查找大key?

这里介绍一个好用的查找大key的第三方工具,用python语言编写的redis-rdb-tools,可以用来解析 Redis 快照(RDB)文件。要使用该工具得先下载python,具体安装过程可以参考网上的教程,下面介绍几个常用命令:

  • 将rdb文件转成csv文件
代码语言:javascript
复制
rdb -c memory /mnt/data/redis/dump.rdb >  /mnt/data/redis/memory.csv   
  • 导出内存中排名前3的keys
代码语言:javascript
复制
rdb --command memory --largest 3 dump.rdb
  • 导出大于 10 kb 的 key 输出到一个表格文件
代码语言:javascript
复制
rdb dump.rdb -c memory --bytes 10240 -f redis.csv

如何删除bigkey?

针对大key,肯定是要删除的,那怎么删除才最高效呢?直接用del命令行不行?答案是不行。Redis 官方文档描述到:

1、String 类型的key,DEL 时间复杂度是 O(1),大key除外。

2、List/Hash/Set/ZSet 类型的key,DEL 时间复杂度是 O(M),M 为元素数量,元素越多,耗时越久。

一次性删除的后果

大Key如果一次性执行删除操作,会立即触发大量内存的释放过程。这个过程中,操作系统需要将释放的内存块重新插入空闲内存块链表,以便之后的管理和再分配。由于这个过程是同步进行的,并且可能涉及大量的内存块操作,因此它将占用相当一部分处理时间,并可能造成Redis主线程的阻塞。

这种阻塞会导致Redis无法及时响应其他命令请求,从而引起请求超时,超时的累积可能会导致Redis连接耗尽,进而产生服务异常。

因此删除大key,一定要慎之又慎,可以选择异步删除或批量删除。

异步删除

Redis从 4.0开始, 可以使用 UNLINK 命令来异步删除大key,删除大Key的语法与DEL命令相同。

代码语言:javascript
复制
UNLINK bigkey

当使用UNLINK删除一个大Key时,Redis不会立即释放关联的内存空间,而是将删除操作放入后台处理队列中。Redis会在处理命令的间隙,逐步执行后台队列中的删除操作,从而不会显著影响服务器的响应性能。

批量删除

主要是针对Hash、List、Set、Zset,具体操作见下方代码描述

代码语言:javascript
复制
@Component
@Slf4j
public class RedisUtils {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * Hash删除: hscan + hdel
     * @param key 大key
     * @param match 要匹配的hash的key,支持正则表达式
     * @param count  每次扫描的记录数。值越小,扫描次数越过、越耗时。建议设置在1000-10000
     */
    public void delBigHash(String key, String match, int count) {
        ScanOptions scanOptions = ScanOptions.scanOptions().match(match).count(count).build();
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, scanOptions);
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> next = cursor.next();
            redisTemplate.opsForHash().delete(key, next.getKey());
            log.info("del:"+ next.getKey());
        }
        try {
            //遍历完成后,游标需要关闭
            cursor.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * List删除: trim + del
     * @param key
     * @param num 每次删除的个数
     */
    public void delBigList(String key, int num) {
        Long size = redisTemplate.opsForList().size(key);
        int counter = 0;
        while (counter < size) {
            //每次从左侧截掉 num 个
            redisTemplate.opsForList().trim(key, 0, num);
            counter += num;
            log.info("count="+counter);
        }
        //最终删除key
        redisTemplate.delete(key);
    }

    /**
     * Set删除: sscan + srem
     */
    public void delBigSet(String key, int count) {
        ScanOptions scanOptions = ScanOptions.scanOptions().count(count).build();
        Cursor<String> cursor = redisTemplate.opsForSet().scan(key, scanOptions);
        while (cursor.hasNext()) {
            String value = cursor.next();
            redisTemplate.opsForSet().remove(key, value);
            log.info("set del:"+ value);
        }
        try {
            //遍历完成后,游标需要关闭
            cursor.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * ZSet删除: zscan + zrem
     */
    public void delBigZSet(String key, int count) {
        ScanOptions scanOptions = ScanOptions.scanOptions().count(count).build();
        Cursor<ZSetOperations.TypedTuple<String>> cursor = redisTemplate.opsForZSet().scan(key, scanOptions);
        while (cursor.hasNext()) {
            ZSetOperations.TypedTuple<String> next = cursor.next();
            redisTemplate.opsForZSet().remove(key, next.getValue());
            log.info("zset del -> value:"+ next.getValue() + ", score:"+ next.getScore());
        }
        try {
            //遍历完成后,游标需要关闭
            cursor.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

结语

在Redis的世界里,大Key问题就像是一颗隐藏的炸弹,随时可能引发性能危机,但通过合理的策略和持续的优化,就可以有效地控制其对系统性能的影响。


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

本文分享自 程序员小义 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么会有大Key?
    • 出现的原因
      • 大小的标准
        • 影响的后果
        • 如何查找大key?
        • 如何删除bigkey?
          • 一次性删除的后果
            • 异步删除
              • 批量删除
              • 结语
              相关产品与服务
              云数据库 Redis
              腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档