关注功能高并发怎么扛?Redis ZSet + Lua + MQ 异步落库一整套

简介: 关注功能高并发怎么设计?本文拆解 Redis ZSet 存关系、Lua 保原子性、MQ 异步落库、消费端令牌桶削峰四环,抗住瞬间洪峰。

大家好,我是晚安code。

关注功能高并发有多难扛?(2026 年 8 月实测,适用于 Redis 6.0+ / RocketMQ 4.9+)这次我把整套「Redis 缓冲 + ZSet 存关系 + Lua 保原子 + MQ 异步落库 + 令牌桶削峰」的方案完整写出来,你照着这套思路做高并发设计,能少走我踩过的弯路。

一、直接写库,关注接口为什么扛不住

先说结论:关注功能的高并发瓶颈,从来不在功能本身,而在你把高频写操作直接怼给了数据库。

关注这个动作拆开看,一次请求至少要干三件事:往关系表插入一条记录、给对方的粉丝数 +1、给自己维护的关注列表加一个成员。很多项目第一版就是这么干的——一个事务里同步写完这三步。用户量一上来,问题全暴露了:

  1. 关系表频繁 insert,索引不断膨胀,写入越来越慢;
  2. 高并发下「插入 + 计数」不是原子的,粉丝数可能对不上账;
  3. 数据库 IO 是有上限的,瞬时洪峰一来直接打穿。

我在见到的经典事故,就是「关注接口双写 Redis + DB」:请求来了先写 MySQL 再写 Redis,并发一上来,两边数据就开始打架——有人取关成功了,Redis 里关系还在;有人关注重复提交,计数翻倍。

说白了,关系数据本身不难存,难的是让数据库别被高频写压垮。所以整套方案的核心思路只有一个:把「高频写」前置到 Redis,用 MQ 把落库这件事切成异步,数据库只吃它扛得住的量。

二、Redis 缓冲层:把关系放进 ZSet

Redis ZSet(Sorted Set,有序集合):Redis 的一种数据结构,每个成员带一个 score 分值,成员按 score 自动排序。你可以理解为「带时间戳的收藏夹」——既能去重判断存没存过,又能按时间倒序翻页。

关注关系选 ZSet 而不是 Set,就一个原因:关注列表天然需要按时间排序。「最近关注了谁」是社交产品里最常见的查询,Set 只能存成员、排不了序,ZSet 用关注时间当 score,ZRANGE 一查就是按时间排好的列表。

我这里的存储模型是双向两份:

following:{用户ID}    member=被关注人ID,score=关注时间戳
follower:{用户ID}     member=粉丝ID,score=关注时间戳

关注时间做 score,取出来天然就是一条时间线,就像朋友圈按发布时间排。注意看这张概念图里的时间圆点——score 就是那条看不见的排序轨道:

ZSet 里 score 是排序轨道,时间戳当 score 就能按时间查

这样设计的好处是:

  • 插入高效ZADD 复杂度 O(log N),时间戳做 score,天然有序;
  • 查询高效ZRANGE 分页查关注/粉丝列表、ZSCORE 判断是否已关注,都是毫秒级;
  • 取关干净ZREM 一条命令删掉关系,两个 key 一起维护。

关注功能高并发架构图:请求进入 Redis 缓冲层,Lua 原子更新 ZSet,异步发送 MQ 由消费端令牌桶限流后落库

上面这张图就是完整链路:关注接口收到请求 → Lua 脚本原子更新 Redis → 发 MQ → 消费者令牌桶限流 → 异步落库。下面逐个环节拆。

三、Lua 脚本:高并发下 Redis 更新也要原子

Lua 脚本:Redis 内置的脚本语言,脚本会整段在 Redis 服务端原子执行——执行期间不会被其他命令插入。你可以理解为「Redis 内的一次性原子事务」。

很多人以为写了 Redis 就万事大吉,其实高并发下 Redis 的「检查 + 更新」也必须用 Lua 串成一步。看这段伪代码,是不是很眼熟:

-- 关注:判断 + 插入 + 计数,三条命令
local exists = redis.call('zscore', KEYS[1], ARGV[1])
if exists then return 1 end  -- 已关注
redis.call('zadd', KEYS[1], ARGV[2], ARGV[1])
redis.call('hincrby', 'follow:count', ARGV[1], 1)
return 0

如果这三步分开执行,并发下就会出问题:两个请求同时判断「没关注」,然后各插一次、计数加两次。我在生产环境就踩过这个坑——上线当天关注数虚高,排查半天发现就是计数器被并发叠加了。

用 Lua 把「判断是否已关注 → 插入 ZSet → 更新计数」合并成一步,Redis 单线程保证整段脚本不会被打断,原子性天然解决。关注和取关各写一个脚本,eval 一次调用就完成,性能比拆分命令还好。

这个「合并成一步」就是 Lua 原子性的精髓——三条散落的命令,被串成一把钥匙上的三个齿,要么一起动,要么都不动:

Lua 脚本 = 把「判断、更新、计数」锁进一把钥匙,原子完成

四、发 MQ 异步落库:打 Tag 精确消费

MQ(Message Queue,消息队列):一个中转消息的中间件,生产者发消息、消费者异步取消息,两边解耦。你可以理解为「业务之间递纸条的收发室」——不是当面交接,而是放进信箱异步处理。

Redis 只是缓冲层,不是持久层。关系最终要落到数据库,但绝不能同步落。正确做法是:Redis 更新成功后,立即发一条消息到 MQ,接口直接返回「关注成功」,剩下的落库交给消费者慢慢做。

这里有个细节值得展开——发消息要打 Tag。以 RocketMQ 为例,同一个 Topic 下按业务场景打不同标签:

// 生产端:关注/取关打不同 Tag
Message msg = new Message(
    "FOLLOW_TOPIC",          // Topic
    isFollow ? "FOLLOW" : "UNFOLLOW",  // Tag 标签
    userId + ":" + targetId   // 业务数据
);
producer.send(msg);

消费者订阅时按 Tag 过滤,只处理自己关心的消息:

consumer.subscribe("FOLLOW_TOPIC", MessageSelector.byTag("FOLLOW || UNFOLLOW"));

为什么要打 Tag?三个理由:消费端能精确过滤,不用消费全部消息再业务层判断;方便分场景限流,关注和取关可以配置不同的处理速率;排查问题时定位快,一看 Tag 就知道这条消息是什么业务。

消息队列削峰的核心价值就在这:接口侧把瞬时洪峰甩给 MQ,MQ 像一个水库,把洪峰的水蓄住,再按下游能承受的流速放出去。这就是常说的削峰填谷。

你看这张图就明白了——上面是劈头盖脸的暴雨(洪峰),下面是稳稳的一条小溪(落库速率),中间的 MQ 就是那个水库闸门:

MQ 是水库,接口是暴雨,数据库是小溪——削峰填谷全靠这个闸门

可能有人会问:Redis 更新成功了,MQ 消息发了,万一落库失败,数据不就对不上了?

放心,这正是异步方案必须配的兜底。消费者落库用「主键幂等」——follow 表用 (user_id, target_id) 建唯一索引,重复消息 insert 时冲突直接跳过;再加一层死信队列,重试几次还失败的进 DLQ 人工排查。Redis 作为读缓存,可以接受偶尔重建,数据库这一层最终一致就行。

五、消费端令牌桶:数据库只吃它吃得下的量

令牌桶(Token Bucket):一种限流算法,系统按固定速率往桶里放令牌,请求要先拿到令牌才能处理,桶满了就丢弃新令牌。你可以理解为「景区按固定频率放人进门的闸机」——人再多,进场的速率是定死的。

到消费端这一步,MQ 里已经堆了一大堆落库消息。如果消费者一股脑全拉出来写库,等于把洪峰从接口侧平移到了数据库侧,前面白干了。所以消费端必须限速,这就是令牌桶上场的地方

我用的是 Redis 实现的分布式令牌桶:消费者每处理一条消息前,先 eval 一个 Lua 脚本拿令牌,拿到才继续,拿不到就稍等重试。核心脚本长这样:

local bucket = 'token:' .. KEYS[1]   -- 桶名
local tokens = tonumber(redis.call('get', bucket) or '0')
local capacity = tonumber(ARGV[1])   -- 桶容量
if tokens >= 1 then
    redis.call('decr', bucket)
    return 1   -- 拿到令牌
end
return 0       -- 桶空,稍后再试

配合一个定时任务按固定速率补令牌,消费速率就被稳稳控住了。数据库的写入 TPS 从「不可控的洪峰」变成「恒定的 500」,压力直接可量化。

限流方案 是否支持突发 实现复杂度 适用场景
固定窗口 否,瞬间可能双倍 粗粒度限流
滑动窗口 部分 要求平滑
令牌桶 是,桶存突发量 削峰填谷、消费端限速
漏桶 否,恒定速率 强制匀速

对比下来,消费端削峰选令牌桶最合适:平时流量平稳时桶是满的、不排队;活动洪峰时桶蓄不住的部分直接丢弃或排队,数据库永远只看到固定速率。我实际调参时,桶容量按「数据库能承受的最大 TPS × 3」设置,既给了突发余量,又不至于把库打爆。

FAQ

可能有人会问:为什么关注功能不能先写数据库再删缓存?

顺序反过来会带来缓存与数据库的短暂不一致:删缓存和写库之间一旦有并发读,读到的就是旧缓存。异步落库 + Redis 兜底的方案里,Redis 是读的主来源,写库是后台补账,只要幂等键兜底,最终一致就够用。

六、总结:这套方案的核心思路

回顾一下整套链路:关注功能高并发设计,核心就一句话——Redis 当缓冲层挡住高频写,ZSet 存关系保排序,Lua 脚本把判断和更新合成原子一步,MQ 异步承接落库并打 Tag 分场景,消费端令牌桶把数据库压在恒定速率。五个环节各司其职,缺一个都容易在洪峰时出岔子。

以我踩过坑的经验,新手最容易漏的是第二环——以为 Redis 天然原子,结果计数器被并发打爆。原子性不会因为数据进了 Redis 就自动有,该用 Lua 还得用

关注接口高并发设计自查清单:

  1. Redis 缓冲:关系写入全部前置 Redis;
  2. ZSet 双向存储:following / follower 各一份;
  3. Lua 原子:判断 + 插入 + 计数合并一个脚本;
  4. MQ 异步:发消息打 Tag,接口立即返回;
  5. 令牌桶限流:消费端限速落库,保护数据库。

这套方案同样适用于点赞、收藏、订阅这类「高频写关系」功能——把场景换一下,思路是通的。这套关注功能高并发方案在你那边落地时,建议先把量级估清楚:预估并发 1 万以内,简化到「Redis + 定时任务」也能扛;真正到了活动级洪峰,再上 MQ + 令牌桶不迟。如果想深入,推荐看 Redis 官方文档的 Lua 脚本章节,和 RocketMQ 的消息过滤文档(搜:Redis eval 文档、RocketMQ Tag 过滤)。


我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你在做关注、点赞这类高并发功能时,遇到过哪些「数据对不上账」的坑?

目录
相关文章
|
4月前
|
消息中间件 运维 监控
【消息队列MQ】消息积压:原因、紧急处理方案
本文系统解析消息积压问题,涵盖定义影响、全链路根因(90%源于消费者侧)、紧急SOP(止损→定位→消化→恢复)、长效预防、主流MQ(Kafka/RocketMQ/RabbitMQ)差异化处理、常见误区及复盘闭环七大维度,构建覆盖故障应急到架构治理的完整知识体系。
|
19天前
|
运维 Java 调度
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
单机 @Scheduled 扛不住分布式定时任务?拆解 XXL-JOB 调度中心与执行器架构、任务分片、失败重试机制,附 30 分钟接入示例。
151 0
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
|
21天前
|
消息中间件 缓存 NoSQL
RocketMQ 延迟消息实战:延迟双删策略解决 Redis 缓存一致性
RocketMQ 延迟消息怎么用?本文从本地搭建开始,用订单超时案例讲透延迟消息与延迟双删,解决 Redis 缓存一致性难题。
104 2
|
20天前
|
消息中间件 缓存 NoSQL
RocketMQ 顺序消费实战:批量接口加 Redis Pipeline同步数据
用户数据同步总乱序、缓存写入又慢?本文用 RocketMQ 顺序消费、批量接口、Redis Pipeline 三件套讲清同步链路。
93 1
RocketMQ 顺序消费实战:批量接口加 Redis Pipeline同步数据
|
20天前
|
Java 数据库连接 数据库
R2DBC vs JDBC:Spring Boot 响应式项目该怎么选
Spring Boot 里纠结 R2DBC vs JDBC?从阻塞差异讲清 WebFlux 组合坑,并给出物联网高并发与普通 CRUD 的选型结论
|
20天前
|
人工智能 自然语言处理 API
阿里云千问Qwen3.5-Omni:原生全模态大模型核心功能全解析
在通用人工智能向全模态感知演进的关键阶段,阿里云千问推出的Qwen3.5-Omni,凭借原生端到端全模态架构、顶尖的音视频理解能力与丰富的交互特性,成为全模态大模型领域的标杆产品。该模型彻底打破文本、图像、音频、视频的模态壁垒,实现“看、听、说、读、思”一体化感知,在215项音频与音视频任务中斩获SOTA成绩,全面对标并部分超越国际顶尖模型,同时提供Plus、Flash、Light三种尺寸版本,适配从高性能推理到低延迟实时交互的全场景需求。本文将从核心架构、基础能力、进阶功能、API调用、应用场景五大维度,全面解析Qwen3.5-Omni的功能特性,帮助开发者与企业用户快速掌握其核心价值与落地
249 2
|
21天前
|
自然语言处理 前端开发 数据挖掘
最新版通义千问(Qwen3.8-Max-Preview)功能介绍
通义千问Qwen3.8-Max-Preview是通义千问团队推出的旗舰级大模型预览版,以2.4万亿参数规模、第三代MoE混合专家架构为核心,实现了原生多模态处理、百万级超长上下文、双推理模式、全栈代码工程、多智能体协作等突破性能力,在复杂推理、专业开发、长文档处理、多模态融合等场景实现跨越式升级,成为面向企业级与开发者的高性能基座模型。本文将从核心架构、基础能力、专业场景功能、技术优势、接入配置与实战应用等维度,全面解析Qwen3.8-Max-Preview的完整功能体系,帮助用户快速掌握其核心价值与使用方法。
239 1
|
21天前
|
jenkins 测试技术 Shell
双非院校想进大厂做测试?这条路我帮你走通了
本文为双非二本机械电子专业转行大厂测试开发的硬核实战指南:从培训班韭菜到字节测开,4年踩坑经验全公开。聚焦接口自动化框架搭建、CI/CD落地、高含金量项目“造”法及面试话术,强调工程能力而非学历标签,助你用代码实力杀进大厂。
|
5月前
|
人工智能 自然语言处理 安全
OpenClaw从入门到精通:阿里云/本地保姆级部署步骤+必装Top10 Skills +免费模型配置一站式指南
2026年,OpenClaw(Clawdbot)已经成为AI智能体领域最主流的开源框架,凭借可本地部署、可云端托管、可技能扩展、可系统执行的超强能力,迅速成为个人效率、办公自动化、信息搜集、知识管理的首选平台。但很多用户在安装完OpenClaw后,往往不知道下一步该装哪些技能,也不清楚哪些技能真正实用、安全、高效。
1369 0
|
1月前
|
算法 Java Nacos
Sentinel 流量治理实战:熔断降级限流 + Nacos 持久化
Sentinel 流量治理如何落地?本文从服务雪崩讲起,带你掌握熔断、降级、限流策略,配合 Nacos 实现规则持久化
272 0

热门文章

最新文章