Redis 使用场景:缓存、队列和限流

Backend2026-10-06TryQuickToolBox

你构建的 Web 应用在用户量少时运行良好,但随着流量增长,你会遇到页面加载缓慢、后台任务重复执行以及 API 被滥用客户端频繁请求的问题。这些都是典型的扩展性问题,而 Redis 可以优雅地解决它们。Redis 是一个内存数据存储,擅长三项核心任务:缓存、队列和限流。在本文中,我们将通过实际示例和最佳实践来探讨每个使用场景。

为什么在这些场景中使用 Redis?

Redis 将数据存储在内存中,因此速度极快——通常响应时间在亚毫秒级。它支持丰富的数据结构,如字符串、列表、集合、有序集合和哈希,这些结构天然适用于缓存、队列和限流。它还提供原子操作、发布/订阅和持久化选项,使其成为后端开发人员的多功能工具。

1. 使用 Redis 进行缓存

缓存是 Redis 最常见的用例。通过将频繁访问的数据存储在 Redis 中,可以减少数据库负载并加快响应速度。

缓存的工作原理

当请求到达时,检查数据是否存在于 Redis 中。如果存在(缓存命中),直接返回。如果不存在(缓存未命中),从数据库获取数据,将其存储到 Redis 并设置过期时间,然后返回。这种模式称为缓存旁路(cache-aside)。

// Node.js 示例,使用 ioredis
const Redis = require('ioredis');
const redis = new Redis();

async function getUser(userId) {
  const cacheKey = `user:${userId}`;
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // 1 小时 TTL
  return user;
}

缓存的最佳实践

缓存失效策略

失效处理很困难。常见的策略包括:

2. 使用 Redis 实现队列

队列将耗时的任务与请求-响应周期解耦。例如,发送电子邮件、处理图像或生成报告可以异步完成。

实现简单队列

Redis 列表非常适合队列。使用 LPUSH 将任务添加到左侧,使用 BRPOP 阻塞并从右侧弹出(FIFO)。

// 生产者:添加任务
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Welcome' }));

// 消费者:处理任务(在工作进程中)
while (true) {
  const job = await redis.brpop('email_queue', 0); // 0 = 无限阻塞
  const { to, subject } = JSON.parse(job[1]);
  await sendEmail(to, subject);
}

使用 BRPOPLPUSH 实现可靠队列

为了避免工作进程崩溃时丢失任务,使用 BRPOPLPUSH 原子地将任务移动到处理列表。成功处理后,将其从处理列表中移除。如果工作进程死亡,另一个工作进程可以从处理列表中恢复任务。

const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
  // 处理任务
  await redis.lrem('processing_queue', 1, job);
} catch (err) {
  // 处理错误,可能重新入队
}

专用队列库

在生产环境中,考虑使用 Bull(Node.js)、RQ(Python)或 Sidekiq(Ruby)等库。它们提供重试、调度和监控功能。

3. 使用 Redis 进行限流

限流保护你的 API 免受滥用并确保公平使用。Redis 的原子操作使其非常适合计数请求。

固定窗口限流

在每个时间窗口内为每个用户递增计数器。如果计数超过限制,则拒绝请求。

async function isAllowed(userId, limit = 100, windowSec = 60) {
  const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSec)}`;
  const count = await redis.incr(key);
  if (count === 1) await redis.expire(key, windowSec);
  return count