在NODE中使用Redis SCAN

18

我有一个带有很多键的Redis,它们以某种格式存在,并且我想要获取符合某些模式的键并对它们执行一些操作。由于在生产环境中不推荐使用KEYS方法,因此我不使用它。我想知道在使用SCAN时如何以最佳方式编写代码。我必须像使用承诺的while循环一样做一些事情,我的当前解决方案看起来像这样(代码有点简化):

'use strict'
const Promise = require('bluebird');
const config = require('./config');
const client = require('./clinet');

let iterator = 0;
Promise.coroutine(function* () {
  do {
    iterator = yield clinet.scanAsync(iterator, 'myQuery', 'COUNT', config.scanChunkSize)
      .then(data => {
        let nextIterator = data[0];
        let values = data[1];
        //do some magic with values
        return nextIterator;
      })
  } while (iterator !== '0');
})();

有没有我所不知道的更好的方法?

7个回答

23

我知道这是一个非常古老的问题,但我发现其他所有答案都让人不满意。这里是另一种使用async await相对清洁的扫描方式(不需要使用另一个外部依赖项)。您可以轻松修改此代码以连续删除每组找到的键(在这种情况下,您希望像这样分批处理,以防有很多键)。将它们推入数组只是演示了在此阶段可以对它们进行的一件非常基本的事情。

const redis = require('redis');
const { promisify } = require('util');

const client = redis.createClient({...opts});
const scan = promisify(client.scan).bind(client);

const scanAll = async (pattern) => {
  const found = [];
  let cursor = '0';

  do {
    const reply = await scan(cursor, 'MATCH', pattern);

    cursor = reply[0];
    found.push(...reply[1]);
  } while (cursor !== '0');

  return found;
}

21
你可以使用递归来不断调用扫描,直到完成。
function scanAsync(cursor, pattern, returnSet){

    return redisClient.scanAsync(cursor, "MATCH", pattern, "COUNT", "100").then(
        function (reply) {

            cursor = reply[0];
            var keys = reply[1];
            keys.forEach(function(key,i){
                returnSet.add(key);
            });

            if( cursor === '0' ){
                return Array.from(returnSet);
            }else{
                return scanAsync(cursor, pattern, returnSet)
            }

    });
}

传入Set()确保键不重复

myResults = new Set();

scanAsync('0', "NOC-*[^listen]*", myResults).map( 
    function( myResults ){ console.log( myResults); }
);

我在使用sscan时传递了一个变量,但它没有正常工作,这有什么原因吗? - Suraj Dalvi
要在客户端实例中获取异步方法,请使用 bluebird.promisifyAll(redis.RedisClient.prototype); - sillyslux

11
你可以尝试使用这段代码片段来在每次迭代中 扫描 (1000) 个键并进行 '删除' 操作。
var cursor = '0';
function scan(pattern,callback){

  redisClient.scan(cursor, 'MATCH',pattern,'COUNT', '1000', function(err, reply){
    if(err){
        throw err;
    }
    cursor = reply[0];
    if(cursor === '0'){
        return callback();
    }else{

        var keys = reply[1];
        keys.forEach(function(key,i){                   
            redisClient.del(key, function(deleteErr, deleteSuccess){
                console.log(key);
            });
        });


        return scan(pattern,callback);
    }
  });
}

scan(strkey,function(){
    console.log('Scan Complete');
});

如果reply[1]中有任何内容,那么这段代码将导致无限循环! - loretoparisi

6

使用scan iterators 是对于 node-redis 模块的一个很好的选择。例如:

const redis = require("redis");
const client = redis.createClient();

async function getKeys(pattern="*", count=10) {
    const results = [];
    const iteratorParams = {
        MATCH: pattern,
        COUNT: count
    }
    for await (const key of client.scanIterator(iteratorParams)) {
        results.push(key);
    }
    return results;
}

(当然,如果您只需要在for await循环中即时处理密钥,而不必将它们存储到其他数组中,那么您也可以这样做。)
(如果您不想覆盖扫描参数(MATCH/COUNT),您可以跳过它们并执行没有参数的client.scanIterator()(默认值将被使用,MATCH="*", COUNT=10)。)

2

node redis doc

for await (const key of redisclient.scanIterator()) {
  // use the key!
  const k = await redisclient.get(key);
  console.log(k);
}

这是2023年最简单的方法。其他一些答案可能会导致冻结,这取决于您使用的承诺依赖类型。对于使用express的node js应用程序,可以在链接上查看最佳检查文档。所以如果你在2023年阅读这篇文章,这就是我的答案。

1

我认为Redis的节点绑定在这里将太多的责任推给了调用者。因此,我创建了自己的扫描库,也使用了node中的生成器:

const redis = require('redis')
const client = redis.createClient(…)
const generators = require('redis-async-gen')
const { keysMatching } = generators.using(client)

…

for await (const key of keysMatching('test*')) {
  console.info(key)
}

显然,你应该关心的是最后一部分。不需要自己仔细控制迭代器,只需使用for循环即可。

我在这里写得更多。


在找到3个键后,您的库会返回以下错误 D:\web\node_modules\redis-async-gen\dist\redis-async-gen.js:33 return new Promise(function (resolve, reject) { ^ RangeError: Maximum call stack size exceeded - imin

-1

由于在“进行魔法行”的代码中,我有很多基于 Promises 的操作,因此我想避免传递回调函数并混合其它代码,而是以更加友好的 Promise 方式进行操作。 - Madbrush

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接