Redis的BigKey

发布于:2024-03-01 ⋅ 阅读:(46) ⋅ 点赞:(0)

1. 常见面试题

  • 海量数据里查询某一个固定前缀的key?
  • 你如何生产上限制key * /flushdb /flushall等危险命令以防止误删误用?
  • Memory Usage命令你用过吗?
  • 多大算BigKey?你如何发现?如何删除?如何处理?
  • BigKey你做过调优吗?惰性释放lazyfree你了解过吗?
  • Morekey问题,生产上redis数据库有1000W条记录,你如何遍历?key*可以吗?

2. MoreKey案例

Morekey问题,生产上redis数据库有1000W条记录,你如何遍历?key*可以吗?

  • 大批量往redis中出入测试数据

这里使用linux脚本往redis中出入100万条数据

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

然后使用管道--pipe命令插入100w大批量数据

cat /tmp/redisTest.txt /opt/redis-7.0.0/src/redis-cli -h 127.0.0.1 -p 6379 -a 111111 --pipe

由于我的redis是部署在docker中的,我直接用java程序向redis中写了100万条数据(速度会很慢)。

public class JedisTest {
    public static void main(String[] args) {
        //1.或的Connection,通过ip和断开
        Jedis jedis=new Jedis("127.0.0.1",6379);
        //2.指定访问redis的密码(我的redis没有设置密码)
        //jedis.auth("111111");
        //3.测试连接是否成功
        System.out.println(jedis.ping());
        //4.操作redis
        for(int i=0;i<100*10000;i++){
            jedis.set("k"+i,"v"+i);
        }
    }
}

100万条数据插入完毕
在这里插入图片描述

下面使用keys *查询一下所有数据
在这里插入图片描述
我们可以发现查询时间达到了恐怖的19s,这就是key *的致命弊端,在实际环境中不要用。由于这个指令没有offset、limit参数,是一次性吐出所有满足条件的key,由于redis是单线程的,其所有操作都是原子的,而keys *算法是遍历算法,复杂度是O(N),如果实例中有千万级以上的key,这个指令就导致redis服务卡顿,所有读写Redis的其它指令都会阻塞,可能会引起缓存雪崩甚至数据库宕机。

生产上如何限制keys * /flushdb /flushall等危险命令使用?

通过配置设置禁用这些命令,redis.conf在SECURITY这一项中

rename-command keys ""
rename-command flushdb ""
rename-command flushall ""

在这里插入图片描述
要想避免使用key*,同时又想遍历数据,此时就可以使用Scan命令。该命令用于迭代数据库中的键,还衍生出了sscan(迭代集合的键),hscan(迭代哈希键)和zscan(迭代有序集合)

scan cursor [match pattern] [Count count]
  • cursor:游标
  • pattern:匹配的模式
  • count:指定从数据集里返回多少元素,默认为10

该命令是基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程,以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历,不保证每次执行都返回某个给定数量的元素,他还支持模糊查询。该命令一次返回的数量不可控,只是大概率符合count参数。

scan返回一个包含两个元素的数组,第一个元素是用于下一次scan操作的新游标位置,第二个元素是一个数组,这个数组中包含了所有被迭代的元素,如果新游标返回0,则说明本次迭代结束
在这里插入图片描述
在这里插入图片描述

scan的遍历顺序非常特别,他不是从第一维度数组的0位一直遍历到末尾,而是采用了高位进位加法来遍历。之所以使用这种特殊的方法遍历,是考虑哈希槽的扩容和缩容时避免槽位的遍历重复和遗漏。解释

2. BigKey

多大算BigKey?你如何发现?如何删除?如何处理?

其实bigkey大的不是key本身,而是它对应的value

阿里云规范中说到,string类型控制在10kb以内,hash、list、set、zset元素个数不要超过5000。非字符串的我bigkey不要使用del删除,使用hscan、sscan、zscan方式渐进删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会出现在慢查询中)

bigkey会造成下面这些危害:

  • 内存不均,集群迁移困难
  • 超时删除,大key删除作梗
  • 网络流程阻塞

大key产生通常不是人为的,如下面两个案例:

  • 社交类:哥哥粉丝列表逐步递增
  • 报表类:某个报表,月日年经年累月的积累

如何发现大key,有两个命令可以使用,首先是redis-cli --bigkeys然后是Memory usage

#注意如果想查询10kb的所有key,这个命令就没什么用了
redis-cli --bigkeys

在这里插入图片描述

加上-i 0.1s这个参数,scan指令就会每隔100条scan就会休眠0.1s,ops(每s运算次数)就不会剧烈抖动,但是扫描时间会增加

# memory usage key [simples count]
Memory usage

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

在这里插入图片描述
如何删除bigkey,参考前面说到的阿里规范,我们使用使用hscan、sscan、zscan方式渐进删除。

  • String类型的bigkey删除:一般使用del,如果过于庞大使用unlink异步删除
  • Hash类型bigkey删除:使用hscan每次获取少量的field-value,再使用hdel删除每个field(一步一步掏空,最后删除)

在这里插入图片描述

  • list类型:使用ltrim渐进式删除,直到全部删除完毕,思路和hash一样

在这里插入图片描述

  • set类型:使用sscan每次获取部分元素,再使用srem命令删除每个元素

在这里插入图片描述

  • Zset类型:使用zscan每次获取部分元素,再使用ZREMRANGEBYRANK命令删除每个元素

在这里插入图片描述

2. BigKey生产调优

BigKey你做过调优吗?惰性释放lazyfree你了解过吗?

redis.conf配置文件的LAZY FREEING部分可以对lazyfree进行配置,Redis有两个原语来删除键。一种称为del,是对象的阻塞删除,这意味着服务器停止处理新的命令,以便以同步方式回收与对象关联的所有内存,如果删除的键与一个小对象关联,则执行del命令所需的时间非常短,可与大多数其他命令相媲美。Redis还提供了非阻塞原语,例如UNLINK 以及FLUSHALL和FLUSHDB ASYNC选项,以便在后台回收内存,这些命令在恒定的时间内执行,另一个线程将尽可能快地释放后台中的对象。

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del yes
replica-lazy-flush no

lazyfree-lazy-user-del yes
lazyfree-lazy-user-flush no