RedisCluster的scan命令

 
在redis中是支持使用通配符的使用,例如‘?’或是’’,所以我们在获取redis里面的某个db里面的所有数据可以用 `keys `这样的指令来实现。但是存在一个问题就是这样做的话,在数据量很大的情况下效率是很不理想的,一般情况下redis的slowlog中总会少不了keys xxx这种类型操作(如果有人在其上面执行该操作的话)
 
scan命令可以帮助我们解决使用keys命令遍历大量数据而导致服务器阻塞的情况,它每次都只遍历一小部分数据,每次操作对应的时间复杂度为O(1)。
 
scan命令是一个基于游标(Cursor)的迭代器,scan命令每次被调用之后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为scan命令的游标参数,延续之前的迭代过程。当scan命令的游标参数被设置成0时,服务器将会开始一次新的迭代,当服务器向用户返回的游标值为0时,表示迭代已经结束。
 
但对于RedisCluster来说,是不可以对所有键进行scan操作的,但可以针对其他数据类型,比如hash, zset,进行一系列hscan,zscan操作。其实可以从jedisCluster的实现可以看出,如果要对所有key进行scan,需要实现MultiKeyCommands,但RedisCluster是不支持这类型操作的,同理pipeline,mget,mset等操作。
 
但在redisCluster中可以通过getClusterNodes获取每个节点的连接,依次进行遍历并查询:
 
 
val jedisCluster = new JedisCluster(hostPorts)
 
    val clusterNodes = jedisCluster.getClusterNodes.values()
    for (clusterNode: JedisPool <- clusterNodes) {
      var scanCursor = "0"
      val resource: Jedis = clusterNode.getResource
   
 
在每个节点上执行scan命令,scan命令的结束条件并不是只有返回下个index=0,也有可能得到的列表为空,此时同样宣告没有新的key了。
 
此时就可以通过一个do/while循环来进行scan操作:
 
for (clusterNode: JedisPool <- clusterNodes) {
      var scanCursor = "0"
      var scanResult: util.List[String] = new util.ArrayList[String]()
      do {
        val resource: Jedis = clusterNode.getResource
        val scan: ScanResult[String] = resource.scan(scanCursor, new ScanParams().`match`(“xxx*"))
        scanCursor = scan.getStringCursor
        scanResult = scan.getResult
        for (key: String <- scanResult) {
          //xxx
        }
        resource.close()
      } while (!scanCursor.equals("0") && !scanResult.isEmpty)
    }
 
 
 
scan时可以设置count(数量)以及match(匹配模式),以便于按照条件进行过滤。
 
http://www.redis.cn/commands/scan.html
 
但需要统计一下通过scan方法执行出来的键值总结果是否与keys执行结果相同(在相同的运行条件下,redis不能随意增加key)。
 
经过我们的验证,在cluster模式下,将所有key放到set下(考虑到master/slave键重复,cluster模式下keys会忽略重复的键,但是每个redis连接进行scan却不会)是相同的:
 
val keys = new mutable.HashSet[String]()
    for (clusterNode: JedisPool <- clusterNodes) {
      var scanCursor = "0"
      var scanResult: util.List[String] = new util.ArrayList[String]()
      do {
        val resource: Jedis = clusterNode.getResource
        val scan: ScanResult[String] = resource.scan(scanCursor, new ScanParams().`match`(PROD_SKU_PRICE + "*"))
        scanCursor = scan.getStringCursor
        scanResult = scan.getResult
        for (key: String <- scanResult) {
          keys.add(key)
        }
        resource.close()
      } while (!scanCursor.equals("0") && !scanResult.isEmpty)
    }
    print (keys.size)
 
 
 
经过验证,总数量是相同的,通过本次测试同时也发现,scan命令的count不会每次真的返回该数量的key值(尽管后续还存在),只会返回比count值小的键值数量。
 
但考虑到在RedisCluster模式下并不能应用scan命令,我们可以对该命令进行一定的改造,使其能够支持按照hash tag进行操作。由于有着同样hash tag的键都会存放在同一个redis节点上,此时仅针对RedisCluster中的单个redis连接操作即可,有点类似之前说过的读写分离方案: http://brandnewuser.iteye.com/blog/2315636 ,直接在之前的方案中,增加scan方法(继承的JedisCluster类中),由于该操作中没有对应的key,因此需要额外传递hashTag用来指定使用到哪个节点来处理:
 
public ScanResult scan(final String hashTag, final String cursor, final ScanParams params) {
    ZhenQueryContextHolder.getInstance().setQueryContext(new ZhenQueryContext(OperationType.READ));
    return new ZhenJedisClusterCommand>(connectionHandler, maxRedirections) {
 
        @Override
        public ScanResult execute(Jedis connection) {
            return connection.scan(cursor, params);
        }
    }.run(hashTag);
}
 
 
参考: 
http://linxiaobai.github.io/2015/01/31/redis%E7%94%A8scan%E8%BF%9B%E8%A1%8C%E6%95%B0%E6%8D%AE%E7%9A%84%E8%BF%AD%E4%BB%A3%E9%81%8D%E5%8E%86/
 

你可能感兴趣的:(redis)