Redis的BigKey
如图避免了bigkey就能避免80%的问题,那还等什么直接开锤。
1、概念:
Redis是基于内存的Key-Value数据存储系统,"Big Key" 通常指的是键的值(Value)过大,而不是键(Key)本身过大。
2、多大算大
参考《阿里云Redis开发规范》
3、有哪些危害
1)、内存分部不均匀,数据倾斜
如下图所示:
集群中有4个分片,每个分片大约 有102个key,实际上是均匀分布。图中第三个key叫key301,hash301,中间有一个放了200w的hash,但因为根据 hash301打散的这个key是个 bigkey,严重造成数据倾斜。
别的key只用了10%或20%的内存,key301用了约80%,而且大概率是热点。上图的使用用法,有可能造成有一个分片 内存满了,访问出了问题,但是其他分片却用的很闲。问题分片的访问比较热,造成网卡打满,或者CPU打满,导致限 流,服务可能就夯住了。
2)、网络传输
- 传输延迟:读取或写入大型值需要更多的时间,尤其是在网络延迟较高的情况下,会导致操作的响应时间变长。
- 网络拥塞:大型值的读取或写入可能会占用大量的网络带宽,可能导致网络拥塞,影响其他客户端的通信。
- 传输错误:在网络不稳定的情况下,大型值的传输可能会导致数据包丢失或错误,需要额外的重传操作。
- 客户端超时:如果一个键的值过大,可能会导致客户端的超时错误,因为在网络传输大量数据时可能会超过客户端的超时阈值。
3)、超时删除
- 删除操作超时:当尝试删除一个大型键时,可能会因为数据量过大而导致删除操作超时。
- 删除操作阻塞其他操作:当尝试删除一个大型键时,可能会导致Redis服务器在执行删除操作期间阻塞其他操作。
- 内存占用过高:在执行删除操作期间,可能会导致内存占用过高,影响Redis服务器的性能。
- 持久化问题:如果使用持久化方式如RDB或AOF,大型键的删除可能会导致持久化文件变得非常大。
4、产生原因举例
- 使用Redis作为缓存来存储特别热门的商品信息,它有成百上千的评论和多张高清图片。
- 汇总统计某个日积月累的报表的数据。
- 社交类,粉丝列表数据等
5、排查bigkey
(1)、MEMORY USAGE指令:
MEMORY USAGE 是Redis提供的一个命令,用于获取指定键的内存占用情况。它可以用来查看Redis中某个键所占用的内存大小,以字节(bytes)为单位。
注意,MEMORY USAGE 命令仅适用于Redis 4.0及以上版本
(2)、redis-cli --bigkeys
- 遍历所有的键(keys)。
- 对每个键执行
MEMORY USAGE命令以获取其内存占用情况。 - 如果某个键的内存占用超过了阈值(默认为1024字节),则将其列为大型键。
redis-cli --bigkeys
# 可以通过 --threshold <value> 参数来设置
redis-cli --bigkeys --threshold 2048
6、如何删除bigkey
普通命令的删除操作
| String | 一般用del,过于庞大unlink |
|---|---|
| Hash | scan获取少量的field-value,在使用hdel删除 |
| List | 使用itrim渐进式逐步删除 |
| Set | sscan获取部分元素再使用srem删除每一个元素 |
| Zset | zscan获取部分元素再使用ZREMRANGEBYRANK删除每一个 |
1、hash删除:hscan + hdel
public void delBigHash(String host, int port, String password, String bigHashKey) {
Jedis jedis = new Jedis(host, port);
if (password != null && !"".equals(password)) {
jedis.auth(password);
}
ScanParams scanParams = new ScanParams().count(100);
String cursor = "0";
do {
ScanResult<Entry<String, String>> scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
List<Entry<String, String>> entryList = scanResult.getResult();
if (entryList != null && !entryList.isEmpty()) {
for (Entry<String, String> entry : entryList) {
jedis.hdel(bigHashKey, entry.getKey());
}
}
cursor = scanResult.getStringCursor();
} while (!"0".equals(cursor));
//删除bigkey
jedis.del(bigHashKey);
}
2、List删除: ltrim
public void delBigList(String host, int port, String password, String bigListKey) {
Jedis jedis = new Jedis(host, port);
if (password != null && !"".equals(password)) {
jedis.auth(password);
}
long llen = jedis.llen(bigListKey);
int counter = 0;
int left = 100;
while (counter < llen) {
//每次从左侧截掉100个
jedis.ltrim(bigListKey, left, llen);
counter += left;
}
//最终删除key
jedis.del(bigListKey);
}
3、Set删除: sscan + srem
public void delBigSet(String host, int port, String password, String bigSetKey) {
Jedis jedis = new Jedis(host, port);
if (password != null && !"".equals(password)) {
jedis.auth(password);
}
ScanParams scanParams = new ScanParams().count(100);
String cursor = "0";
do {
ScanResult<String> scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
List<String> memberList = scanResult.getResult();
if (memberList != null && !memberList.isEmpty()) {
for (String member : memberList) {
jedis.srem(bigSetKey, member);
}
}
cursor = scanResult.getStringCursor();
} while (!"0".equals(cursor));
//删除bigkey
jedis.del(bigSetKey);
}
4、SortedSet删除: zscan + zrem
public void delBigZset(String host, int port, String password, String bigZsetKey) {
Jedis jedis = new Jedis(host, port);
if (password != null && !"".equals(password)) {
jedis.auth(password);
}
ScanParams scanParams = new ScanParams().count(100);
String cursor = "0";
do {
ScanResult<Tuple> scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
List<Tuple> tupleList = scanResult.getResult();
if (tupleList != null && !tupleList.isEmpty()) {
for (Tuple tuple : tupleList) {
jedis.zrem(bigZsetKey, tuple.getElement());
}
}
cursor = scanResult.getStringCursor();
} while (!"0".equals(cursor));
//删除bigkey
jedis.del(bigZsetKey);
}
7、bigkey生产调优
1、lazyfree-lazy-server-del
lazyfree-lazy-server-del 控制在Redis的服务器(redis-server)上进行懒惰删除(lazy eviction)时的策略。懒惰删除是指在服务器需要内存空间时,Redis会查找并删除一些过期的键来释放内存,但并不会立即释放被删除键所占用的内存空间,而是在有需要时才会释放。
这个配置选项有两个可能的值:
lazyfree:表示启用懒惰删除策略。no:表示关闭懒惰删除策略,即在删除键时会立即释放相应的内存空间。
默认情况下,Redis会使用lazyfree,也就是启用懒惰删除策略。
2、replica-lazy-flush
replica-lazy-flush 控制了Redis从节点是否允许执行懒惰清空(Lazy Flushing)操作。懒惰清空是指在从节点上执行数据清空操作时,可以选择等待一段时间再执行,以便批量处理多个清空操作,从而减少对主节点的负担。
这个配置选项有两个可能的值:
yes:表示从节点允许执行懒惰清空操作。no:表示从节点不允许执行懒惰清空操作,即立即执行清空操作。
默认情况下,replica-lazy-flush 的值为 yes,也就是允许从节点执行懒惰清空操作。
3、lazyfree-lazy-user-del
lazyfree-lazy-user-del支持yes或者no。默认是no。- 如果设置为
yes,那么del命令就等价于unlink,也是非阻塞删除。