本文目录导读:

在 Laravel 中实现去重,使用 Redis 的 SET 是非常常见且高效的做法,不过具体用不用、怎么用,取决于你的业务场景和去重的粒度。
下面我分几种情况帮你分析,并给出代码示例。
为什么 Redis SET 适合去重?
- 天然无序、元素唯一:
SADD添加重复元素会返回 0,利用这一点可以判断是否已存在。 - 内存高效:对于几百万甚至上千万的 ID 或字符串,Redis SET 性能依然很好。
- 原子操作:
SADD+ 判断是否新增一步到位,避免竞态条件。
典型场景:防止重复消费消息
比如队列消费者收到消息后,先判断该消息是否处理过。
use Illuminate\Support\Facades\Redis;
public function handle(array $data)
{
$uniqueKey = $data['order_id']; // 或 md5(序列化)
// 尝试加入 SET,如果已存在则跳过
$added = Redis::sadd('processed_orders', $uniqueKey);
if (! $added) {
Log::info('已处理过,跳过', $orderId);
return;
}
// 业务逻辑...
}
注意:SADD 返回 1 表示成功添加,返回 0 表示已存在。
场景:每日去重(带有效期)
去重通常需要设置过期时间,避免 SET 无限增长,Redis SET 本身不支持单个元素过期,但可以对整个 Key 设置 TTL。
$date = now()->format('Y-m-d');
$key = "unique_visitors:{$date}";
// 添加用户 ID
$isNew = Redis::sadd($key, $userId);
// 如果是当天首次添加,设置过期时间(48 小时)
if ($isNew) {
Redis::expire($key, 48 * 3600); // 48小时自动清理
}
大规模去重(亿级)的优化建议
如果单日数据量极大(比如上亿),Redis SET 的内存压力会很大,此时可以考虑:
| 方案 | 说明 | 推荐场景 |
|---|---|---|
| Redis HyperLogLog | 只统计唯一数,不能取出具体元素,内存极小 | 统计 UV、不关心具体谁访问过 |
| Redis Bloom Filter | 概率性去重,有误判率,但内存固定 | 大流量过滤、消息去重可接受极低误判 |
| 分片 SET | 将 ID 按哈希分散到多个 SET,降低单 Key 大小 | 百亿级 ID,内存仍可承受 |
示例:使用 HyperLogLog 统计唯一数量(不关心具体元素)
// 添加
Redis::pfadd('uv_page_1', $userId);
// 获取近似唯一数
$count = Redis::pfcount('uv_page_1');
常见陷阱 & 最佳实践
❌ 不要用 SMEMBERS 检查是否存在
SMEMBERS会返回所有元素,O(n) 操作,数据量大时会阻塞 Redis。- ✅ 应使用
SISMEMBER或SADD返回值。
✅ 使用 Pipeline 批量去重
批量添加多条数据时,用 Pipeline 减少网络开销:
Redis::pipeline(function ($pipe) use ($items) {
foreach ($items as $item) {
$pipe->sadd('batch_set', $item);
}
});
✅ 设置合理过期策略
- 如果业务允许,给 SET 加 TTL,避免长期占用内存。
- 如果需要长期保留,考虑将数据转存到 MySQL 或归档。
- 一般场景:直接使用 Redis
SET去重,简单高效。 - 需要统计唯一数:用
HyperLogLog。 - 海量数据、可容忍极低误判:用
Bloom Filter(Laravel 需安装predis/predis或 phpredis 扩展,并借助第三方库实现)。 - 核心思路:利用
SADD的返回值判断是否首次插入。
如果你能具体描述一下你的业务场景(比如大概的并发量、数据量、去重依据是什么),我可以给你更精准的建议。