PHP项目Redis集合如何去重处理

wen PHP项目 27

本文目录导读:

PHP项目Redis集合如何去重处理

  1. 使用Redis Set集合(最常用)
  2. 使用有序集合(Sorted Set)去重
  3. 使用HyperLogLog(基数统计去重)
  4. 结合Bloom Filter(布隆过滤器)
  5. 实际去重示例:防止重复提交
  6. 场景选型建议
  7. 注意事项

在PHP项目中使用Redis进行集合去重,主要有以下几种方案:

使用Redis Set集合(最常用)

Set天然具备去重特性,同一元素只会存储一次。

添加元素(自动去重)

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 批量添加
$redis->sAdd('user:online', 'user1', 'user2', 'user1', 'user3');
// 结果:{'user1', 'user2', 'user3'}
// 单个添加
$redis->sAdd('user:online', 'user1');

判断元素是否存在

$exists = $redis->sIsMember('user:online', 'user1'); // true/false

获取所有元素

$users = $redis->sMembers('user:online'); // 返回数组
$count = $redis->sCard('user:online'); // 获取集合大小

使用有序集合(Sorted Set)去重

需要排序或带权重的场景使用。

// 添加元素,分数可设为时间戳
$redis->zAdd('user:score', 85, 'user1');
$redis->zAdd('user:score', 90, 'user2');
$redis->zAdd('user:score', 85, 'user1'); // 不会重复,只更新分数
// 获取所有元素
$users = $redis->zRange('user:score', 0, -1); // 按分数排序

使用HyperLogLog(基数统计去重)

适合大数据量、允许误差的去重统计。

// 添加元素
$redis->pfAdd('page:uv:20240101', ['user1', 'user2', 'user1', 'user3']);
// 统计去重后的数量(有误差,约0.81%)
$count = $redis->pfCount('page:uv:20240101'); // 返回3

结合Bloom Filter(布隆过滤器)

适合超大规模数据判断元素是否存在。

// 使用RedisBloom模块
$redis->rawCommand('BF.ADD', 'url:filter', 'http://example.com/1');
$redis->rawCommand('BF.ADD', 'url:filter', 'http://example.com/2');
// 判断是否存在
$exists = $redis->rawCommand('BF.EXISTS', 'url:filter', 'http://example.com/1');

实际去重示例:防止重复提交

class RedisDeduplication {
    private $redis;
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    /**
     * 检查并记录去重
     * @param string $key 去重键
     * @param string $value 去重值
     * @param int $expire 过期时间(秒)
     * @return bool 是否为新元素
     */
    public function checkAndSet($key, $value, $expire = 3600) {
        // 使用Set的SISMEMBER检查是否存在
        $isNew = !$this->redis->sIsMember($key, $value);
        if ($isNew) {
            // 使用事务确保原子性
            $this->redis->multi();
            $this->redis->sAdd($key, $value);
            $this->redis->expire($key, $expire);
            $this->redis->exec();
        }
        return $isNew;
    }
    /**
     * 批量去重检查
     */
    public function batchCheck($key, array $values) {
        $exists = [];
        foreach ($values as $value) {
            $exists[$value] = $this->redis->sIsMember($key, $value);
        }
        return $exists;
    }
    /**
     * 清理过期数据
     */
    public function cleanExpired($key) {
        $this->redis->del($key);
    }
}
// 使用示例
$dedup = new RedisDeduplication();
// 防止用户重复点赞
$userId = 1001;
$postId = 2001;
$dedupKey = "like:{$postId}";
if ($dedup->checkAndSet($dedupKey, $userId, 86400)) {
    echo "点赞成功";
    // 更新点赞数等业务逻辑
} else {
    echo "您已经点过赞了";
}

场景选型建议

场景 推荐方案 原因
小规模数据去重 Redis Set 简单、功能完整
需要排序的去重 ZSet 带分数排序
大规模去重(允许误差) HyperLogLog 内存占用极小
超大规模去重 Bloom Filter 空间效率极高
需要过期时间的去重 Set + expire 自动清理

注意事项

  1. 原子性问题:使用事务或Lua脚本保证原子性

    // Lua脚本实现原子操作
    $lua = "
     local exists = redis.call('SISMEMBER', KEYS[1], ARGV[1])
     if exists == 0 then
         redis.call('SADD', KEYS[1], ARGV[1])
         redis.call('EXPIRE', KEYS[1], ARGV[2])
         return 1
     end
     return 0
    ";
    $result = $redis->eval($lua, [$key, $value, $expire], 1);
  2. 内存控制:使用MEMORY USAGE命令监控集合大小

  3. 序列化处理:复杂对象需序列化后再存储

  4. 键命名规范:使用分隔的命名空间

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