Redis Set 去重统计不准怎么办:从 SADD 返回值到 Key 粒度一步步排查
来源:17golang原创
时间:2026-06-15 12:58:44 194浏览 收藏
一个活动报名页显示“已报名 1250 人”,运营同学却说后台订单只有 1178 条。第一眼看像是缓存延迟,但我们不能直接下结论。Redis Set 做去重统计时,数量不准通常藏在三个地方:返回值理解错、Key 粒度混在一起、过期时间没处理好。
这篇文章我们从一个很小的现场开始,一步一步把问题拆开,看清楚 `SADD`、`SCARD` 和 Key 设计到底应该怎么配合。
摘要
Redis Set 适合保存“不重复成员”,常用于报名用户、接口请求编号、当天访问用户等场景。要让统计稳定,写入时要看 `SADD` 的新增结果,读取时用 `SCARD` 回查数量,同时按业务维度设计 Key,并给临时统计设置合理过期时间。
适合人群
适合正在用 Redis 做去重、UV、活动参与人数、幂等请求记录的后端同学。文章示例使用 Redis 命令和少量伪代码,不依赖特定语言框架。
目录
- 问题现场:为什么显示数量比订单多
- 先验证 SADD:它返回的不是总人数
- 继续定位:Key 粒度混乱会把数据串起来
- 修复方案:写入、统计、过期分开处理
- 常见坑和最终检查
问题现场:为什么显示数量比订单多
我们先复现一个简单场景:活动 ID 是 9001,同一个用户可能重复点击报名按钮。业务希望“一个用户只算一次”,于是代码把用户 ID 写进 Redis Set。
SADD activity:join:9001 10001 SADD activity:join:9001 10002 SADD activity:join:9001 10001 SCARD activity:join:9001
按 Set 的语义,用户 `10001` 第二次写入不应该增加人数。如果页面仍然变多,我们就要先看写入逻辑是不是把每次点击都当成新增了。

先验证 SADD:它返回的不是总人数
第一轮猜测是:代码是不是误解了 `SADD` 的返回值?`SADD` 返回的是本次真正新增的成员数量,不是 Set 里的总成员数。
127.0.0.1:6379> SADD activity:join:9001 10001 (integer) 1 127.0.0.1:6379> SADD activity:join:9001 10001 (integer) 0 127.0.0.1:6379> SCARD activity:join:9001 (integer) 1
这段结果说明了两件事:第一次写入返回 `1`,代表新增成功;第二次写入返回 `0`,代表成员已经存在。要展示当前总人数,应该用 `SCARD`,不能把每一次请求都累加到一个普通数字字段里。
如果业务同时维护了一个 `join_count`,就很容易出现偏差:重复点击让计数器增加了,但 Set 实际人数没变。此时应以 Set 的数量为准,或者只在 `SADD` 返回 `1` 时再更新其他统计。
继续定位:Key 粒度混乱会把数据串起来
如果 `SADD` 返回值没用错,我们继续看 Key。很多统计不准不是命令错了,而是 Key 粒度太粗。比如把所有活动都写到同一个 Key:
SADD activity:join 9001:10001 SADD activity:join 9002:10001
这能去重,但统计某一个活动时就不方便,还容易在读取时切错维度。更清晰的做法是把活动 ID 放到 Key 里,把用户 ID 当成员。
SADD activity:join:9001 10001 SADD activity:join:9002 10001 SCARD activity:join:9001
这一步我们能定位到第二个常见原因:成员内容和 Key 维度混在一起,导致后续统计、清理、对账都变复杂。Key 负责业务范围,成员负责唯一对象,职责分开后排查会轻很多。

修复方案:写入、统计、过期分开处理
现在我们把方案收拢。写入时只关心成员是否新增,统计时用 `SCARD`,临时活动结束后让 Key 自动过期。
SADD activity:join:9001 10001 EXPIRE activity:join:9001 604800 SCARD activity:join:9001
在业务代码里,可以把逻辑拆成三个动作:
added = SADD activity:join:{activityId} {userId}
if added == 1:
写入报名记录或发送首次报名消息
count = SCARD activity:join:{activityId}
注意:如果活动 Key 已经存在,反复设置过期时间可能会延长活动窗口。更稳的写法是创建 Key 后设置一次过期,或者按活动结束时间计算剩余秒数,避免每天访问都把 Key 往后顺延。

常见坑和最终检查
第一个坑是把 `SADD` 的返回值当总人数。它只能告诉你本次新增了几个成员,总人数要用 `SCARD`。
第二个坑是成员值不稳定。比如有的地方写用户 ID,有的地方写手机号,有的地方写 `user:10001`,这些在 Redis 看来都是不同成员,去重自然会失效。成员格式要统一。
第三个坑是大 Set 直接拉全量成员。统计人数用 `SCARD` 即可,不要为了数数量去读出所有成员。需要抽样排查时再用少量扫描,不要把全部成员一次性取回应用层。
最终检查可以按这四步走:
TYPE activity:join:9001 SCARD activity:join:9001 SISMEMBER activity:join:9001 10001 TTL activity:join:9001
如果类型是 `set`,数量和业务记录能对上,关键用户存在性正确,生命周期也符合活动结束时间,这个去重统计就基本稳住了。
总结
Redis Set 去重统计不准时,不要只盯着“Redis 有没有问题”。我们按现场一步步看,真正容易出错的是业务使用方式:把 `SADD` 返回值当总数、Key 粒度不清、成员格式不统一、过期策略顺延。
推荐落地规则很简单:Key 表示业务范围,成员表示唯一对象;新增看 `SADD`,总数看 `SCARD`,生命周期交给明确的过期策略。这样再遇到重复点击、重复请求或活动统计,就能少很多对账麻烦。
-
148 收藏
-
406 收藏
-
286 收藏
-
235 收藏
-
117 收藏
-
368 收藏
-
118 收藏
-
464 收藏
-
180 收藏
-
数据库 · Redis | 2天前 | Redis · 消息队列 · Stream · 消费组 · redis 消息队列 Redis Stream 消费组 XREADGROUP XACK XPENDING XAUTOCLAIM187 收藏
-
139 收藏
-
116 收藏
-
111 收藏
-
438 收藏
-
146 收藏
-
476 收藏
-
216 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习