1. 适配C++语言的redis client库

使用redis官方推荐的C++语言 redis clientredis_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:

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐