场景:
一次性取出redis一个field中的所有key,并遍历。
方案一:(生产环境禁用keys)
使用redisTemplate.opsForHash().keys("filed")
前期数据量少,感j觉不到性能问题。数据量上去后keys方法严重消耗CPU,导致服务"假死", 其他连接阻塞 , 一般在生产环境禁用keys方法。
方案二:
使用redisTemplate游标分批次获取
使用scan主要两个参数:match和count。
match:key的正则表达式
count:每次扫描的记录数。值越小,扫描次数越过、越耗时。建议设置在1000-10000
Spring RedisTemplate实现scan
1. hscan sscan zscan
- 例子中的"field"是值redis的key,即从key为"field"中的hash中查找
- redisTemplate的opsForHash,opsForSet,opsForZSet 可以 分别对应 sscan、hscan、zscan
- 也可以使用
(JedisCommands) connection.getNativeConnection()
的 hscan、sscan、zscan 方法实现cursor遍历,
try { Cursor<Map.Entry<Object,Object>> cursor = redisTemplate.opsForHash().scan("field", ScanOptions.scanOptions().match("*").count(1000).build()); while (cursor.hasNext()) { Map.Entry<Object,Object> entry = cursor.next(); Object key = entry.getKey(); Object valueSet = entry.getValue(); } //关闭cursor cursor.close(); } catch (IOException e) { e.printStackTrace(); }
- cursor.close(); 游标一定要关闭,不然连接会一直增长;
- 可以使用
client lists
info clients
info stats
命令查看客户端连接状态,会发现scan操作一直存在
2. scan
2.1 使用spring-data-redis封装好的scan方法
public Set<String> scan(String matchKey) { Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<String> keysTmp = new HashSet<>(); Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("*" + matchKey + "*").count(1000).build()); while (cursor.hasNext()) { keysTmp.add(new String(cursor.next())); } return keysTmp; }); return keys; }
2.2 使用redis.clients.jedis的MultiKeyCommands,自己循环scan
- 获取
connection.getNativeConnection
;connection.getNativeConnection()
实际对象是Jedis(debug可以看出) ,Jedis实现了很多接口
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands, AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands
- 当 scan.getStringCursor() 存在 且不是 0 的时候,一直移动游标获取
public Set<String> scan(String key) { return redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<String> keys = Sets.newHashSet(); JedisCommands commands = (JedisCommands) connection.getNativeConnection(); MultiKeyCommands multiKeyCommands = (MultiKeyCommands) commands; ScanParams scanParams = new ScanParams(); scanParams.match("*" + key + "*"); scanParams.count(1000); // 这个不是返回结果的数量,应该是每次scan的数量 ScanResult<String> scan = multiKeyCommands.scan("0", scanParams); while (null != scan.getStringCursor()) { keys.addAll(scan.getResult()); // 这一次scan match到的结果 if (!StringUtils.equals("0", scan.getStringCursor())) { // 不断拿着新的cursor scan,最终会拿到所有匹配的值 scan = multiKeyCommands.scan(scan.getStringCursor(), scanParams); continue; } else { break; } } return keys; }); }
作者:南岩飞雪
链接:https://www.jianshu.com/p/4c842c41ba41
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
链接:https://www.jianshu.com/p/4c842c41ba41
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。