目录
[1 认识 Redis 的应用层协议 - RESP 协议](#1 认识 Redis 的应用层协议 - RESP 协议)
[2 安装 Redis-plus-plus 库](#2 安装 Redis-plus-plus 库)
[3 使用 Redis-plus-plus 库](#3 使用 Redis-plus-plus 库)
[3.1 基础使用](#3.1 基础使用)
[3.2 全局指令对应的函数](#3.2 全局指令对应的函数)
[set 与 get 函数](#set 与 get 函数)
[mset 与 mget 函数](#mset 与 mget 函数)
[exists 函数](#exists 函数)
[del 函数](#del 函数)
[keys 函数](#keys 函数)
[expire 与 ttl 函数](#expire 与 ttl 函数)
[type 函数](#type 函数)
[3. 3 list 类型相关函数](#3. 3 list 类型相关函数)
[blpop 函数](#blpop 函数)
[3.4 set 类型相关函数](#3.4 set 类型相关函数)
[smembers 函数](#smembers 函数)
[3.5 zset 类型相关函数](#3.5 zset 类型相关函数)
[zrange 函数](#zrange 函数)
1 认识 Redis 的应用层协议 - RESP 协议
Redis 的 RESP 协议(RE dis S erialization Protocol)是客户端与 Redis 服务器之间通信的应用层协议。它设计简单、易解析、效率高,并且是二进制安全的。从 Redis 2.0 开始,RESP 成为标准通信协议;Redis 6.0 引入了 RESP3,增加了更多数据类型和特性。
RESP 的设计目标
简单:人类可读,便于调试(可用 telnet 直接测试)。
高效:解析器实现简单,减少客户端/服务端 CPU 开销。
二进制安全:可以传输任意字节,不依赖特定编码。
可扩展:RESP3 支持更多类型,如 Map、Set、Push 等。
RESP 的数据类型(RESP2)
RESP2 中,所有消息由以下 5 种基本类型组成,通过第一个字节区分类型:
| 类型 | 前缀 | 说明 | 示例 |
|---|---|---|---|
| Simple String | + |
简单字符串,不含 CRLF | +OK\r\n |
| Error | - |
错误信息 | -ERR unknown command\r\n |
| Integer | : |
整数 | :1000\r\n |
| Bulk String | $ |
二进制安全字符串 | $6\r\nfoobar\r\n |
| Array | * |
数组,可嵌套 | *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n |
2 安装 Redis-plus-plus 库
虽然 RESP 有各种数据类型,但实际编程时不需要我们手动的去构建,已经有成熟的第三方库对此进行了封装,比如 Redis-plus-plus 库,要安装 Redis-plus-plus 库,得先安装 hiredis 库,因为 Redis-plus-plus 库依赖 hiredis 库
Ubuntu:apt install libhiredis-dev
Centos:yum install hiredis-devel.x86_64
下载redis-plus-plus 源码:git clone https://github.com/sewenew/redis-plus-plus.git
Ubuntu 直接使用 cmake 构建,Centos 自带的 cmake 版本较低,需要先安装 cmake3
bash
cd redis-plus-plus
mkdir build
cd build
cmake3 ..
make
make install
构建成功后,会在 系列头⽂件. 会在 /usr/local/include/ 中多出 sw ⽬录,并且内部包含redis-plus-plus的⼀ /usr/local/lib/ 中多出⼀系列 libredis 库⽂件
3 使用 Redis-plus-plus 库
下面只介绍部分指令对应的函数,其余的指令对应的函数可以通过学习介绍的函数举一反三
- 当一个函数,参数需要传递多个值的时候,往往都是支持 初始化列表 或者是 一对迭代器 的方式,来进行实现的
- 当一个函数,返回值需要表示多个数据的时候,也往往会借助 插入迭代器,来实现往一个容器中添加元素的效果
- 当某些场景涉及到无效值的时候,往往会搭配 std :: optional来进行使用.
3.1 基础使用
在你的 C++ 代码中,包含头文件并创建 Redis 对象即可开始操作:
cpp
#include <sw/redis++/redis++.h>
// 创建 Redis 对象并连接本地 Redis
auto redis = sw::redis::Redis("tcp://127.0.0.1:6379");
// 测试连接
std::string result = redis.ping(); // 返回 "PONG"
Redis 对象内部维护连接池,可以安全地在多线程环境中共享使用。连接是延迟创建的,只有首次发送命令时才会真正建立 TCP 连接。
现在要使用 makefile 编译上面的代码,在编译之前要先寻找 libredis++.a 和 libhiredis.a 这两个静态库在哪里:
bash
sudo find /usr/ -name libhiredis*
sudo find /usr/ -name libredis*
然后在 makefile 里显示的指明第三方库的具体路径(/usr/local/lib64/不在默认搜索路径,不能使用 -l 选项指明):
hello:hello.cc
g++ $^ -o $@ /usr/local/lib64/libredis++.a /usr/local/lib/libhiredis.a -lpthread -std=c++17
.PHONY:clean
clean:
rm -rf hello
并注意使用 C++17 标准编译
3.2 全局指令对应的函数
set 与 get 函数
set 的函数原型:
cpp
ool Redis::set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type)
StringView 类型是什么:
StringView是 redis-plus-plus 中用于表示只读字符串视图的类型。它的设计目的是为了避免不必要的字符串拷贝,提升性能。本质与目的
StringView本身不拥有字符串数据,它只是一个"视图",内部包含一个指向字符数据的指针和一个长度。这和你把std::string传给函数时可能发生的拷贝不同,StringView只是引用了已有的内存,因此传递效率很高。它的角色类似于 C++17 标准库中的
std::string_view。为什么需要它?
这涉及 redis-plus-plus 对 C++ 标准版本的兼容策略。
当 redis-plus-plus 使用 C++17 或更高标准编译时 ,它内部的
StringView实际上就是std::string_view的别名(或直接使用后者)。当 redis-plus-plus 使用 C++11 或 C++14 编译时 ,由于标准库还没有
std::string_view,redis-plus-plus 就提供了自己实现的StringView作为替代品,保证 API 在不同标准下保持一致。
get 的函数原型:
cpp
OptionalString Redis::get(const StringView &key)
OptionalString 类型是什么:
OptionalString是 redis-plus-plus 库定义的一个类型别名,它本质上是一个"可能包含值,也可能为空"的字符串包装器它解决什么问题?
在 Redis 中,
GET一个不存在的键时,服务器会返回nil(空值)。如果get函数直接返回std::string,就无法区分"值是空字符串"和"键根本不存在"这两种情况。而 C++ 的std::optional正好能表达"有值/无值"的语义。它的真实身份是什么?
OptionalString的具体类型取决于你编译 redis-plus-plus 时使用的 C++ 标准:
C++17 模式下 :它就是
std::optional<std::string>的别名。C++11/14 模式下 :因为标准库还没有
std::optional,所以 redis-plus-plus 提供了一个自定义的Optional模板类来实现同样功能。如何正确使用它?
拿到
OptionalString后,必须先判断它是否包含有效值 ,再取值。直接取值一个空的 optional 会抛出std::bad_optional_access异常。
cppauto val = redis.get("key"); // 正确做法:先判断 if (val) { // OptionalString 可以隐式转换为 bool std::cout << *val << std::endl; // 用 * 或 val.value() 解引用 } else { // 键不存在 }
get 与 set 的使用示例:
cpp
#include <sw/redis++/redis++.h>
#include <iostream>
int main()
{
auto redis = sw::redis::Redis("tcp://127.0.0.1:6379");
redis.flushall();
redis.set("key1","111");
redis.set("key2","222");
redis.set("key3","333");
auto value1 = redis.get("key1");
if(value1) std::cout << "value1=" << value1.value() << std::endl;
auto value2 = redis.get("key2");
if(value2) std::cout << "value2=" << value2.value() << std::endl;
auto value3 = redis.get("key3");
if(value3) std::cout << "value3=" << value3.value() << std::endl;
return 0;
}
使用 set 设置过期时间
第三个参数 ttl 接收一个 std::chrono::milliseconds 对象,默认值为 0 毫秒。当 ttl 大于 0 时,键将在指定时间后自动过期。
使用示例:
cpp
#include <sw/redis++/redis++.h>
#include <chrono>
using namespace std::chrono_literals; // 启用 10s, 5min 等字面量
auto redis = sw::redis::Redis("tcp://127.0.0.1:6379");
// 设置 "session:key" 的值为 "data",并在 10 秒后过期
redis.set("session:key", "data", std::chrono::seconds(10));
// 或者使用字面量
redis.set("session:key", "data", 10s);
// 也可以使用其他时间单位,例如 500 毫秒
redis.set("temp:key", "value", 500ms);
NX 和 XX 选项设置
set 的第四个参数 UpdateType 是一个枚举类型,定义在 sw::redis 命名空间下。
sw::redis::UpdateType::NOT_EXIST:对应 Redis 的 NX (if Not eXists)语义,仅当键不存在时才执行设置操作。
sw::redis::UpdateType::EXIST:对应 Redis 的 XX (if eXists)语义,仅当键已经存在时才执行设置操作。
sw::redis::UpdateType::ALWAYS:默认行为,无条件设置(覆盖旧值)。
这个选项不能单独使用 ,必须搭配 set 的另一个重载版本,也就是带有过期时间参数的那个版本。过期时间参数设为 0s 表示不设置过期时间。
cpp
// 对应 Redis 的 SET key val NX
redis.set("key", "val", 0s, sw::redis::UpdateType::NOT_EXIST);
// 对应 Redis 的 SET key val XX
redis.set("key", "val", 0s, sw::redis::UpdateType::EXIST);
当使用了 NOT_EXIST 或 EXIST 选项时,set 函数的返回值 bool 变得有意义了:
返回
true:条件满足,设置成功。返回
false:条件不满足(例如 NX 时键已存在,或 XX 时键不存在),操作被忽略,Redis 返回空回复(NULL REPLY)。
mset 与 mget 函数
mset 函数原型:
cpp
// 使用初始化列表
template <typename T>
void mset(std::initializer_list<T> il);
// 使用迭代器范围
template <typename Input>
void mset(Input first, Input last);
使用示例:
方法一:初始化列表(键值对数量固定时)
cpp
redis.mset({
std::make_pair("key1", "111"),
std::make_pair("key2", "222"),
{"key3", "333"} // C++17 可以简化写法
});
方法二:迭代器(键值对动态生成时)
cpp
std::vector<std::pair<std::string, std::string>> kvs = {
{"key1", "111"},
{"key2", "222"},
{"key3", "333"}
};
redis.mset(kvs.begin(), kvs.end());
mget 函数原型:
cpp
// 使用迭代器范围指定键,结果写入输出迭代器
template <typename Input, typename Output>
void mget(Input first, Input last, Output output);
// 使用初始化列表指定键
template <typename T, typename Output>
void mget(std::initializer_list<T> il, Output output);
使用示例
方法一:初始化列表
cpp
std::vector<std::string> keys = {"key1", "key2", "key3"};
std::vector<OptionalString> values;
redis.mget(keys.begin(), keys.end(), std::back_inserter(values));
方法二:直接传初始化列表
cpp
std::vector<OptionalString> values;
redis.mget({"key1", "key2", "key3"}, std::back_inserter(values));
exists 函数
redis-plus-plus 的 exists 函数用于检查一个或多个键是否存在。它的返回值不是简单的 bool,而是存在的键的数量 (long long 类型)
cpp
// 检查单个键
long long exists(const StringView &key);
// 检查多个键(使用初始化列表)
long long exists(std::initializer_list<StringView> keys);
// 检查多个键(使用迭代器范围)
long long exists(Input first, Input last);
使用示例:
检查单个键:
cppauto count = redis.exists("my_key"); if (count > 0) { // 键存在 }批量检查多个键:
cppauto count = redis.exists({"key1", "key2", "key3"}); // 如果 key1 和 key3 存在,返回 2
虽然 exists 返回的是 long long,但在只检查一个键时,你可以像判断 bool 一样使用它。不过更好的做法是明确与 0 比较,以体现语义的准确性。这与 expire、setnx 等返回 bool 的函数不同,exists 的返回值在批量操作时承载了"存在数量"的信息
del 函数
del 函数用于删除一个或多个键,其返回值是实际被删除的键的数量 (long long 类型)。
与 exists 类似,del 也提供了三种重载形式,以支持不同的传参方式
cpp
// 删除单个键
long long del(const StringView &key);
// 删除迭代器范围内的多个键
template <typename Input>
long long del(Input first, Input last);
// 删除初始化列表中的多个键
template <typename T>
long long del(std::initializer_list<T> il);
使用示例:
删除单个键:
cpplong long deleted = redis.del("my_key"); // 如果 my_key 存在,deleted 为 1;不存在则为 0批量删除多个键:
cpp// 使用初始化列表 long long count = redis.del({"key1", "key2", "key3"}); // 使用迭代器范围 std::vector<std::string> keys = {"key1", "key2", "key3"}; long long count = redis.del(keys.begin(), keys.end());
返回值的含义 :del 返回的是成功删除的键数量,而不是命令是否执行成功
keys 函数
函数原型
在 redis-plus-plus 中,keys 并不返回容器,而是要求你提供一个输出迭代器:
cpp
template <typename Output>
void keys(const StringView &pattern, Output output);
基本用法
使用 std::back_inserter 将结果插入到 std::vector 中:
cpp
std::vector<std::string> result_keys;
auto inserter = std::back_inserter(result_keys);
redis.keys("user:*", inserter); // 查找所有以 "user:" 开头的键
这种设计是为了解耦:keys 只负责从 Redis 获取数据,而"数据存到哪里"由你通过迭代器决定,keys 不关心你用什么容器接收结果
expire 与 ttl 函数
expire 函数原型
cpp
#include <chrono>
bool expire(const StringView &key, long long timeout);
bool expire(const StringView &key, const std::chrono::seconds &timeout);
这两个版本在功能上完全等价。传 60 和传 std::chrono::seconds(60) 效果一样,都是让键在 60 秒后过期。但是传 std::chrono::seconds(60) 语义更加清晰。更加推荐的做法,是启用 10s、1min 等等字面常量:
cpp
#include <sw/redis++/redis++.h>
#include <chrono>
using namespace std::chrono_literals; // 启用 10s, 5min 等字面量
// ...
// 1. 给已存在的键设置 10 秒后过期
bool set = redis.expire("my_key", 10s);
if (set) {
// 设置成功
}
返回值:
返回
true:表示过期时间设置成功 (服务端返回1)。返回
false:表示键不存在 ,因此无法为其设置过期时间(服务端返回0)
ttl 函数原型
cpp
long long ttl(const StringView &key);
返回值:
返回值是一个 long long 类型的整数,需要根据其数值来判断键的状态:
大于 0 :键存在,且返回值就是剩余的秒数。
等于 -1 :键存在,但没有设置过期时间(永久有效)。
等于 -2 :键不存在。
type 函数
函数原型
cpp
std::string type(const StringView &key);
它的返回值是一个 std::string,表示键所存储值的数据类型
使用示例:
cpp
std::string val = redis.type("my_key");
if (val == "string") {
// 处理字符串类型
} else if (val == "none") {
// 键不存在
}
3. 3 list 类型相关函数
blpop 函数
函数原型:
cpp
OptionalStringPair blpop(const StringView &key, const std::chrono::seconds &timeout);
template <typename Input>
OptionalStringPair blpop(Input first, Input last, const std::chrono::seconds &timeout);
OptionalStringPair blpop(std::initializer_list<StringView> keys, const std::chrono::seconds &timeout);
返回值:
返回类型是
OptionalStringPair,这是一个std::optional<std::pair<std::string, std::string>>的别名。当操作成功时,你可以通过op->first获取元素来自哪个列表键 ,通过op->second获取弹出的元素值 。如果超时且没有数据,返回空的optional。
使用示例:
cpp
#include <sw/redis++/redis++.h>
#include <chrono>
#include <iostream>
using namespace std::chrono_literals;
auto redis = sw::redis::Redis("tcp://127.0.0.1:6379");
// 从 list1 或 list2 的头部弹出元素,最多阻塞 5 秒
auto op = redis.blpop({"list1", "list2"}, 5s);
if (op) {
std::cout << "元素来自: " << op->first
<< ", 值为: " << op->second << std::endl;
} else {
std::cout << "超时,没有数据" << std::endl;
}
3.4 set 类型相关函数
smembers 函数
函数原型:
template <typename Output>
void smembers(const StringView &key, Output output);
使用示例:
你需要先创建一个容器(推荐 std::vector<std::string> 或 std::set<std::string>),然后用 std::back_inserter (使用 vector 时)或 std::inserter (使用 set 或 vector 时,因为 set 没有 push_back 方法,但是有 insert 方法)创建迭代器传入
#include <sw/redis++/redis++.h>
#include <vector>
#include <iterator>
auto redis = sw::redis::Redis("tcp://127.0.0.1:6379");
std::vector<std::string> members;
redis.smembers("my_set", std::back_inserter(members));
// 遍历结果
for (const auto &m : members) {
std::cout << m << std::endl;
}
// 如果希望结果自动去重且有序,可以使用 std::set 配合 std::inserter
std::set<std::string> members;
redis.smembers("my_set", std::inserter(members, members.end()));
3.5 zset 类型相关函数
zrange 函数
函数原型:
// 只返回成员
template <typename Output>
void zrange(const StringView &key, long long start, long long stop, Output output);
// 返回成员及其分数(当 Output 是 pair<string, double> 的迭代器时自动启用)
template <typename Output>
void zrange(const StringView &key, long long start, long long stop, Output output);
redis-plus-plus 会根据你传入的输出迭代器类型,决定发送给 Redis 的原始命令是否带 WITHSCORES 选项
使用示例:
只获取成员(不带分数)
cppstd::vector<std::string> members; redis.zrange("my_zset", 0, -1, std::back_inserter(members)); // 对应命令: ZRANGE my_zset 0 -1获取成员及其分数
cppstd::vector<std::pair<std::string, double>> res_with_score; redis.zrange("my_zset", 0, -1, std::back_inserter(res_with_score)); // 对应命令: ZRANGE my_zset 0 -1 WITHSCORES当你把
std::back_inserter指向一个std::pair<std::string, double>的容器时,redis-plus-plus 会自动加上WITHSCORES选项,返回的每个元素都带有对应的分数