npm install ioredis

ioredis 是 Node 上最流行 + 最强的 Redis 客户端。

基础

import Redis from 'ioredis';

const redis = new Redis({
    host: 'localhost',
    port: 6379,
    password: process.env.REDIS_PASSWORD,
});

// 或用 URL
const redis2 = new Redis('redis://default:password@localhost:6379');

await redis.set('user:42', JSON.stringify({ name: 'Alice' }));
const user = JSON.parse(await redis.get('user:42'));

await redis.quit();

String 操作

await redis.set('key', 'value');
await redis.set('key', 'value', 'EX', 60);          // 60 秒后过期
await redis.set('key', 'value', 'EX', 60, 'NX');    // 不存在才设(锁)

await redis.get('key');                              // 'value' 或 null
await redis.del('key');
await redis.exists('key');                           // 1 / 0

await redis.incr('counter');                         // 自增
await redis.incrby('counter', 10);                   // 加 10
await redis.expire('key', 60);                       // 设过期
await redis.ttl('key');                              // 剩余秒数

Hash

await redis.hset('user:42', 'name', 'Alice');
await redis.hset('user:42', { name: 'Alice', age: 30, email: 'a@x' });
await redis.hget('user:42', 'name');
await redis.hgetall('user:42');                       // 返回对象
await redis.hdel('user:42', 'email');
await redis.hincrby('user:42', 'age', 1);

List(队列)

await redis.rpush('queue', 'task1');                 // 入队(尾)
await redis.lpush('queue', 'task1');                 // 入队(头)

const task = await redis.lpop('queue');               // 出队(头)
const task2 = await redis.rpop('queue');              // 出队(尾)

// 阻塞式(队列消费者用)
const [_, task3] = await redis.blpop('queue', 10);   // 等 10 秒

Set / Sorted Set

// Set
await redis.sadd('online', 'user42');
await redis.smembers('online');
await redis.sismember('online', 'user42');

// Sorted Set(带分数 / 用于排行榜)
await redis.zadd('leaderboard', 100, 'alice');
await redis.zadd('leaderboard', 200, 'bob');
await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');   // top 10

Pub / Sub

// 订阅
const sub = new Redis();
await sub.subscribe('news');
sub.on('message', (channel, msg) => {
    console.log(`[${channel}]`, msg);
});

// 发布(另一个连接)
const pub = new Redis();
await pub.publish('news', 'hello world');

订阅连接不能再做普通命令——必须用单独 Redis 实例。

缓存模式(最常见用法)

async function getUser(id) {
    const cacheKey = `user:${id}`;

    // 1. 试缓存
    const cached = await redis.get(cacheKey);
    if (cached) return JSON.parse(cached);

    // 2. 查 DB
    const user = await db.users.findById(id);
    if (!user) return null;

    // 3. 写缓存(5 分钟过期)
    await redis.set(cacheKey, JSON.stringify(user), 'EX', 300);
    return user;
}

// 改动时清缓存
async function updateUser(id, data) {
    await db.users.update(id, data);
    await redis.del(`user:${id}`);
}

分布式锁

async function withLock(key, ttl, fn) {
    const lockKey = `lock:${key}`;
    const lockValue = Math.random().toString();

    // 设锁(NX = 不存在才设)
    const acquired = await redis.set(lockKey, lockValue, 'EX', ttl, 'NX');
    if (!acquired) throw new Error('Failed to acquire lock');

    try {
        return await fn();
    } finally {
        // 用 Lua 脚本原子检查 + 删
        const script = `
            if redis.call("get", KEYS[1]) == ARGV[1] then
                return redis.call("del", KEYS[1])
            else
                return 0
            end
        `;
        await redis.eval(script, 1, lockKey, lockValue);
    }
}

或用 Redlock 库。

限流

async function checkRateLimit(userId, limit, windowSec) {
    const key = `ratelimit:${userId}`;
    const count = await redis.incr(key);
    if (count === 1) {
        await redis.expire(key, windowSec);
    }
    return count <= limit;
}

if (!await checkRateLimit('user42', 100, 60)) {
    throw new Error('Too many requests');
}

Pipeline(批量减少 RTT)

const pipeline = redis.pipeline();
pipeline.set('a', 1);
pipeline.set('b', 2);
pipeline.set('c', 3);
const results = await pipeline.exec();
// 一次 RTT 完成 3 个操作

看连接情况

redis.on('connect', () => console.log('Connected'));
redis.on('ready', () => console.log('Ready'));
redis.on('error', err => console.error(err));
redis.on('close', () => console.log('Closed'));
redis.on('reconnecting', () => console.log('Reconnecting'));

  • 缓存 key 设计要有命名空间(如 user:42)—— 防止冲突
  • 大 value(> 1 MB)放 Redis 慢——考虑放对象存储
  • 不要把 Redis 当数据库——重要数据 + 持久化要双保险
  • 过期时间设合理——太短 = 频繁查 DB;太长 = 数据陈旧
  • 多实例时用 cluster 或 sentinel——单点风险

下一篇:ORM。