你的标题是 “Redis 解锁:C++ 实战深度探索 Set 数据类型”,看起来像是想深入探讨 Redis 的 Set 数据类型在 C++ 中的实战用法,同时可能把“解锁”理解为“解锁分布式锁”的意思(因为 Redis 分布式锁和 SET 命令关系非常密切),或者单纯想全面掌握 Set 类型。
下面我把两个方向都给你讲清楚,并重点用 redis-plus-plus(目前最流行的 C++ Redis 客户端)来写实战代码。
1. Redis Set 数据类型核心特性与典型用法
Redis 的 Set 是无序、不重复的字符串集合,底层实现有两种:
- intset(小集合 + 全是整数时)
- hashtable(ziplist 编码已废弃,新版用 dict)
常用命令(时间复杂度几乎都是 O(1) 或 O(N) 很小):
| 命令 | 作用 | 时间复杂度 | 示例 |
|---|---|---|---|
| SADD | 添加一个或多个元素 | O(1) | SADD myset “apple” “banana” |
| SREM | 删除元素 | O(1) | SREM myset “apple” |
| SISMEMBER | 判断元素是否存在 | O(1) | SISMEMBER myset “apple” |
| SCARD | 获取集合元素个数 | O(1) | SCARD myset |
| SMEMBERS | 获取所有元素 | O(N) | SMEMBERS myset |
| SRANDMEMBER | 随机返回一个/多个元素 | O(1) / O(N) | SRANDMEMBER myset 3 |
| SPOP | 随机弹出并删除元素 | O(1) | SPOP myset |
| SUNION | 并集 | O(N) | SUNION set1 set2 |
| SINTER | 交集 | O(N) | SINTER set1 set2 |
| SDIFF | 差集 | O(N) | SDIFF set1 set2 |
典型业务场景:
- 抽奖系统(随机抽取用户)
- 标签系统(用户打标签,去重)
- 共同好友、共同关注
- 访问用户去重(UV 统计)
- 黑名单 / 白名单
- 秒杀商品库存校验(但更常用 string 或 lua)
2. C++ 中使用 redis-plus-plus 操作 Set(推荐方式)
先安装 redis-plus-plus(基于 hiredis,线程安全,支持连接池)
# 假设你用 vcpkg 或自己编译
vcpkg install redis-plus-plus
基本连接与 Set 操作示例
#include <sw/redis++/redis++.h>
#include <iostream>
#include <vector>
#include <string>
using namespace sw::redis;
int main() {
try {
// 单节点连接(推荐使用连接池)
ConnectionOptions opts;
opts.host = "127.0.0.1";
opts.port = 6379;
// opts.password = "yourpass"; // 如有密码
// 连接池(生产推荐)
ConnectionPoolOptions pool_opts;
pool_opts.size = 16; // 连接数
pool_opts.wait_timeout = std::chrono::milliseconds(100);
pool_opts.connection_timeout = std::chrono::seconds(5);
auto redis = Redis(RedisURI("tcp://127.0.0.1:6379"), pool_opts);
// --------------------- Set 操作 ---------------------
std::string key = "myset:fruits";
// 清空测试数据(慎用生产环境)
redis.del(key);
// SADD 添加元素(支持批量)
long long cnt = redis.sadd(key, {"apple", "banana", "orange", "apple"}); // apple 只加一次
std::cout << "Added " << cnt << " unique elements\n"; // 输出 3
// SISMEMBER 判断存在
if (redis.sismember(key, "banana")) {
std::cout << "banana exists\n";
}
// SCARD 元素个数
std::cout << "Size: " << *redis.scard(key) << "\n"; // 3
// SMEMBERS 获取全部(小心大集合)
std::vector<std::string> members;
redis.smembers(key, std::back_inserter(members));
std::cout << "All members: ";
for (const auto& m : members) std::cout << m << " ";
std::cout << "\n";
// SRANDMEMBER 随机取(不删除)
auto rand_one = redis.srandmember(key);
if (rand_one) std::cout << "Random: " << *rand_one << "\n";
// SPOP 随机弹出并删除
auto popped = redis.spop(key);
if (popped) std::cout << "Popped: " << *popped << "\n";
// SREM 删除
redis.srem(key, "orange");
// 集合运算示例
redis.sadd("set:a", {"1", "2", "3", "4"});
redis.sadd("set:b", {"3", "4", "5", "6"});
std::vector<std::string> inter;
redis.sinter({"set:a", "set:b"}, std::back_inserter(inter));
std::cout << "Intersection: ";
for (auto& v : inter) std::cout << v << " "; // 3 4
std::cout << "\n";
} catch (const Error& e) {
std::cerr << "Redis error: " << e.what() << std::endl;
return 1;
}
return 0;
}
3. Redis 分布式锁才是“解锁”最经典的场景(SET + NX + PX)
Redis 官方推荐的单节点分布式锁就是用 SET 命令(不是 Set 类型,而是 String 类型):
SET lock:order:12345 "uuid-xxx-clientA" NX PX 30000
- NX:不存在才设置(加锁)
- PX:毫秒级过期(防死锁)
- value 必须唯一(通常是 UUID + 客户端标识)
C++ 实现简单版分布式锁(非 Redlock)
#include <sw/redis++/redis++.h>
#include <string>
#include <random>
#include <chrono>
#include <thread>
class SimpleRedisLock {
private:
Redis& _redis;
std::string _lock_key;
std::string _lock_value;
std::chrono::milliseconds _expire_ms;
public:
SimpleRedisLock(Redis& r, const std::string& key, int expire_seconds = 30)
: _redis(r), _lock_key(key),
_expire_ms(std::chrono::seconds(expire_seconds)) {}
bool try_lock() {
// 生成唯一 value(简单示例,可用 uuid 库)
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(10000000, 99999999);
_lock_value = "client-" + std::to_string(dis(gen));
auto reply = _redis.set(_lock_key, _lock_value,
std::chrono::duration_cast<std::chrono::milliseconds>(_expire_ms),
UpdateType::ONLY_IF_NOT_EXIST); // 等价 NX
return reply && *reply; // true 表示加锁成功
}
bool unlock() {
// 安全解锁:用 Lua 脚本保证原子性
static const char* lua_script = R"(
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
)";
auto res = _redis.eval<long long>(lua_script, {_lock_key}, {_lock_value});
return res && *res == 1;
}
~SimpleRedisLock() {
unlock(); // 析构自动尝试释放(视业务可去掉)
}
};
// 使用示例
void process_order(Redis& redis) {
SimpleRedisLock lock(redis, "lock:order:12345", 30);
if (!lock.try_lock()) {
std::cout << "获取锁失败\n";
return;
}
std::cout << "获得锁,开始处理订单...\n";
std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟业务
lock.unlock();
std::cout << "订单处理完成,已释放锁\n";
}
更安全的做法:
- 用 redis-plus-plus 自带的 Redlock(多节点)
- 或引入 Redisson 的思想,用更完善的续期机制(watchdog)
// redis-plus-plus 内置 Redlock 用法(需多个 master 节点)
auto redlock = Redlock({node1, node2, node3, node4, node5});
if (redlock.lock("my-resource", std::chrono::seconds(30))) {
// 业务
redlock.unlock();
}
总结对比
| 需求 | 推荐数据类型 | 核心命令 | C++ 库支持度 |
|---|---|---|---|
| 去重、标签、抽奖 | Set | SADD / SPOP / SRANDMEMBER | 非常好 |
| 分布式锁(单节点) | String | SET NX PX | 优秀 |
| 高可用分布式锁 | String | Redlock 算法 | redis++ 支持 |
| 排行榜 | Sorted Set | ZADD / ZRANGE | – |
如果你真正想问的是 Set 类型 的深度实战,还是 分布式锁的 C++ 实现,可以再补充具体场景,我可以继续给你更细致的代码或优化方案(Lua 续期、Redlock 完整实现、 fencing token 等)。