1. 适配C++语言的redis client库
使用redis官方推荐的C++语言 redis client:redis_plus_plus官网
redis_plus_plus特性如下:
This is a C++ client library for Redis. It's based on hiredis,
and is compatible with C++ 17, C++ 14, and C++ 11.
NOTE: I'm not a native speaker. So if the documentation is unclear,
please feel free to open an issue or pull request. I'll response ASAP.
Features
Most commands for Redis.
Connection pool.
Redis scripting.
Thread safe unless otherwise stated.
Redis publish/subscribe.
Redis pipeline.
Redis transaction.
Redis Cluster.
Redis Sentinel.
STL-like interfaces.
Generic command interface.
Redis Stream.
Redlock.
Redis ACL.
TLS/SSL support.
2. Linux下编译安装redis_plus_plus
参照redis_plus_plus官方readme的“Installation”部分进行安装。
编译环境为: Ubuntu 18.04,gcc 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)。
redis_plus_plus依赖hiredis( version >= v0.12.1),故编译顺序为:先编译hiredis,后编译redis_plus_plus。
2.1 编译hiredis
官方指导教程明确说明“不要安装多个版本hiredis”!已经安装过,请自行搜索移除方法。
编译最新版,安装到默认路径(/usr/local),整个编译过程在1分钟内完成
$ git clone https://github.com/redis/hiredis.git
$ cd hiredis
$ make
$ sudo make install
make install只做了如下简单的操作 ,想移除hiredis的安装只需要做如下操作的逆操作即可。
2.2 编译redis_plus_plus
默认编译生成动态库和静态库,使用C++17标准(注意:你的应用程序也必须使用C++17版本,即redis_plus_plus库和应用程序需要使用相同的C++语言标准)。redis_plus_plus支持C++11、C++14、C++17语言标准。
如果cmake没有安装,可以自行安装:$ sudo apt install cmake
整个过程git clone时间比较久,其余操作在2分钟内完成。
$ git clone https://github.com/sewenew/redis-plus-plus.git
$ cd redis-plus-plus
$ mkdir build
$ cd build
$ cmake -DREDIS_PLUS_PLUS_CXX_STANDARD=17 .. #使用C++17版本,C++11则修改为11,C++14则修改为14
$ make
$ sudo make install
make install做了如下操作:
dog@dog:build$ sudo make install
[ 47%] Built target redis++
[ 94%] Built target redis++_static
[100%] Built target test_redis++
Install the project...
-- Install configuration: "Release"
-- Installing: /usr/local/lib/libredis++.a
-- Installing: /usr/local/lib/libredis++.so.1.3.3
-- Installing: /usr/local/lib/libredis++.so.1
-- Installing: /usr/local/lib/libredis++.so
-- Set runtime path of "/usr/local/lib/libredis++.so.1.3.3" to ""
-- Installing: /usr/local/share/cmake/redis++/redis++-targets.cmake
-- Installing: /usr/local/share/cmake/redis++/redis++-targets-release.cmake
-- Installing: /usr/local/include/sw/redis++/cmd_formatter.h
-- Installing: /usr/local/include/sw/redis++/command.h
-- Installing: /usr/local/include/sw/redis++/command_args.h
-- Installing: /usr/local/include/sw/redis++/command_options.h
-- Installing: /usr/local/include/sw/redis++/connection.h
-- Installing: /usr/local/include/sw/redis++/connection_pool.h
-- Installing: /usr/local/include/sw/redis++/errors.h
-- Installing: /usr/local/include/sw/redis++/pipeline.h
-- Installing: /usr/local/include/sw/redis++/queued_redis.h
-- Installing: /usr/local/include/sw/redis++/queued_redis.hpp
-- Installing: /usr/local/include/sw/redis++/redis++.h
-- Installing: /usr/local/include/sw/redis++/redis.h
-- Installing: /usr/local/include/sw/redis++/redis.hpp
-- Installing: /usr/local/include/sw/redis++/redis_cluster.h
-- Installing: /usr/local/include/sw/redis++/redis_cluster.hpp
-- Installing: /usr/local/include/sw/redis++/reply.h
-- Installing: /usr/local/include/sw/redis++/sentinel.h
-- Installing: /usr/local/include/sw/redis++/shards.h
-- Installing: /usr/local/include/sw/redis++/shards_pool.h
-- Installing: /usr/local/include/sw/redis++/subscriber.h
-- Installing: /usr/local/include/sw/redis++/transaction.h
-- Installing: /usr/local/include/sw/redis++/utils.h
-- Installing: /usr/local/include/sw/redis++/tls.h
-- Installing: /usr/local/include/sw/redis++/cxx_utils.h
-- Installing: /usr/local/share/cmake/redis++/redis++-config.cmake
-- Installing: /usr/local/share/cmake/redis++/redis++-config-version.cmake
-- Installing: /usr/local/lib/pkgconfig/redis++.pc
3. 测试redis_plus_plus
使用官方的例子进行测试。
3.1 启动redis_server
$ redis-server
查看redis_server状态:
$ systemctl restart redis-server # 重启
$ systemctl stop redis-server # 停止
$ systemctl start redis-server # 启动
3.2 测试代码
#include <sw/redis++/redis++.h>
#include <iostream>
#include <unordered_set>
#include <algorithm>
using namespace std;
using namespace sw::redis;
// cout << vector
template <typename T>
std::ostream &operator<<(std::ostream &out, const std::vector<T> &v)
{
if (!v.empty())
{
out << '[';
std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
out << "\b\b]"; // 删除末尾的", "
}
return out;
}
// cout << unordered_map
template <typename T, typename U>
std::ostream &operator<<(std::ostream &out, const std::unordered_map<T, U> &umap)
{
out << '[';
for (auto item : umap)
{
out << "(" << item.first << "," << item.second << "),";
}
out << "\b]"; // 删除末尾的","
return out;
}
// cout << unorderd_set
template <typename T>
std::ostream &operator<<(std::ostream &out, const std::unordered_set<T> &uset)
{
out << '(';
for (auto item : uset)
{
cout << item << ",";
}
out << "\b)"; // 删除末尾的","
return out;
}
int main()
{
try
{
// Create an Redis object, which is movable but NOT copyable.
auto redis = Redis("tcp://127.0.0.1:6379");
/// ***** STRING commands *****
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.
/// ***** LIST commands *****
// std::vector<std::string> to Redis LIST.
std::vector<std::string> vec = {"a", "b", "c"};
redis.rpush("list", vec.begin(), vec.end());
// std::initializer_list to Redis LIST.
redis.rpush("list", {"a", "b", "c"});
// Redis LIST to std::vector<std::string>.
vec.clear();
redis.lrange("list", 0, -1, std::back_inserter(vec));
cout << "list: " << vec << endl;
/// ***** HASH commands *****
redis.hset("hash", "field", "val");
// Another way to do the same job.
redis.hset("hash", std::make_pair("field", "val"));
// std::unordered_map<std::string, std::string> to Redis HASH.
std::unordered_map<std::string, std::string> m = {
{"field1", "val1"},
{"field2", "val2"}};
redis.hmset("hash", m.begin(), m.end());
// Redis HASH to std::unordered_map<std::string, std::string>.
m.clear();
redis.hgetall("hash", std::inserter(m, m.begin()));
cout << "hash:" << m << endl;
// Get value only.
// NOTE: since field might NOT exist, so we need to parse it to OptionalString.
std::vector<OptionalString> vals;
redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals));
/// ***** SET commands *****
redis.sadd("set", "m1");
// std::unordered_set<std::string> to Redis SET.
std::unordered_set<std::string> set = {"m2", "m3"};
redis.sadd("set", set.begin(), set.end());
// std::initializer_list to Redis SET.
redis.sadd("set", {"m2", "m3"});
// Redis SET to std::unordered_set<std::string>.
set.clear();
redis.smembers("set", std::inserter(set, set.begin()));
cout << "set:" << set << endl;
if (redis.sismember("set", "m1"))
{
std::cout << "m1 exists" << std::endl;
} // else NOT exist.
/// ***** SORTED SET commands *****
redis.zadd("sorted_set", "m1", 1.3);
// std::unordered_map<std::string, double> to Redis SORTED SET.
std::unordered_map<std::string, double> scores = {
{"m2", 2.3},
{"m3", 4.5}};
redis.zadd("sorted_set", scores.begin(), scores.end());
// Redis SORTED SET to std::vector<std::pair<std::string, double>>.
// NOTE: The return results of zrangebyscore are ordered, if you save the results
// in to `std::unordered_map<std::string, double>`, you'll lose the order.
std::vector<std::pair<std::string, double>> zset_result;
redis.zrangebyscore("sorted_set",
UnboundedInterval<double>{}, // (-inf, +inf)
std::back_inserter(zset_result));
// Only get member names:
// pass an inserter of std::vector<std::string> type as output parameter.
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));
// Get both member names and scores:
// pass an back_inserter of std::vector<std::pair<std::string, double>> as output parameter.
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));
/// ***** SCRIPTING commands *****
// 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());
/// ***** Pipeline *****
// 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));
/// ***** Transaction *****
// 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));
/// ***** Generic Command Interface *****
// 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 Cluster *****
// Create a RedisCluster object, which is movable but NOT copyable.
auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000");
// RedisCluster has similar interfaces as Redis.
redis_cluster.set("key", "value");
val = redis_cluster.get("key");
if (val)
{
std::cout << *val << std::endl;
} // else key doesn't exist.
// Keys with hash-tag.
redis_cluster.set("key{tag}1", "val1");
redis_cluster.set("key{tag}2", "val2");
redis_cluster.set("key{tag}3", "val3");
std::vector<OptionalString> hash_tag_res;
redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"},
std::back_inserter(hash_tag_res));
}
catch (const Error &e)
{
// Error handling.
}
return 0;
}
3.3 构建redis client app
使用静态库:
g++ -std=c++17 -o app main.cpp /path/to/your/libredis++.a /path/to/your/libhiredis.a -pthread
使用动态库:
$ g++ -std=c++17 -o app main.cpp -lredis++ -lhiredis -pthread
运行提示找不到“libredis++.so.1 :
$ ./app
./app: error while loading shared libraries: libredis++.so.1: cannot open shared object file: No such file or directory
因为找不到libredis++.so.1的路径,需要将其路径添加到LD_LIBRARY_PATH中。redis_plus_plus库默认安装路径如下:
在“~/.bashrc”末尾为LD_LIBRARY_PATH添加“/usr/local/lib”路径即可:
保存“~/.bashrc”修改,关闭terminal终端,重启打开新terminal终端,在新终端运行app: