You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否通过游标以并行方式高效扫描Redis?

Redis并行SCAN扫描实现方案

你的串行SCAN确实会因为单游标依赖导致扫描效率偏低,想要并行扫描且避免游标重叠/数据重复遗漏,基于Redis哈希槽的分槽并行扫描是可行且可靠的方案,核心逻辑如下:

原理说明

Redis的所有键会通过CRC16(key) & 16383计算哈希槽,总共16384个独立槽位,每个键唯一归属一个槽。我们可以针对每个槽位发起独立的SCAN请求:

  • 每个槽的SCAN游标完全独立,不需要依赖其他槽的扫描结果
  • Redis 6.2+支持HASHSLOT参数直接过滤指定槽的键,低版本也可在客户端计算键的槽位进行筛选
  • 多个槽的扫描任务可以并行执行,同时每个槽内部保持串行的游标迭代,既保证效率又不会出现数据重复或遗漏

具体实现(Redis 6.2+版本,推荐)

用async.queue控制并发槽数,每个槽独立完成扫描并流式输出结果:

const redis = require('redis');
const async = require('async');

async function scanRedisKeys(req, res, next) {
    const client = redis.createClient({ host: 'localhost', port: 6379 });
    await client.connect(); // 改用Promise API更易维护

    // 并发限制:同时扫描10个槽,可根据Redis性能调整
    const scanQueue = async.queue(async (slot, callback) => {
        let cursor = '0';
        do {
            try {
                // 针对当前槽发起SCAN,HASHSLOT参数指定扫描的槽位
                const result = await client.scan(cursor, {
                    MATCH: '*',
                    COUNT: 1000, // 每个SCAN请求返回更多结果,减少请求次数
                    HASHSLOT: slot
                });
                cursor = result.cursor;
                // 流式输出当前槽的键
                for (const key of result.keys) {
                    res.write(`${key}\n`);
                }
            } catch (err) {
                console.error(`扫描槽${slot}出错:`, err);
                res.status(400).end();
                callback(err);
                return;
            }
        } while (cursor !== '0');
        callback();
    }, 10);

    // 把16384个槽全部加入队列
    for (let slot = 0; slot < 16384; slot++) {
        scanQueue.push(slot);
    }

    // 所有槽扫描完成后结束响应
    scanQueue.drain(() => {
        res.status(200).end();
        client.quit();
    });

    // 队列出错处理
    scanQueue.error((err) => {
        console.error('扫描队列出错:', err);
        if (!res.headersSent) {
            res.status(400).end();
        }
        client.quit();
    });
}

低版本Redis兼容方案(<6.2)

如果你的Redis版本低于6.2,不支持HASHSLOT参数,可以在客户端计算每个返回键的哈希槽,只保留当前槽的键:

// 新增计算哈希槽的工具函数
function getHashSlot(key) {
    const crc = require('crc').crc16;
    return crc(key) & 16383;
}

// 修改槽扫描逻辑
const scanQueue = async.queue(async (targetSlot, callback) => {
    let cursor = '0';
    do {
        try {
            const result = await client.scan(cursor, {
                MATCH: '*',
                COUNT: 1000
            });
            cursor = result.cursor;
            // 客户端过滤出属于当前目标槽的键
            const targetKeys = result.keys.filter(key => getHashSlot(key) === targetSlot);
            for (const key of targetKeys) {
                res.write(`${key}\n`);
            }
        } catch (err) {
            console.error(`扫描槽${targetSlot}出错:`, err);
            res.status(400).end();
            callback(err);
            return;
        }
    } while (cursor !== '0');
    callback();
}, 10);

注意事项

  • 并发数不要设置过高(建议10-20),避免给Redis造成过大的CPU/IO压力
  • COUNT参数可以根据键的数量调整,数值越大单次请求返回的键越多,请求次数越少
  • 流式输出时,Node.js的res.write是单线程执行的,不用担心并发写入的顺序混乱问题,不同槽的结果会自然交织输出,保证流畅性
  • 记得处理Redis连接错误、扫描错误等异常情况,避免客户端长时间等待

内容的提问来源于stack exchange,提问作者Alexander Mills

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 20:40:34