【视频点播系统】Redis-SDK 介绍及使用

Redis-SDK 介绍及使用

  • [一. Redis 介绍](#一. Redis 介绍)
  • [二. Redis 安装](#二. Redis 安装)
  • [三. redis-plus-plus SDK 介绍](#三. redis-plus-plus SDK 介绍)
  • [四. redis-plus-plus SDK 安装](#四. redis-plus-plus SDK 安装)
  • [五. redis-plus-plus 类与接口](#五. redis-plus-plus 类与接口)
    • [1. 异常、客户端配置、常规操作](#1. 异常、客户端配置、常规操作)
    • [2. 字符串、列表、哈希、无序集合、有序集合](#2. 字符串、列表、哈希、无序集合、有序集合)
    • [3. transaction、pipeline、watch](#3. transaction、pipeline、watch)
  • [六. redis-plus-plus 使用样例](#六. redis-plus-plus 使用样例)
    • [1. 目录结构](#1. 目录结构)
    • [2. 项目构建](#2. 项目构建)
    • [3. 代码实现](#3. 代码实现)
      • [3.1 字符串、列表、哈希、无序集合、有序集合操作](#3.1 字符串、列表、哈希、无序集合、有序集合操作)
      • [3.2 transaction、pipeline、watch 操作](#3.2 transaction、pipeline、watch 操作)
  • [七. redis-plus-plus 封装](#七. redis-plus-plus 封装)
    • [1. 设计与实现](#1. 设计与实现)
      • [1.1 目录结构](#1.1 目录结构)
      • [1.2 代码实现](#1.2 代码实现)
    • [2. 使用样例](#2. 使用样例)
      • [1.1 目录结构](#1.1 目录结构)
      • [1.2 项目构建](#1.2 项目构建)
      • [1.3 代码实现](#1.3 代码实现)

一. Redis 介绍

  • Redis 是一种高性能的内存键值数据库,常用于缓存、消息队列和实时数据处理等场景。它将数据主要存储在内存中,读写速度非常快,同时支持将数据持久化到磁盘,保证数据在重启后的安全性。
  • Redis 提供了丰富的数据结构,如字符串、哈希、列表、集合、有序集合等,能够满足多种业务需求。它支持主从复制、哨兵机制和集群模式,具备良好的高可用性和横向扩展能力。凭借简单易用的命令、优秀的性能和稳定性,Redis 被广泛应用于互联网系统中,如用户会话管理、排行榜、计数器和分布式锁等,是现代分布式架构中的重要基础组件。

Redis 的主要特性

  • 高性能:数据主要存储在内存中,读写操作通常在微秒级完成,非常适合高并发、低延迟的业务场景。
  • 支持多种数据结构:包括字符串、哈希、列表、集合和有序集合等,能够直接在服务端完成复杂的数据操作,减少应用层逻辑负担。
  • 支持数据持久化:提供 RDB 和 AOF 两种方式,在保证高性能的同时,能够在系统重启后恢复数据。
  • 高可用与扩展能力:支持主从复制、哨兵模式和集群模式,满足分布式系统的需求。
  • 功能丰富且易用:支持事务、发布订阅、Lua 脚本等特性,被广泛应用于缓存、消息队列、排行榜和分布式锁等场景。

二. Redis 安装

bash 复制代码
# 安装 Redis 服务端
sudo apt install redis -y
# 安装 Redis 客户端工具
sudo apt install redis-tools
# 启动 Redis 服务
service redis-server start
# 停止 Redis 服务
service redis-server stop
# 重启 Redis 服务
service redis-server restart

修改 /etc/redis/redis.conf 支持远程连接。

  • 修改 bind 127.0.0.1 为 bind 0.0.0.0
  • 修改 protected-mode yes 为 protected-mode no

由于在环境搭建中已经安装了 Redis 服务器,并且启动了,我们不需要再次安装:

但是需要手动安装 redis-tools 客户端:

使用客户端连接 Reids 服务器:

容器内运行 redis 服务器,可根据需要在配置文件 (./redis/redis.conf 该配置文件所在目录与 docker-compsoe.yml 同级) 中设置 redis 的 default 用户默认密码 (在文件1037行)

三. redis-plus-plus SDK 介绍

  • redis-plus-plus (又称 redis++) 是一个基于 C++ 的 Redis 客户端库,对 Redis 官方 C 客户端 hiredis 进行了现代 C++ 风格的封装。它遵循 C++11 及以上标准,提供类型安全、接口清晰的 API,使开发者能够更加方便地在 C++ 项目中使用 Redis。
  • redis-plus-plus 支持字符串、哈希、列表、集合、有序集合等 Redis 常用数据结构,并对事务、流水线 (Pipeline)、发布订阅、Lua 脚本等高级功能提供良好支持。同时,该库内置连接池机制,支持同步和异步操作,能够适应高并发场景。
  • 凭借良好的性能、易用性和完善的文档,redis-plus-plus 被广泛应用于高性能服务器和分布式系统中,是 C++ 访问 Redis 的主流选择之一。

四. redis-plus-plus SDK 安装

Github 地址:https://github.com/sewenew/redis-plus-plus

bash 复制代码
# 安装 hiredis 开发库(依赖)
apt install libhiredis-dev
# 克隆 redis-plus-plus 源码
git clone https://github.com/sewenew/redis-plus-plus.git
# 进入源码目录
cd redis-plus-plus
# 创建并进入构建目录
mkdir build && cd build
# 生成构建文件
cmake ..
# 编译并安装
make && sudo make install 

由于在环境搭建中已经安装了 redis-plus-plus,这里不需要再次安装了。

五. redis-plus-plus 类与接口

1. 异常、客户端配置、常规操作

异常类与接口:

cpp 复制代码
namespace sw {
    namespace redis {
        // 错误类型枚举
        enum ReplyErrorType {
            ERR, // 错误类型
            MOVED, // 迁移类型
            ASK // 询问类型
        };
        // 错误类
        class Error : public std::exception {
        public:
            explicit Error(const std::string &msg) : _msg(msg) {}
            Error(const Error &) = default;
            Error& operator=(const Error &) = default;
            Error(Error &&) = default;
            Error& operator=(Error &&) = default;
            virtual ~Error() override = default;
            // 获取错误信息
            virtual const char* what() const noexcept override {
                return _msg.data();
            }
        private:
            std::string _msg;
        };
        // 监控键被修改错误类
        class WatchError : public Error {
        public:
            explicit WatchError() : Error("Watched key has been modified") {}
            WatchError(const WatchError &) = default;
            WatchError& operator=(const WatchError &) = default;
            WatchError(WatchError &&) = default;
            WatchError& operator=(WatchError &&) = default;
            virtual ~WatchError() override = default;
        };
    }
}

客户端配置类与接口:

cpp 复制代码
namespace sw {
    namespace redis {
        // 连接选项结构体
        struct ConnectionOptions {
            std::string host; // 主机地址
            int port = 6379; // 端口号
            std::string path; // 路径
            std::string user = "default"; // 用户
            std::string password; // 密码
            int db = 0; // 数据库索引
            bool keep_alive = false; // 是否保持长连接
        }
        // 连接池选项结构体
        struct ConnectionPoolOptions {
            std::size_t size = 1; // 连接池大小
            std::chrono::milliseconds wait_timeout{0}; // 等待超时时间
            std::chrono::milliseconds connection_lifetime{0}; // 连接生命周期
            std::chrono::milliseconds connection_idle_time{0}; // 连接空闲时间
        }
        // Redis 客户端类
        class Redis {
        public:
            explicit Redis(const std::string &uri)
            explicit Redis(const ConnectionOptions &connection_opts, const ConnectionPoolOptions &pool_opts = {})
        };
    }
}

客户端常规操作接口:

cpp 复制代码
namespace sw {
    namespace redis {
        class Redis {
            // 删除所有库中的数据 
            void flushall(bool async = false);
            // 删除当前库中所有数据 
            void flushdb(bool async = false);
            // 删除指定键值对 
            long long del(const StringView &key);
            // 删除多个键值对 
            template <typename Input>
            long long del(Input first, Input last);
            // 判断指定键值对是否存在 
            long long exists(const StringView &key);
            // 判断多个键值对是否存在 
            template <typename Input>
            long long exists(Input first, Input last);
            // 为key设置一个过期时间 
            bool expire(const StringView &key, const std::chrono::seconds &timeout);
            // 移除key的过期时间,将key设置为永久有效 
            bool persist(const StringView &key);
            // 对指定数字字段进行数值增加 
            long long incrby(const StringView &key, long long increment);
            // 对指定数字字段进行数值减少 
            long long decrby(const StringView &key, long long decrement);
        }
    }
}

2. 字符串、列表、哈希、无序集合、有序集合

字符串操作接口:

cpp 复制代码
namespace sw {
    namespace redis {
        class Redis {
            // 获取string键值对 
            OptionalString get(const StringView &key);
            // 新增string键值对,且设置过期时间-毫秒,0表示不设置超时 
            // 标志位:EXIST/NOT_EXIST/ALWAYS 表示什么情况新增、更新键值对
            bool set(const StringView &key, const StringView &val, 
                const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), 
                UpdateType type = UpdateType::ALWAYS);
            // 更新string键值对,并返回旧值 
            OptionalString getset(const StringView &key, const StringView &val);
            // 批量获取string键值对 
            template <typename Input, typename Output>
            void mget(Input first, Input last, Output output);
            // 批量新增string键值对 
            template <typename Input>
            void mset(Input first, Input last);
        }
    }
}

列表操作接口:

cpp 复制代码
namespace sw {
    namespace redis {
        class Redis {
            // 往右新增string键值对
            long long rpush(const StringView &key, const StringView &val);
            // 往左新增string键值对
            long long lpush(const StringView &key, const StringView &val);
            // 获取元素数量
            long long llen(const StringView &key);
            // 往右新增多个string键值对
            template <typename Input>
            long long rpush(const StringView &key, Input first, Input last);
            // 往左新增多个string键值对
            template <typename Input>
            long long lpush(const StringView &key, Input first, Input last);
            // 获取指定索引位置的string键值对
            OptionalString lindex(const StringView &key, long long index);
            // 从左边弹出一个string键值对
            OptionalString lpop(const StringView &key);
            // 从右边弹出一个string键值对
            OptionalString rpop(const StringView &key);
            // 获取指定范围的string键值对
            template <typename Output>
            void lrange(const StringView &key, long long start, long long stop, Output output);
        }
    }
}

哈希操作接口:

cpp 复制代码
namespace sw {
    namespace redis {
        class Redis {
            // 判断哈希表中是否存在指定字段
            bool hexists(const StringView &key, const StringView &field);
            // 删除哈希表中指定字段
            long long hdel(const StringView &key, const StringView &field);
            // 批量删除哈希表中指定字段
            template <typename Input>
            long long hdel(const StringView &key, Input first, Input last);
            // 获取哈希表中所有字段和值
            template <typename Output>
            void hgetall(const StringView &key, Output output);
            // 获取哈希表中指定字段的值
            OptionalString hget(const StringView &key, const StringView &field);
            // 批量获取哈希表中指定字段的值
            template <typename Input, typename Output>
            void hmget(const StringView &key, Input first, Input last, Output output);
            // 批量设置哈希表中指定字段的值
            template <typename Input>
            void hmset(const StringView &key, Input first, Input last);
            // 设置哈希表中指定字段的值
            long long hset(const StringView &key, const StringView &field, const StringView &val);
            // 哈希表中指定字段的值增加指定增量 
            long long hincrby(const StringView &key, const StringView &field, long long increment);
        };
    }
}

无序集合操作接口:

cpp 复制代码
namespace sw {
    namespace redis {
        class Redis {
            // 添加集合元素
            long long sadd(const StringView &key, const StringView &member);
            // 批量添加集合元素
            template <typename Input> 
            long long sadd(const StringView &key, Input first, Input last);
            // 获取集合元素数量
            long long scard(const StringView &key);
            // 判断集合元素是否存在
            bool sismember(const StringView &key, const StringView &member);
            // 获取集合所有元素
            template <typename Output> 
            void smembers(const StringView &key, Output output);
        };
    }
}

有序集合操作接口:

cpp 复制代码
namespace sw {
    namespace redis {
        class Redis {
            // 添加有序集合元素
            long long zadd(const StringView &key, const StringView &member,
                double score, UpdateType type = UpdateType::ALWAYS, bool changed = false);
            // 批量添加有序集合元素
            template <typename Input>
            long long zadd(const StringView &key, Input first, Input last,
                UpdateType type = UpdateType::ALWAYS, bool changed = false);
            // 获取有序集合元素数量
            long long zcard(const StringView &key);
            // 有序集合元素分数增加
            double zincrby(const StringView &key, double increment, const StringView &member);
            // 获取有序集合排名区间元素
            template <typename Output>
            void zrange(const StringView &key, long long start, long long stop, Output output);
            // 获取有序集合分数区间元素
            template <typename Interval, typename Output>
            void zrangebyscore(const StringView &key, const Interval &interval, Output output);
            // 获取有序集合排名区间元素(带偏移量和数量限制)
            template <typename Interval, typename Output>
            void zrangebyscore(const StringView &key, const Interval &interval,
                const LimitOptions &opts, Output output);
            // 获取有序集合元素排名
            OptionalLongLong zrank(const StringView &key, const StringView &member);
            // 获取有序集合字典序区间元素
            template <typename Interval, typename Output>
            void zrangebylex(const StringView &key, const Interval &interval, Output output);
            // 获取有序集合字典序区间元素(带偏移量和数量限制)
            template <typename Interval, typename Output>
            void zrangebylex(const StringView &key, const Interval &interval,
                const LimitOptions &opts, Output output);
            // 获取有序集合排名区间元素(降序)
            template <typename Output>
            void zrevrange(const StringView &key, long long start, long long stop, Output output); 
            // 获取有序集合分数区间元素(降序)
            template <typename Interval, typename Output>
            void zrevrangebyscore(const StringView &key, const Interval &interval, Output output);
            // 获取有序集合排名区间元素(降序,带偏移量和数量限制)
            template <typename Interval, typename Output>
            void zrevrangebyscore(const StringView &key, const Interval &interval,
                const LimitOptions &opts, Output output);
            // 获取有序集合元素排名(降序)
            OptionalLongLong zrevrank(const StringView &key, const StringView &member);
            // 获取有序集合字典序区间元素(降序)
            template <typename Interval, typename Output>
            void zrevrangebylex(const StringView &key, const Interval &interval, Output output);
            // 获取有序集合字典序区间元素(降序,带偏移量和数量限制)
            template <typename Interval, typename Output>
            void zrevrangebylex(const StringView &key, const Interval &interval,
                const LimitOptions &opts, Output output);
            // 获取有序集合元素分数
            OptionalDouble zscore(const StringView &key, const StringView &member);
            // 有序集合元素扫描
            template <typename Output>
            Cursor zscan(const StringView &key, Cursor cursor, const StringView &pattern,
                long long count, Output output);
            // 删除有序集合元素
            long long zrem(const StringView &key, const StringView &member);
            // 弹出有序集合最大分数元素
            Optional<std::pair<std::string, double>> zpopmax(const StringView &key);
            // 弹出有序集合最小分数元素
            Optional<std::pair<std::string, double>> zpopmin(const StringView &key);
            // 删除有序集合分数区间元素
            template <typename Interval>
            long long zremrangebyscore(const StringView &key, const Interval &interval);
            // 删除有序集合排名区间元素
            long long zremrangebyrank(const StringView &key, long long start, long long stop);
            // 删除有序集合字典序区间元素
            template <typename Interval>
            long long zremrangebylex(const StringView &key, const Interval &interval);
        };
    }
}

3. transaction、pipeline、watch

  • 默认情况下,创建一个 transaction、pipeline 对象是比较昂贵的,因为会新建连接实例化对象,建议尽可能重复使用。
  • 因此为了减低使用成本,建议使用连接池,并且连接池中最大的连接数量必须大于1,创建transaction、pipeline 对象的时候,设置不创建新连接,而是从连接池中获取。
  • transaction、pipeline 对象的常规数据操作,无法直接获取到结果,必须执行 .exec() 来获取结果。它们的使用方式基本一致,但是使用目标不同。
  • transaction 为了能够让多个操作成为一个原子操作来执行;pipeline 为了能够实现网络通信的管线话传输来提高效率。

注意:transaction、pipeline 对象的操作不是线程安全的,析构时会将连接返回给连接池。

cpp 复制代码
namespace sw {
    namespace redis {
        // Redis 客户端操作类
        class Redis {
            // 1.从连接池获取连接,创建Transaction对象
            // pipe: 若为 true 表示开启管道模式,所有操作都在一个事务中执行,若为 false 表示不开启管道模式
            // new_connection: 若为 true 表示新创建连接,若为 false 表示从连接池获取连接
            Transaction transaction(bool piped = false, bool new_connection = true);
            // 2.从连接池获取连接,创建Pipeline对象
            // new_connection: 若为 true 表示新创建连接,若为 false 表示从连接池获取连接
            Pipeline pipeline(bool new_connection = true);
        };
        using Transaction = QueuedRedis<TransactionImpl>;
        using Pipeline = QueuedRedis<PipelineImpl>;

        // 队列式Redis操作类
        template <typename Impl>
        class QueuedRedis {
            // 返回底层的 Redis 客户端对象
            Redis redis(); 
            // 执行队列中的所有操作,返回结果队列
            QueuedReplies exec(); 
            // 清空队列中的所有操作
            void discard(); 
            // 3.清空数据库中的所有键值对
            QueuedRedis& flushall(bool async = false);
            // 4.删除指定键值对
            QueuedRedis& del(const StringView &key);
            // 5.检查指定键是否存在
            QueuedRedis& exists(const StringView &key);
            // 6.设置键的过期时间
            QueuedRedis& expire(const StringView &key, const std::chrono::seconds &timeout);
            // 7.获取指定键的值
            QueuedRedis& get(const StringView &key);
            // 8.获取指定键的子字符串
            QueuedRedis& getrange(const StringView &key, long long start, long long end);
            // 9.设置指定键的值,并返回旧值
            QueuedRedis& getset(const StringView &key, const StringView &val);
            // 10.批量获取多个键的值
            template <typename Input>
            QueuedRedis& mget(Input first, Input last);
            //......
            // 11.检查哈希表中指定字段是否存在
            QueuedRedis& hexists(const StringView &key, const StringView &field);
            // 12.获取哈希表中指定字段的值
            QueuedRedis& hget(const StringView &key, const StringView &field);
            // 13.获取哈希表中所有字段和值
            QueuedRedis& hgetall(const StringView &key);
            // 14.获取列表中指定范围的元素
            QueuedRedis& lrange(const StringView &key, long long start, long long stop);
        };
        
        // 队列式Redis操作结果类
        class QueuedReplies {
            // 返回结果队列的大小
            std::size_t size();
            // 获取指定索引位置的操作结果
            template <typename Result>
            auto get(std::size_t idx) -> typename std::enable_if<!std::is_same<Result, bool>::value, Result>::type 
            // 将指定索引位置的操作结果写入输出迭代器
            template <typename Output>
            void get(std::size_t idx, Output output);
            // 返回指定索引位置的原始 redisReply 对象
            redisReply& get(std::size_t idx);
        };
    }
}

transaction 事务操作主要目的是,将多个操作当做一个原子操作执行。但是事务操作虽然是原子操作,但不代表不会出现错误,因为 redis 事务操作中,无法感知数据变化,有可能数据被修改了会导致一些错误。

因此,事务操作之前,最好对 key 进行监控,若 key 的 val 发生改变,则事务抛出异常 (在异常处理中,可以重新针对新的数据做出新的操作),因此 transaction 通常是和 watch 一起搭配使用的。也就是说只要在你提交事务前它被别人改过,当前事务就会失败。

cpp 复制代码
class sw {
    class redis {
        class Redis {
            // 监控一个key
            void watch(const StringView &key);
            // 监控多个key
            template <typename Input>
            void watch(Input first, Input last);
        }
    }
}

六. redis-plus-plus 使用样例

1. 目录结构

bash 复制代码
example/
|-- redis
|   |-- complement.cc
|   |-- hash.cc
|   |-- list.cc
|   |-- makefile
|   |-- pipeline.cc
|   |-- set.cc
|   |-- string.cc
|   |-- transaction.cc
|   |-- watch.cc
|   |-- zset.cc

2. 项目构建

bash 复制代码
# makefile
all: string list hash set zset complement pipeline transaction watch
string: string.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
list: list.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
hash: hash.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
set: set.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
zset: zset.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
complement: complement.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
pipeline: pipeline.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
transaction: transaction.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread
watch: watch.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -lpthread

.PHONY: clean
clean:
	rm -f string list hash set zset complement pipeline transaction watch

编译生成可执行程序:

3. 代码实现

3.1 字符串、列表、哈希、无序集合、有序集合操作

cpp 复制代码
// string.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);
    
    // 1.添加一个键值对
    redis.set("name", "zhangsan");
    // 2.获取一个键值对
    sw::redis::OptionalString val = redis.get("name");
    std::cout << "name: " << *val << std::endl;
    // 3.删除键值对
    redis.del("name");
    // 4.对键值对中的数据进行自增/自减
    redis.set("age", "18");
    redis.incrby("age", 1);
    std::cout << "age: " << *redis.get("age") << std::endl;
    redis.decrby("age", 1);
    std::cout << "age: " << *redis.get("age") << std::endl;
    // 5.删除字符串
    redis.del("age");

    return 0;
}
cpp 复制代码
// list.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);

    // 1.从左侧插入数据
    redis.lpush("skills", "C");
    std::vector<std::string> arr = {"C++", "Java", "Python"};
    redis.lpush("skills", arr.begin(), arr.end());
    // 2.从右侧获取数据
    int len = redis.llen("skills");
    for (int i = len - 1; i >= 0; i--) {
        // 3.获取指定下标的数据
        sw::redis::OptionalString val = redis.lindex("skills", i);
        std::cout << *val << " ";
    }
    std::cout << std::endl;
    // 4.获取所有的数据
    std::vector<std::string> skills;
    redis.lrange("skills", 0, -1, std::back_inserter(skills));
    for (auto& val : skills) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    // 5.删除左右两侧的数据
    redis.lpop("skills");
    redis.rpop("skills");
    // 6.输出最终的数据
    skills.clear();
    redis.lrange("skills", 0, -1, std::back_inserter(skills));
    for (auto& val : skills) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    // 7.删除列表
    redis.del("skills");

    return 0;
}
cpp 复制代码
// hash.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);
    
    // 1.插入一个哈希键值对
    redis.hset("user", "name", "zhangsan");
    // 2.批量插入哈希键值对
    std::unordered_map<std::string, std::string> userinfo = {
        {"age", "18"},
        {"gender", "male"},
        {"hobby", "music"}
    };
    redis.hmset("user", userinfo.begin(), userinfo.end());
    // 3.获取一个哈希键值对
    sw::redis::OptionalString val = redis.hget("user", "name");
    std::cout << "name: " << *val << std::endl;
    // 4.获取所有哈希键值对
    std::unordered_map<std::string, std::string> results;
    redis.hgetall("user", std::inserter(results, results.begin()));
    for (auto& val : results) {
        std::cout << val.first << ": " << val.second << std::endl;
    }
    // 5.对键值对中的值进行自增/自减
    redis.hincrby("user", "age", 5);
    std::cout << "age: " << *redis.hget("user", "age") << std::endl;
    redis.hincrby("user", "age", -5);
    std::cout << "age: " << *redis.hget("user", "age") << std::endl;
    // 6.删除哈希
    redis.del("user");

    return 0;
}
cpp 复制代码
// set.cc
#include <iostream>
#include <unordered_set>
#include <chrono>
#include <sw/redis++/redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);
    
    // 1.向集合中添加一个元素
    redis.sadd("skills", "C++");
    // 2.批量向集合中添加多个元素
    std::unordered_set<std::string> skills = {"Python", "Java", "Go"};
    redis.sadd("skills", skills.begin(), skills.end());
    // 3.获取集合中元素的个数
    long long size = redis.scard("skills");
    std::cout << "元素的个数:" << size << std::endl;
    // 4.判断集合中是否包含某个元素
    bool ret = redis.sismember("skills", "C++");
    if (ret) {
        std::cout << "C++ 存在" << std::endl;
    } else {
        std::cout << "C++ 不存在" << std::endl;
    }
    // 5.获取集合中所有元素
    std::unordered_set<std::string> skills2;
    redis.smembers("skills", std::inserter(skills2, skills2.begin()));
    for (auto& val : skills2) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    // 6.删除无序集合
    redis.del("skills");

    return 0;
}
cpp 复制代码
// zset.cc
#include <iostream>
#include <unordered_set>
#include <chrono>
#include <sw/redis++/redis.h>

// 打印有序集合中的所有元素
void print(sw::redis::Redis& redis, const std::string& key) {
    sw::redis::Cursor cursor = 0; // 定义游标变量
    std::vector<std::pair<std::string, double>> memers; // 删除集合
    while (true) {
        // 扫描有序集合中的元素
        cursor = redis.zscan(key, cursor, "*", 10, std::back_inserter(memers));
        if (cursor == 0) {
            break;
        }
    }
    // 打印有序集合中的所有元素
    for (auto& val : memers) {
        std::cout << val.first << " " << val.second << std::endl;
    }
}

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);
    
    // 1.向有序集合中添加元素:单个/批量添加
    std::cout << "----------添加数据----------" << std::endl;
    redis.zadd("score", "zhangsan", 34);
    std::unordered_map<std::string, double> scores = {
        {"lisi", 95}, 
        {"wangwu", 56},
        {"zhaoliu", 78},
        {"tianqi", 89},
        {"xiaohong", 99},
        {"xiaoming", 88},
        {"xiaolan", 77},
        {"xiaowang", 66}
    };
    redis.zadd("score", scores.begin(), scores.end());
    print(redis, "score");
    // 2.获取集合中元素的数量
    std::cout << "----------获取数据----------" << std::endl;
    std::cout << "元素数量:" << redis.zcard("score") << std::endl;
    // 3.获取指定元素权重得分
    std::cout << "小红得分:" << *redis.zscore("score", "xiaohong") << std::endl;
    // 4.获取指定元素的权重排名
    std::cout << "小红从低到高的排名:" << *redis.zrank("score", "xiaohong") << std::endl;
    std::cout << "小红从高到低的排名:" << *redis.zrevrank("score", "xiaohong") << std::endl;
    // 5.获取集合数据
    std::vector<std::string> results;
    redis.zrange("score", 0, 2, std::back_inserter(results));
    std::cout << "从低到高排名的前3名:";
    for (auto& val : results) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    results.clear();
    redis.zrevrange("score", 0, 2, std::back_inserter(results));
    std::cout << "从高到低排名的前3名:";
    for (auto& val : results) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    results.clear();
    sw::redis::BoundedInterval<double> interval(0, 60, sw::redis::BoundType::RIGHT_OPEN);
    redis.zrangebyscore("score", interval, std::back_inserter(results));
    std::cout << "60分以下的学生从低到高的排名:";
    for (auto& val : results) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    results.clear();
    redis.zrevrangebyscore("score", interval, std::back_inserter(results));
    std::cout << "60分以下的学生从高到低的排名:";
    for (auto& val : results) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    // 6.删除指定元素
    std::cout << "----------删除小红----------" << std::endl;
    redis.zrem("score", "xiaohong");
    print(redis, "score");
    // 7.弹出一个最高分和最低分
    sw::redis::Optional<std::pair<std::string, double>> max = redis.zpopmax("score");
    if (max) {
        std::cout << "弹出最高分:" << max->first << ":" << max->second << std::endl;
    }
    sw::redis::Optional<std::pair<std::string, double>> min = redis.zpopmin("score");
    if (min) {
        std::cout << "弹出最低分:" << min->first << ":" << min->second << std::endl;
    }
    print(redis, "score");
    // 8.以权重得分删除指定区间的数据
    std::cout << "----------删除70~80分数的学生----------" << std::endl;
    sw::redis::BoundedInterval<double> interval1(70, 80, sw::redis::BoundType::OPEN);
    redis.zremrangebyscore("score", interval1);
    print(redis, "score");
    // 9.以权重排名删除指定区间的数据
    std::cout << "----------删除排名第1~3名的学生----------" << std::endl;
    redis.zremrangebyrank("score", 0, 2);
    print(redis, "score");
    // 10.修改元素权重
    std::cout << "----------修改田七的权重----------" << std::endl;
    redis.zincrby("score", 31, "tianqi");
    print(redis, "score");
    // 11.删除有序集合
    redis.del("score");

    return 0;
}
cpp 复制代码
// cpmplement.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);
    
    // 1.字段生命周期控制(设置过期时间/移除过期时间)
    redis.set("name", "zhangsan");
    redis.expire("name", std::chrono::seconds(5));
    redis.persist("name");
    // 2.判断字段是否存在
    bool ret = redis.exists("name");
    if (ret == true) {
        std::cout << "name 字段存在" << std::endl;
    } else {
        std::cout << "name 字段不存在" << std::endl;
    }
    // 3.有序集合字典序操作
    redis.zadd("score", "a", 100);
    redis.zadd("score", "d", 100);
    redis.zadd("score", "c", 100);
    redis.zadd("score", "b", 100);
    redis.zadd("score", "e", 100);
    redis.zadd("score", "f", 100);
    sw::redis::LimitOptions limit; // 偏移量为1,获取3个元素
    limit.offset = 1;
    limit.count = 3;
    sw::redis::BoundedInterval<std::string> interval("b", "f", sw::redis::BoundType::CLOSED); // 包含b和f
    std::vector<std::string> results;
    std::cout << "字典序升序:";
    redis.zrangebylex("score", interval, limit, std::back_inserter(results));
    for (auto& val : results) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    results.clear();
    std::cout << "字典序降序:";
    redis.zrevrangebylex("score", interval, limit, std::back_inserter(results));
    for (auto& val : results) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    // 4.库的清理操作
    redis.flushall();

    return 0;
}

3.2 transaction、pipeline、watch 操作

cpp 复制代码
// transaction.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);

    {
        // 1.从连接池获取连接,创建Transaction对象,需要注意的是,transaction对象的生命周期必须要在连接池的生命周期内
        sw::redis::Transaction transaction = redis.transaction(false, false);
        // 2.使用transaction对象进行批量操作
        transaction.set("key1", "value1");
        transaction.set("key2", "value2");
        transaction.get("key1");
        transaction.get("key2");
        // 3.执行批量操作
        sw::redis::QueuedReplies replies = transaction.exec();
        // 4.处理批量操作的结果:需要根据操作的顺序来获取结果
        std::cout << "set key1: " << replies.get<bool>(0) << std::endl;
        std::cout << "set key2: " << replies.get<bool>(1) << std::endl;
        std::cout << "key1: " << replies.get<std::string>(2) << std::endl;
        std::cout << "key2: " << replies.get<std::string>(3) << std::endl;
    }
    {
        // 1.从连接池获取连接,创建Transaction对象
        sw::redis::Transaction transaction = redis.transaction(false, false);
        // 2.使用transaction对象进行批量操作
        transaction.hset("stu", "name", "zhangsan");
        transaction.hset("stu", "age", "18");
        transaction.hset("stu", "gender", "male");
        transaction.hgetall("stu");
        // 3.执行批量操作
        sw::redis::QueuedReplies replies = transaction.exec();
        // 4.处理批量操作的结果:需要根据操作的顺序来获取结果
        std::cout << "hset stu: " << replies.get<bool>(0) << std::endl;
        std::cout << "hset stu: " << replies.get<bool>(1) << std::endl;
        std::cout << "hset stu: " << replies.get<bool>(2) << std::endl;
        std::unordered_map<std::string, std::string> stu;
        replies.get(3, std::inserter(stu, stu.end()));
        for (auto &val : stu) {
            std::cout << val.first << ": " << val.second << std::endl;
        }
    }
    // 删除key1键、key2键、stu哈希表
    redis.del("key1");
    redis.del("key2");
    redis.del("stu");

    return 0;
}
cpp 复制代码
// pipeline.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);

    {
        // 1.从连接池获取连接,创建Pipeline对象,需要注意的是,pipeline对象的生命周期必须要在连接池的生命周期内
        sw::redis::Pipeline pipeline = redis.pipeline(false);
        // 2.使用pipeline对象进行批量操作
        pipeline.set("key1", "value1");
        pipeline.set("key2", "value2");
        pipeline.get("key1");
        pipeline.get("key2");
        // 3.执行批量操作
        sw::redis::QueuedReplies replies = pipeline.exec();
        // 4.处理批量操作的结果:需要根据操作的顺序来获取结果
        std::cout << "set key1: " << replies.get<bool>(0) << std::endl;
        std::cout << "set key2: " << replies.get<bool>(1) << std::endl;
        std::cout << "key1: " << replies.get<std::string>(2) << std::endl;
        std::cout << "key2: " << replies.get<std::string>(3) << std::endl;
    }
    {
        // 1.从连接池获取连接,创建Pipeline对象
        sw::redis::Pipeline pipeline = redis.pipeline(false);
        // 2.使用pipeline对象进行批量操作
        pipeline.hset("stu", "name", "zhangsan");
        pipeline.hset("stu", "age", "18");
        pipeline.hset("stu", "gender", "male");
        pipeline.hgetall("stu");
        // 3.执行批量操作
        sw::redis::QueuedReplies replies = pipeline.exec();
        // 4.处理批量操作的结果:需要根据操作的顺序来获取结果
        std::cout << "hset stu: " << replies.get<bool>(0) << std::endl;
        std::cout << "hset stu: " << replies.get<bool>(1) << std::endl;
        std::cout << "hset stu: " << replies.get<bool>(2) << std::endl;
        std::unordered_map<std::string, std::string> stu;
        replies.get(3, std::inserter(stu, stu.end()));
        for (auto &val : stu) {
            std::cout << val.first << ": " << val.second << std::endl;
        }
    }
    // 删除key1键、key2键、stu哈希表
    redis.del("key1");
    redis.del("key2");
    redis.del("stu");

    return 0;
}
cpp 复制代码
// watch.cc
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>

int main()
{
    // 创建连接选项、连接池选项配置对象、并实例化 Redis 客户端对象
    sw::redis::ConnectionOptions conn_opts = {
        .host = "192.168.174.128",
        .port = 6379,
        .password = "123456"
    };
    sw::redis::ConnectionPoolOptions pool_opts = {
        .size = 10,
        .connection_idle_time = std::chrono::milliseconds(600)
    };
    sw::redis::Redis redis(conn_opts, pool_opts);
    
    // 1.添加初始值
    redis.set("score", "100");
    {
        // 2.通过redis对象创建transaction对象
        sw::redis::Transaction transaction = redis.transaction(false, false);
        // 3.通过transaction对象获取redis对象(redis对象与transaction对象共享连接)
        sw::redis::Redis redis = transaction.redis();
        while (true) {
            try {
                // 4.通过redis对象执行watch操作(transaction对象中没有watch操作、transaction操作无法获取结果,但是redis操作可以获取结果)
                redis.watch("score");
                bool ret = redis.exists("score");
                if (!ret) {
                    break; // 不存在时跳出循环
                }
                // 5.通过transaction对象执行
                transaction.incrby("score", 20);
                transaction.exec();
                break; // 事务执行成功时跳出循环
            } catch (const sw::redis::WatchError &e) {
                std::cout << "transaction watch error: " << e.what() << std::endl;
                continue;
            } catch (const sw::redis::Error &e) {
                std::cout << "transaction exec error: " << e.what() << std::endl;
                throw;
            } 
        }
    }
    // 6.删除score键
    redis.del("score");

    return 0;
}

七. redis-plus-plus 封装

1. 设计与实现

只需要创建 redis 服务器配置结构即可。

1.1 目录结构

bash 复制代码
source/
|-- redis.cc
|-- redis.h

1.2 代码实现

cpp 复制代码
// redis.h
#include <iostream>
#include <chrono>
#include <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>

namespace xzyredis {
    // Redis 客户端配置选项
    struct redis_settings {
        int db = 0; // 数据库编号
        int port = 6379; // 端口号
        std::string host; // 主机地址
        std::string user = "default"; // 默认用户
        std::string passwd; // 密码
        size_t connection_pool_size = 3; // 连接池大小
    };
    // 创建 Redis 客户端对象工厂类
    class RedisFactory {
    public:
        static std::shared_ptr<sw::redis::Redis> create(const redis_settings& settings);
    };
}
cpp 复制代码
// redis.cc
#include "redis.h"

namespace xzyredis {
    // 创建 Redis 客户端对象
    std::shared_ptr<sw::redis::Redis> RedisFactory::create(const redis_settings& settings) {
        // 1.创建连接选项
        sw::redis::ConnectionOptions conn_opts = {
            .host = settings.host,
            .port = settings.port,
            .user = settings.user,
            .password = settings.passwd,
            .db = settings.db
        };
        // 2.连接池选项配置对象
        sw::redis::ConnectionPoolOptions pool_opts = {
            .size = settings.connection_pool_size
        };
        // 3.实例化 Redis 客户端对象
        return std::make_shared<sw::redis::Redis>(conn_opts, pool_opts);
    }
}

2. 使用样例

1.1 目录结构

bash 复制代码
test/
|-- redis
    |-- makefile
    |-- redis_test.cc

1.2 项目构建

bash 复制代码
# makefile
redis_test: redis_test.cc ../../source/redis.cc
	g++ -o $@ $^ -std=c++17 -lhiredis -lredis++ -pthread

.PHONY: clean
clean:
	rm -f redis_test

编译生成可执行程序:

1.3 代码实现

cpp 复制代码
// redis_test.cc
#include "../../source/redis.h"

int main()
{
    try {
        // 1.创建 Redis 客户端配置选项
        xzyredis::redis_settings settings = {
            .host = "192.168.174.128",
            .passwd = "123456",
            .connection_pool_size = 3
        };
        // 2.创建 Redis 客户端对象
        std::shared_ptr<sw::redis::Redis> redis = xzyredis::RedisFactory::create(settings);
        {
            // 3.创建事务对象
            sw::redis::Transaction transaction = redis->transaction(false, false);
            // 4.获取事务对象的 Redis 客户端对象
            sw::redis::Redis redis = transaction.redis();
            // 5.添加一个键值对
            redis.set("name", "zhangsan");
            // 6.获取一个键值对
            sw::redis::OptionalString val = redis.get("name");
            std::cout << "name: " << *val << std::endl;
            // 7.删除所有键值对
            redis.flushall();
        }
        
    }
    catch (const sw::redis::Error& e) {
        std::cerr << "Redis connection failed: " << e.what() << std::endl;
    }
}
相关推荐
SelectDB技术团队2 小时前
日志成本降低 83%:云上 Elasticsearch 和 SelectDB 的基准测试及成本分析
数据库·apache
全栈前端老曹2 小时前
【Redis】Redis 客户端连接与编程实践——Python/Java/Node.js 连接 Redis、实现计数器、缓存接口
前端·数据库·redis·python·缓存·全栈
霖霖总总2 小时前
[小技巧72]AFTER COMMIT vs AFTER SYNC:MySQL 半同步复制的持久性博弈
数据库·mysql
麦聪聊数据2 小时前
后端研发范式演进:从对象映射(ORM)到逻辑解耦(SQL2API)
数据库·sql·架构
爱敲代码的小鱼2 小时前
后端web开发Mysql数据库:
数据库·mysql
Franciz小测测2 小时前
GitLab 双物理机高可用新方案(基于 Rsyncd + Keepalived+PostgreSQL 流复制)
数据库·postgresql·gitlab
野犬寒鸦2 小时前
WebSocket协同编辑:高性能Disruptor架构揭秘及项目中的实战应用
java·开发语言·数据库·redis·后端
鸽芷咕2 小时前
迁移即一致!金仓数据库内置数据校验能力如何支撑信创平滑替换?
数据库
TDengine (老段)2 小时前
TDengine IDMP 基本概念
大数据·数据库·物联网·ai·时序数据库·tdengine·涛思数据