这篇文章,我想和你聊一聊在使用 Redis 时,可能会踩到的「坑」。
如果你在使用 Redis 时,也遇到过以下这些「诡异」的场景,那很大概率是踩到「坑」了:
明明一个 key 设置了过期时间,怎么变成不过期了?
使用 O(1) 复杂度的 SETBIT 命令,Redis 竟然被 OOM 了?
执行 RANDOMKEY 随机拿出一个 key,竟然也会阻塞 Redis?
同样的命令,为什么主库查不到数据,从库却可以查到?
从库内存为什么比主库用得还多?
写入到 Redis 的数据,为什么莫名其妙丢了?
…
究竟是什么原因,导致的这些问题呢?
这篇文章,我就来和你盘点一下,使用 Redis 时可能会踩到「坑」,以及如何去规避。
我把这些问题划分成了三大部分:
常见命令有哪些坑?
数据持久化有哪些坑?
主从库同步有哪些坑?
导致这些问题的原因,很有可能会「颠覆」你的认知,如果你准备好了,那就跟着我的思路开始吧!
这篇文章干货很多,希望你可以耐心读完。
首先,我们来看一下,平时在使用 Redis 时,有哪些常见的命令会遇到「意料之外」的结果。
1) 过期时间意外丢失?
你在使用 Redis 时,肯定经常使用 SET 命令,它非常简单。
SET 除了可以设置 key-value 之外,还可以设置 key 的过期时间,就像下面这样:
127.0.0.1:6379> SET testkey val1 EX 60 OK 127.0.0.1:6379> TTL testkey (integer) 59
此时如果你想修改 key 的值,但只是单纯地使用 SET 命令,而没有加上「过期时间」的参数,那这个 key 的过期时间将会被「擦除」。
127.0.0.1:6379> SET testkey val2 OK 127.0.0.1:6379> TTL testkey // key永远不过期了! (integer) -1
看到了么?testkey 变成永远不过期了!
如果你刚刚开始使用 Redis,相信你肯定也踩过这个坑。
导致这个问题的原因在于:SET 命令如果不设置过期时间,那么 Redis 会自动「擦除」这个 key 的过期时间。
如果你发现 Redis 的内存持续增长,而且很多 key 原来设置了过期时间,后来发现过期时间丢失了,很有可能是因为这个原因导致的。
这时你的 Redis 中就会存在大量不过期的 key,消耗过多的内存资源。
所以,你在使用 SET 命令时,如果刚开始就设置了过期时间,那么之后修改这个 key,也务必要加上过期时间的参数,避免过期时间丢失问题。
2) DEL 竟然也会阻塞 Redis?
删除一个 key,你肯定会用 DEL 命令,不知道你没有思考过它的时间复杂度是多少?
O(1)?其实不一定。
如果你有认真阅读 Redis 的官方文档,就会发现:删除一个 key 的耗时,与这个 key 的类型有关。
Redis 官方文档在介绍 DEL 命令时,是这样描述的:
key 是 String 类型,DEL 时间复杂度是 O(1)
key 是 List/Hash/Set/ZSet 类型,DEL 时间复杂度是 O(M),M 为元素数量
也就是说,如果你要删除的是一个非 String 类型的 key,这个 key 的元素越多,那么在执行 DEL 时耗时就越久!
为什么会这样?
原因在于,删除这种 key 时,Redis 需要依次释放每个元素的内存,元素越多,这个过程就会越耗时。
而这么长的操作耗时,势必会阻塞整个 Redis 实例,影响 Redis 的性能。
所以,当你在删除 List/Hash/Set/ZSet 类型的 key 时,一定要格外注意,不能无脑执行 DEL,而是应该用以下方式删除:
查询元素数量:执行 LLEN/HLEN/SCARD/ZCARD 命令
判断元素数量:如果元素数量较少,可直接执行 DEL 删除,否则分批删除
分批删除:执行 LRANGE/HSCAN/SSCAN/ZSCAN + LPOP/RPOP/HDEL/SREM/ZREM 删除
了解了 DEL 对于 List/Hash/Set/ZSet 类型数据的影响,我们再来分析下,删除一个 String 类型的 key 会不会有这种问题?
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
啊?前面不是提到,Redis 官方文档的描述,删除 String 类型的 key,时间复杂度是 O(1) 么?这不会导致 Redis 阻塞吧?
其实这也不一定!
你思考一下,如果这个 key 占用的内存非常大呢?
例如,这个 key 存储了 500MB 的数据(很明显,它是一个 bigkey),那在执行 DEL 时,耗时依旧会变长!
这是因为,Redis 释放这么大的内存给操作系统,也是需要时间的,所以操作耗时也会变长。
所以,对于 String 类型来说,你最好也不要存储过大的数据,否则在删除它时,也会有性能问题。
此时,你可能会想:Redis 4.0 不是推出了 lazy-free 机制么?打开这个机制,释放内存的操作会放到后台线程中执行,那是不是就不会阻塞主线程了?
这个问题非常好。
真的会是这样吗?
这里我先告诉你结论:即使 Redis 打开了 lazy-free,在删除一个 String 类型的 bigkey 时,它仍旧是在主线程中处理,而不是放到后台线程中执行。所以,依旧有阻塞 Redis 的风险!
这是为什么?
这里先卖一个关子,感兴趣的同学可以先自行查阅 lazy-free 相关资料寻找答案。