在Redis中,"bigkey"是指占用较大内存空间的键(key)。对于bigkey的判定标准,《阿里云redis开发规范》中规定了:对于String类型value,value占用空间大于10kb,对于list,hash,set,zset类型若元素个数超过5000,就算bigkey。
bigkey有哪些危害?
① 超时删除,对于大key,删除是及其耗时的,由于redis命令的执行是单线程的,删除大key会导致主进程阻塞影响性能。
② 内存不均,集群迁移困难。
③ 网络流量阻塞
如何删除bigkey?
对于String类型的bigkey,不用del命令进行删除,使用unlink命令删除。
对于 list , hash , set , zset 类型的bigkey我们采用 hscan , sscan , zscan 命令渐进式删除。
scan命令:是一个基于游标的迭代器, 返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标, 而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。如果新游标返回 0 表示迭代已结束。
list :采用 ltrim 进行渐进式删除,直到全部删除完成。
hash : 采用 hscan 和 hdel 命令分批次删除,使用hscan每次获取少量的元素(键值对),再使用hdel命令删除。
set :采用 sscan 和 srem 命令分批次删除。
zset : 采用 zscan 和 zremrangebyrank 命令批次删除。
为什么bigkey采用渐进式删除就可以减少删除操作的阻塞时间呢?
在Redis中,所有的命令操作都是按顺序执行的,一个命令执行完成后才能执行下一个命令。当执行一个比较耗时的命令(删除大key)时,Redis主进程就会被阻塞,无法处理其他命令请求。
通过将删除操作分批次执行,可以让每个批次的删除操作执行的时间较短,从而减少了每次删除的阻塞时间。在每个删除批次之间,Redis主进程有机会处理其他命令请求,包括读取、写入和其他操作。这样就实现了删除操作与其他操作之间的交替执行。通过合理的设置每个批次的删除数据量和删除间隔,可以让系统在删除大key的同时保持较好的响应性能,提高整体的吞吐量和并发处理能力。
需要注意的是,虽然分批次删除可以减少删除操作的阻塞时间,但是仍然会有一定的性能影响。在删除大量数据时,仍然需要考虑系统的负载和响应时间,以避免过度消耗资源或影响用户体验。因此,需要根据实际情况进行测试和调优,找到适合系统的最佳批次大小和删除间隔。
bigkey删除案例:
public void delBigHash(String ip,int port,String password,String bigHashKey) {
Jedis jedis = new Jedis(ip, port);
if (password != null && "".equals(password)) {
jedis.auth(password);
}
ScanParams scanParams = new ScanParams().count(100);
String cursor = "0";
do {
ScanResult> scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
List> result = scanResult.getResult();
if (result != null && !result.isEmpty()) {
for (Map.Entry entry : result) {
jedis.hdel(bigHashKey, entry.getKey());
}
}
cursor = scanResult.getCursor();
} while (!"0".equals(cursor));
//删除bigkey
jedis.del(bigHashKey);
}
public void delBigList(String bigListKey,String ip,int port,String password){
Jedis jedis = new Jedis(ip, port);
if(password != null && "".equals(password)){
jedis.auth(password);
}
long llen = jedis.llen(bigListKey);
long left = 100;
long count = 0;
while(count < llen) {
String ltrim = jedis.ltrim(bigListKey, left, llen);
System.out.println(ltrim);
count += left;
}
jedis.del(bigListKey);
}
public void delBigSet(String ip,int port,String password,String bigSetKey){
Jedis jedis = new Jedis(ip, port);
if(password != null && "".equals(password)){
jedis.auth(password);
}
//每次扫描个数
ScanParams scanParams = new ScanParams().count(100);
String cursor = "0";
do {
ScanResult scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
List result = scanResult.getResult();
if(result != null && !result.isEmpty()) {
for (String member : result) {
jedis.srem(bigSetKey, member);
}
}
cursor = scanResult.getCursor();
}while("0".equals(cursor));
jedis.del(bigSetKey);
}
public void delBigZset(String ip,int port,String password,String bigZsetKey){
Jedis jedis = new Jedis(ip, port);
if(password != null && "".equals(password)){
jedis.auth(password);
}
//每次扫描个数
ScanParams scanParams = new ScanParams().count(100);
String cursor = "0";
do {
ScanResult scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
List result = scanResult.getResult();
if(result != null && !result.isEmpty()) {
for (Tuple tuple : result) {
jedis.zrem(bigZsetKey, tuple.getElement());
}
}
cursor = scanResult.getCursor();
}while("0".equals(cursor));
jedis.del(bigZsetKey);
}