场景:

一次性取出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.getNativeConnectionconnection.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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted on 2021-09-18 16:02  lshan  阅读(165)  评论(0编辑  收藏  举报