一次性取出redis一个field中的所有key,并遍历。
使用redisTemplate.opsForHash().keys("filed")
前期数据量少,未感知到性能问题。后查询资料得知,数据量上去后keys方法严重消耗CPU,一般在生产环境禁用keys方法。
防患于未然,该方式摒弃!
使用redisTemplate游标分批次获取
使用scan主要两个参数:match和count。
match:key的正则表达式
count:每次扫描的记录数。值越小,扫描次数越过、越耗时。建议设置在1000-10000
public void getKeysTest(){
try {
Cursor>> cursor = deviceRedis.opsForHash().scan("filed",
ScanOptions.scanOptions().match("*").count(1000).build());
while (cursor.hasNext()) {
String key = cursor.next().getKey()
Set valueSet = cursor.next().getValue();
}
} catch (IOException e) {
e.printStackTrace();
}
}
使用scan代替keys肯定会导致整个查询消耗的总时间变大,但不会影响redis服务卡顿,影响服务使用。
使用scan代替keys方式后,发现仅测试环境如此低的并发和数据量情况下,redis却经常会报错:
Could not get a resource from the pool !重启后回复正常,然后又重复!
代码中对于redis的存、取、删除、过期设置、游标等操作都有涉及,最开始始终无法定位具体哪里的原因。
以为是RedisTemplate从JedisPool中获取连接使用后没有释放链接造成的。
但RedisTemplate是对Jedis做了二次封装,可自动通过连接池来管理连接。解读RedisTemplate源码后,确认了这一点:
RedisTemplate封装的redis操作方法,大多方法最底层都会调用最核心的excute方法,在excute方法的finally模块可以看到每次操作完成后都会自动关闭释放连接。该原因剔除!
最后通过调低连接池的max-active,多次的测试、重现,最终确认问题的原因是由于调用了scan后redis连接数只升不降。
通过redis-cli进入redis控制台,使用CLIENT LIST命令查看redis客户端连接信息:
其中,age标识已建立连接的时长(单位:秒),cmd标识操作命令。
正常情况下,redis连接信息中99%只应显示ping和auth命令,因为其他redis操作都是操作完成后立即释放连接,但是从上图看到scan命令对应连接的连接时长远高于其他操作,说明连接一直未断开。后来又测试几次,发现scan操作的连接信息只升不降。
最终问题终于定位le:scan操作后,连接没有释放,导致连接池可用连接被用完!
又查看到scan的源码:
@Override
public Cursor> scan(K key, final ScanOptions options) {
final byte[] rawKey = rawKey(key);
return template.executeWithStickyConnection(new RedisCallback>>() {
@Override
public Cursor> doInRedis(RedisConnection connection) throws DataAccessException {
return new ConvertingCursor, Map.Entry>(connection.hScan(rawKey, options),
new Converter, Map.Entry>() {
@Override
public Entry convert(final Entry source) {
return new Map.Entry() {
@Override
public HK getKey() {
return deserializeHashKey(source.getKey());
}
@Override
public HV getValue() {
return deserializeHashValue(source.getValue());
}
@Override
public HV setValue(HV value) {
throw new UnsupportedOperationException("Values cannot be set when scanning through entries.");
}
};
}
});
}
});
没有看到操作完成后关闭/释放/归还连接(或者我没找到吧)。
只能通过手动关闭来实现:
public void getKeysTest(){
try {
Cursor>> cursor = deviceRedis.opsForHash().scan("filed",
ScanOptions.scanOptions().match("*").count(1000).build());
while (cursor.hasNext()) {
String key = cursor.next().getKey()
Set valueSet = cursor.next().getValue();
}
//关闭scan
cursor.close();
} catch (IOException e) {
e.printStackTrace();
}
}
最后问题解决了,未再出现Could not get a resource from the pool!错误。
与各位共勉!