RedisTemplate Cluser scan
参考资料:
https://blog.youkuaiyun.com/qq_36530221/article/details/104950721
https://blog.youkuaiyun.com/weixin_44225613/article/details/104161071
https://www.cnblogs.com/alterem/archive/2019/08/30/11433340.html
最近开发涉及到根据前缀获取redis里面的key值列表然后进行业务处理。最开始直接使用了keys的方式进行查询(redisTemplate.keys(_key);
)。
因为keys命令会使线程阻塞,并且keys是以遍历的方式实现的,时间复杂度是 O(n),Redis库中的key越多,查找时阻塞的时间越长,如果这时候有大量的业务请求送达Redis,有可能导致Redis崩溃。因此需要改用scan
命令来实现。但是官方文档中目前没有支持cluser spring-data-redis就比较尴尬,因此需要自己来实现redis集群模式下的scan。找了一些资料,经过测试有了下面的代码。(这里要注意我的 spring-data-redis是1.8.x版本,如果是其他版本的可能需要换其他方式了。)
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Component;
import redis.clients.jedis.*;
import redis.clients.util.JedisClusterCRC16;
@Component
public class RedisHelper {
private static final Logger log = LoggerFactory.getLogger(RedisHelper.class);
private static final int DEFAULT_COUNT = 1000 ;
@Autowired
private RedisTemplate redisTemplate;
/**
* scan 实现
* @param pattern 表达式
* @param consumer 对迭代到的key进行操作
*/
public void scan(String pattern, Consumer<byte[]> consumer) {
this.redisTemplate.execute((RedisConnection connection) -> {
try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count(Long.MAX_VALUE).match(pattern).build())) {
cursor.forEachRemaining(consumer);
return null;
} catch (IOException e) {
log.error("RedisHelper scan:", e);
throw new RuntimeException(e);
}
});
}
/**
* 获取符合条件的key - 仅支持单机模式
* @param pattern 表达式
* @return
*/
public List<String> keys(String pattern) {
List<String> keys = new ArrayList<>();
this.scan(pattern, item -> {
//符合条件的key
String key = new String(item,StandardCharsets.UTF_8);
keys.add(key);
});
return keys;
}
/**
* 用于redis集群模式下使用scan
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public Set<String> scanx(String key) {
return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> keys = new HashSet<>();
JedisCluster cluster = (JedisCluster) connection.getNativeConnection();
Collection<JedisPool> pools = cluster.getClusterNodes().values();
for (JedisPool pool : pools) {
Jedis resource = pool.getResource();
ScanParams scanParams = new ScanParams();
scanParams.match(key + "*");
scanParams.count(DEFAULT_COUNT);
ScanResult<String> scan = resource.scan("0", scanParams);
while (null != scan.getStringCursor()) {
keys.addAll(scan.getResult());
if (StringUtils.equals("0", scan.getStringCursor())) {
break;
}
scan = resource.scan(scan.getStringCursor(), scanParams);
}
resource.close();
}
return keys;
});
}
}