Redis 解锁:C++ 实战深度探索 Set 数据类型

你的标题是 “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++ 库支持度
去重、标签、抽奖SetSADD / SPOP / SRANDMEMBER非常好
分布式锁(单节点)StringSET NX PX优秀
高可用分布式锁StringRedlock 算法redis++ 支持
排行榜Sorted SetZADD / ZRANGE

如果你真正想问的是 Set 类型 的深度实战,还是 分布式锁的 C++ 实现,可以再补充具体场景,我可以继续给你更细致的代码或优化方案(Lua 续期、Redlock 完整实现、 fencing token 等)。

文章已创建 4547

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部