Laravel去重用RedisSET吗

wen PHP项目 15

本文目录导读:

Laravel去重用RedisSET吗

  1. 为什么 Redis SET 适合去重?
  2. 典型场景:防止重复消费消息
  3. 场景:每日去重(带有效期)
  4. 大规模去重(亿级)的优化建议
  5. 常见陷阱 & 最佳实践

在 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。
  • ✅ 应使用 SISMEMBERSADD 返回值。

✅ 使用 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 的返回值判断是否首次插入。

如果你能具体描述一下你的业务场景(比如大概的并发量、数据量、去重依据是什么),我可以给你更精准的建议。

抱歉,评论功能暂时关闭!