今天玩一下 redis-plus-plus 这个东西。这个是一个基于 c++ 的 redis 的客户端库。可以使我们的程序作为一个客户端连接到我们的 redis 服务器,对服务器进行操作。
安装
redis-plus-plus 是基于 hiredis 的,所以要先安装 hiredis。可以直接使用包管理器。
sudo apt install libhiredis-dev
然后我们下载一下 redis-plus-plus 的源码。
git clone https://github.com/sewenew/redis-plus-plus.git
然后我们找到 redis-plus-plus 的文件夹下面,以此执行以下几个命令。
mkdir build && cd build
cmake ..
make
sudo make install
安装成功后,我们可以在 usr/local/include 中多出 sw ⽬录, 并且内部包含 redis-plus-plus 的⼀系列头⽂件. 会在 usr/local/lib 中多出⼀系列 libredis 库⽂件,后面我们在创建项目的时候要用到。
然后我们开启一个 Visual Studio 的 Linux 项目,使用 ssh 连接到咱们的虚拟机或者 WSL 。打开项目属性->链接器->输入->附加依赖项,在里面加入我们的库的路径,分别是:
/usr/local/lib/libredis++.a;
/lib/x86_64-linux-gnu/libhiredis.a;
系统不同库文件的路径也可能会不一样,我们需要找到自己的库文件在哪里。加入之后我们就可以 #include <sw/redis++/redis++.h> 包含头文件正式使用关于 redis-plus-plus 的功能啦。
使用
下面给出一些常用的 API。
auto redis = Redis("tcp://127.0.0.1:6379");
auto res = redis.ping();
std::cout << res << std::endl;
连接到 redis 服务端,字符串中的IP地址和端口要换成自己的服务端所在的地址。
ping() 可以测试我们的连接是否成功,如果成功,我们会收到一个 PONG ,它会以返回值的形式存在 res 中。
redis.set("key", "val");
auto val = redis.get("key"); // val is of type OptionalString. See 'API Reference' section for details.
if (val) {
// Dereference val to get the returned value of std::string type.
std::cout << *val << std::endl;
} // else key doesn't exist.
set 函数可以设置一个 string 类型的值到数据库中,第一个参数为 key,第二个是对应的 value。
get 函数可以从数据库中获得 key 对应的 value,该值会以返回值的形式返回。
std::vector<std::string> vec = {"a", "b", "c"};
redis.rpush("list", vec.begin(), vec.end());
redis.rpush("list", {"a", "b", "c"});
vec.clear();
redis.lrange("list", 0, -1, std::back_inserter(vec));
上文为给 list 对象插入内容的两种方式,rpush 为从右侧插入,lpush 为左插
第一种:使用 vector 容器。第一个参数为 key,第二第三个参数为容器的两个迭代器,表示插入的区间。
第二种:直接插入一个右值,第一个参数为 key,第二个参数是要插入的右值。
最后一行内容为把 list 对象中的元素存入到 vector 容器中
第一个参数为对应的 key,第二第三个参数为 list 中元素的下标,表示要存入的区间,第三个参数为 -1 表示,此区间直到对象末尾。
最后一个参数是一种用于为容器添加元素的迭代器,上文中表示的是尾插的迭代器。
redis.hset("hash", "field", "val");
// Another way to do the same job.
redis.hset("hash", std::make_pair("field", "val"));
两种方式插入哈希类型,第一种是直接插入,第二种是使用 pair 对象进行插入。
std::unordered_map<std::string, std::string> m = {
{"field1", "val1"},
{"field2", "val2"}
};
redis.hmset("hash", m.begin(), m.end());
使用 map 批量插入。
m.clear();
redis.hgetall("hash", std::inserter(m, m.begin()));
把 hash 对象中所有的键和值都存到 map 中。
第一个参数为 hash 对象对应的 key,第三个参数为插入的迭代器。
std::vector<OptionalString> vals;
redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals));
把 hash 对象中指定的键对应的值存到 map 中。
第一个参数为 hash 对象对应的 key,第二个参数为需要存储的值对应的 keys,第三个参数为插入的迭代器。
redis.sadd("set", "m1");
在 set 对象中插入一个值。
std::unordered_set<std::string> set = {"m2", "m3"};
redis.sadd("set", set.begin(), set.end());
redis.sadd("set", {"m2", "m3"});
批量插入的两种方法:使用 unordered_set 容器和直接插入。
set.clear();
redis.smembers("set", std::inserter(set, set.begin()));
把 set 容器中的元素存入到 set 容器中。
if (redis.sismember("set", "m1")) {
std::cout << "m1 exists" << std::endl;
} // else NOT exist.
判断数据库中对应的 set 对象中是否有该元素,如果有则返回 true 否则返回 false。
redis.zadd("sorted_set", "m1", 1.3);
在数据库中的 sorted set 对象中插入元素,第一个参数为 key,第二个参数为插入的元素,第三个参数为插入的元素的分数。
std::unordered_map<std::string, double> scores = {
{"m2", 2.3},
{"m3", 4.5}
};
redis.zadd("sorted_set", scores.begin(), scores.end());
在数据库中的 sorted set 对象中,使用 unorder_map 容器批量插入元素。
std::vector<std::pair<std::string, double>> zset_result;
redis.zrangebyscore("sorted_set",
UnboundedInterval<double>{}, // (-inf, +inf)
std::back_inserter(zset_result));
将 sorted set 对象中的元素存储到 vector 容器中。其结果是有序的,如果保存到 unordered_map 中 会失去顺序。
第一个参数为 key,第二个参数为一个区间(上文中表示的为正无穷到负无穷),第三个参数为一个插入的迭代器。
std::vector<std::string> without_score;
redis.zrangebyscore("sorted_set",
BoundedInterval<double>(1.5, 3.4, BoundType::CLOSED), // [1.5, 3.4]
std::back_inserter(without_score));
此种方法只会吧元素的内容保存到队列中,而不会保存分数。第二个参数表示为 1.5 到 3.4 的一个闭区间。
std::vector<std::pair<std::string, double>> with_score;
redis.zrangebyscore("sorted_set",
BoundedInterval<double>(1.5, 3.4, BoundType::LEFT_OPEN), // (1.5, 3.4]
std::back_inserter(with_score));
上文中第二个参数表示的是一个 1.5 到 3.4 的左开右闭区间。
// Script returns a single element.
auto num = redis.eval<long long>("return 1", {}, {});
// Script returns an array of elements.
std::vector<std::string> nums;
redis.eval("return {ARGV[1], ARGV[2]}", {}, {"1", "2"}, std::back_inserter(nums));
// mset with TTL
auto mset_with_ttl_script = R"(
local len = #KEYS
if (len == 0 or len + 1 ~= #ARGV) then return 0 end
local ttl = tonumber(ARGV[len + 1])
if (not ttl or ttl <= 0) then return 0 end
for i = 1, len do redis.call("SET", KEYS[i], ARGV[i], "EX", ttl) end
return 1
)";
// Set multiple key-value pairs with TTL of 60 seconds.
auto keys = {"key1", "key2", "key3"};
std::vector<std::string> args = {"val1", "val2", "val3", "60"};
redis.eval<long long>(mset_with_ttl_script, keys.begin(), keys.end(), args.begin(), args.end());
上文是一些脚本编程的使用方法。
// Create a pipeline.
auto pipe = redis.pipeline();
// Send mulitple commands and get all replies.
auto pipe_replies = pipe.set("key", "value")
.get("key")
.rename("key", "new-key")
.rpush("list", {"a", "b", "c"})
.lrange("list", 0, -1)
.exec();
// Parse reply with reply type and index.
auto set_cmd_result = pipe_replies.get<bool>(0);
auto get_cmd_result = pipe_replies.get<OptionalString>(1);
// rename command result
pipe_replies.get<void>(2);
auto rpush_cmd_result = pipe_replies.get<long long>(3);
std::vector<std::string> lrange_cmd_result;
pipe_replies.get(4, back_inserter(lrange_cmd_result));
如上是 redis 管道操作的相关代码:
使用 pipeline() 函数创建一个管道对象 pipe。然后我们可以直接对管道对象调用 前面的命令函数,管道会为我们保存这些命令,在我们调用 exec() 函数时统一发送给 redis 服务端。服务端在处理完这些命令后也会统一给我们进行回复。这样可以减少客户端与服务端的交互次数,节省网络开销。命令执行结果我们存储在了 pipe_replies 对象中,我们可以使用 get() 函数加数据类型,使用类似于数组的下标的传参方式获取对应的命令的结果,也可以使用 get() 函数加数组下标传参加插入迭代器的方式把结果存入容器中。
// Create a transaction.
auto tx = redis.transaction();
// Run multiple commands in a transaction, and get all replies.
auto tx_replies = tx.incr("num0")
.incr("num1")
.mget({"num0", "num1"})
.exec();
// Parse reply with reply type and index.
auto incr_result0 = tx_replies.get<long long>(0);
auto incr_result1 = tx_replies.get<long long>(1);
std::vector<OptionalString> mget_cmd_result;
tx_replies.get(2, back_inserter(mget_cmd_result));
哎?这玩意用着和管道怎么这么像,那事务和管道有什么区别呢?
我不知道!
看下一趴吧
// There's no *Redis::client_getname* interface.
// But you can use *Redis::command* to get the client name.
val = redis.command<OptionalString>("client", "getname");
if (val) {
std::cout << *val << std::endl;
}
// Same as above.
auto getname_cmd_str = {"client", "getname"};
val = redis.command<OptionalString>(getname_cmd_str.begin(), getname_cmd_str.end());
// There's no *Redis::sort* interface.
// But you can use *Redis::command* to send sort the list.
std::vector<std::string> sorted_list;
redis.command("sort", "list", "ALPHA", std::back_inserter(sorted_list));
// Another *Redis::command* to do the same work.
auto sort_cmd_str = {"sort", "list", "ALPHA"};
redis.command(sort_cmd_str.begin(), sort_cmd_str.end(), std::back_inserter(sorted_list));
上文为通用命令的使用方法,在 Redis 客户端库的使用场景中,通用命令接口(Generic Command Interface)是一种允许用户以灵活、通用的方式执行 Redis 命令的机制。通常,客户端库会为一些常见的 Redis 命令(如 GET
、SET
、HSET
等)提供专门的方法。然而,Redis 支持的命令众多,客户端库不可能为每一个命令都提供特定的封装方法。通用命令接口就是为解决这一问题而设计的,它允许用户直接发送任意的 Redis 命令,而不依赖于库中预定义的特定方法。
我么可以使用 command() 函数在传参中传入我们要执行的命令,并且可以通过两种方式获得命令执行的结果。
第一种:在 command 函数后添加尖括号,里面为返回值类型,通过返回值获得执行结果。
第二种:在 command 函数添加最后一个参数为插入迭代器,把执行结果存入到容器中。
以上是本期的全部内容,下周同一时间期待在此与大家见面。