使用 Redis 优化后端查询

发布: (2026年2月4日 GMT+8 14:12)
4 min read
原文: Dev.to

Source: Dev.to

原始做法(舒适版)

接口逻辑很简单:

  1. 查询数据库
  2. 按分数排序用户
  3. 返回前 10 名
SELECT * FROM users ORDER BY score DESC LIMIT 10;

在有适当索引的情况下,小规模时还能正常工作,但排行榜具有以下特点:

  • 访问频繁
  • 更新频繁
  • 竞争激烈、实时数据

每次请求都去数据库,很快就成了瓶颈。

第一次尝试:使用 Redis

Redis 看起来很完美:内存、快速、天生适合排名。
然而,在本地启动时出现错误:

Error: Address already in use
Port 6379 was already occupied.

尝试重启服务、杀掉进程都无效后,我决定把 Redis 隔离开来。

解决方案:Docker 化 Redis

docker run -d -p 6379:6379 --name redis-server redis

在容器中运行 Redis 使其:

  • 被隔离
  • 可移植
  • 干净运行
  • 易于重启

环境搞定后,我就可以继续前进了。

引入有序集合(ZSET)

Redis 有序集合会自动按分数对成员排序。

  • 成员 → 用户 ID
  • 分数 → 积分

这消除了 SQL 排序和大量数据库读取的需求。

更新用户分数

await redis.zadd("leaderboard", score, userId);

获取前 10 名

await redis.zrevrange("leaderboard", 0, 9, "WITHSCORES");

排名逻辑现在完全在内存中,延迟立刻提升。

我没预料到的隐藏瓶颈

在取到前 10 名的用户 ID 后,我还需要获取用户的其他信息(用户名、头像等):

for (let userId of topUsers) {
  await redis.hgetall(`user:${userId}`);
}

这在 Redis 中引入了 N+1 问题:

  • 1 次请求 → 获取排行榜
  • 10 次请求 → 分别获取每个用户

结果是 11 次网络往返,增加约 100 ms。

真正的解决方案:Redis 管道(Pipelining)

Redis 管道可以批量发送命令,减少往返次数。

const pipeline = redis.pipeline();
for (let userId of topUsers) {
  pipeline.hgetall(`user:${userId}`);
}
const users = await pipeline.exec();

现在只需要 一次 网络往返,消除了 N+1 带来的延迟。

结果

阶段延迟
数据库排序~200 ms
Redis(无管道)~120 ms
Redis + 管道~20 ms

整体提升约 10 倍,主要得益于削减网络调用。

我的收获

  • 基础设施问题优先——如果 Redis 没跑起来,其他一切都无从谈起。
  • 数据结构决定性能——ZSET 完全消除了重复排序。
  • N+1 问题不只出现在数据库——任何远程系统都有可能出现。
  • 网络延迟隐形却昂贵——即使是“快”的系统,调用次数过多也会变慢。
  • Docker 简化后端生活——容器化依赖可以避免操作系统层面的冲突。

最终架构

  1. 分数更新ZADD
  2. 获取前 10ZREVRANGE
  3. 批量获取用户数据 → pipeline + EXEC
  4. 返回响应

不再访问数据库,全部在内存中完成,网络调用最少,响应时间约 20 ms。

结束语

优化不是把工具往问题上砸,而是找出时间真正花在哪里。在本例中,最大收益来自:

  • 修复运行环境
  • 选对数据结构
  • 减少网络往返

解决了这三点,差距立现。

Back to Blog

相关文章

阅读更多 »