本文目录导读:

- 使用Redis Set集合(最常用)
- 使用有序集合(Sorted Set)去重
- 使用HyperLogLog(基数统计去重)
- 结合Bloom Filter(布隆过滤器)
- 实际去重示例:防止重复提交
- 场景选型建议
- 注意事项
在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 | 自动清理 |
注意事项
-
原子性问题:使用事务或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); -
内存控制:使用
MEMORY USAGE命令监控集合大小 -
序列化处理:复杂对象需序列化后再存储
-
键命名规范:使用分隔的命名空间