Redis使用Scan而不是Keys

当需要模糊查询redis数据库中某一个key的时候,可以使用两种方式keysscan。推荐使用scan

以在 Node.js 中使用 ioredis 操作 Redis 的 KEYSSCAN 为例:

前提条件

确保你已经安装了 ioredis 包。如果尚未安装,可以使用以下命令进行安装:

npm install ioredis

基本设置

首先,导入 ioredis 并创建一个 Redis 客户端实例:

const Redis = require('ioredis');

// 创建 Redis 客户端
const redis = new Redis({
  host: 'localhost', // Redis 服务器地址
  port: 6379,        // Redis 服务器端口
  // password: 'your_password', // 如果 Redis 设置了密码,取消注释并设置
});

使用 KEYS 命令

适用场景

  • 简单查询,适用于开发和调试环境。

  • 当 Redis 数据库中的键数量较少时。

使用方法

async function getKeysUsingKeys(pattern) {
  try {
    // 执行 KEYS 命令
    const keys = await redis.keys(pattern);
    console.log('Matching keys:', keys);
    return keys;
  } catch (error) {
    console.error('Error fetching keys with KEYS:', error);
  }
}

// 示例用法
const token = '12345';
const pattern = `*token:${token}`;
getKeysUsingKeys(pattern);

解释

  • redis.keys(pattern):异步执行 KEYS 命令,返回所有匹配指定模式的键。

  • 模式构建:例如,*token:12345 匹配任何以 token:12345 结尾的键,比如 user:abc:token:12345

示例输出

Matching keys: [ 'user:abc:token:12345', 'admin:token:12345' ]

注意事项

  • 性能问题KEYS 命令在 Redis 中具有线性时间复杂度(O(N)),当键的数量庞大时,会导致显著的性能下降。

  • 阻塞性:在执行期间,Redis 服务器可能会阻塞,影响其他客户端的请求。

使用 SCAN 命令

适用场景

  • 生产环境中,键数量较大时。

  • 需要非阻塞地迭代键。

使用方法

基本 SCAN 使用

async function getKeysUsingScan(pattern) {
  let cursor = '0';
  const matchingKeys = [];

  do {
    // 执行 SCAN 命令
    const result = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
    cursor = result[0];
    const keys = result[1];
    matchingKeys.push(...keys);
  } while (cursor !== '0');

  console.log('Matching keys:', matchingKeys);
  return matchingKeys;
}

// 示例用法
const token = '12345';
const pattern = `*token:${token}`;
getKeysUsingScan(pattern);

使用 ioredis 的 Stream API

ioredis 提供了更高级的迭代方法,通过 Stream API 可以更方便地处理大量键。

const { Readable } = require('stream');

async function getKeysUsingScanStream(pattern) {
  const stream = redis.scanStream({
    match: pattern,
    count: 100, // 每次扫描的数量,可以根据需要调整
  });

  const matchingKeys = [];

  stream.on('data', (keys) => {
    for (const key of keys) {
      matchingKeys.push(key);
    }
  });

  // 返回一个 Promise 来等待流的结束
  await new Promise((resolve, reject) => {
    stream.on('end', resolve);
    stream.on('error', reject);
  });

  console.log('Matching keys:', matchingKeys);
  return matchingKeys;
}

// 示例用法
const token = '12345';
const pattern = `*token:${token}`;
getKeysUsingScanStream(pattern);

解释

  • redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100)

    • cursor:扫描的起始位置,首次调用时为 '0'

    • 'MATCH', pattern:指定匹配模式。

    • 'COUNT', 100:每次扫描返回的键数,实际返回的数量可能稍有不同。

  • scanStream

    • ioredis 提供的流接口,自动处理游标迭代,简化扫描过程。

    • 适合处理大量键时使用,避免一次性内存占用过高。

优点

  • 非阻塞SCAN 命令不会阻塞 Redis 服务器,适合生产环境。

  • 渐进式扫描:可以分批处理键,降低内存和 CPU 的压力。

示例输出

Matching keys: [ 'user:abc:token:12345', 'admin:token:12345' ]

注意事项

  • 不保证顺序SCAN 命令返回的键顺序不固定。

  • 可能重复或遗漏:在并发修改 Redis 数据库时,SCAN 可能会返回重复或遗漏的键。需要在应用逻辑中处理这些情况。

总结

  • KEYS 命令

    • 优点:简单易用,适合开发和调试。

    • 缺点:在键数量庞大时性能差,可能阻塞 Redis 服务器。

  • SCAN 命令

    • 优点:非阻塞,适合生产环境,支持大规模键的迭代。

    • 缺点:需要多次调用,复杂度稍高,可能有重复或遗漏。

  • ioredisscanStream API

    • 优点:简化 SCAN 迭代过程,适合处理大量键。

    • 缺点:需要理解流的操作方式。