[C++] redis client: redis_plus_plus

1. Redis client library adapted to C++ language

Use the C++ language officially recommended by redis redis client: redis_plus_plus official website

redis_plus_plus features are as follows:

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 respond 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 ACLs.
    TLS/SSL support.

2. Compile and install redis_plus_plus under Linux

Refer to the “Installation” section of redis_plus_plus official readme to install.

The compilation environment is: Ubuntu 18.04, gcc 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04).

redis_plus_plus depends on hiredis (version >= v0.12.1), so the compilation order is: first compile hiredis, then compile redis_plus_plus.

2.1 Compile hiredis

The official tutorial clearly states “Do not install multiple versions of hiredis”! It has already been installed, please search for the removal method by yourself.

Compile the latest version and install it to the default path (/usr/local). The entire compilation process is completed within 1 minute

$ git clone
https://github.com/redis/hiredis.git

$ cd hiredis

$ make

$ sudo make install

make install only does the following simple operations. If you want to remove the installation of hiredis, you only need to do the reverse operation of the following operations.

2.2 Compile redis_plus_plus

The default compilation generates dynamic and static libraries, using the C++17 standard (note: your application must also use the C++17 version, that is, the redis_plus_plus library and the application need to use the same C++ language standard). redis_plus_plus supports C++11, C++14, C++17 language standards.

If cmake is not installed, you can install it yourself: $ sudo apt install cmake

The whole process of git clone takes a long time, and the rest of the operations are completed within 2 minutes.

$ 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 .. #Use C++17 version, C++11 is modified to 11, C++14 is modified to 14

$ make

$ sudo make install

make install did the following:

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. Test redis_plus_plus

Use the official example for testing.

3.1 Start redis_server

$ redis-server

View redis_server status:

$ systemctl restart redis-server # restart

$ systemctl stop redis-server # stop

$ systemctl start redis-server # start

3.2 Test code

#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 & amp;operator<<(std::ostream & amp;out, const std::vector<T> & amp;v)
{
    if (!v. empty())
    {
        out << '[';
        std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
        out << "\b\b]"; // delete ", " at the end
    }
    return out;
}
 
// cout << unordered_map
template <typename T, typename U>
std::ostream & amp;operator<<(std::ostream & amp;out, const std::unordered_map<T, U> & amp;umap)
{
    out << '[';
    for (auto item : umap)
    {
        out << "(" << item.first << "," << item.second << "),";
    }
    out << "\b]"; // delete "," at the end
 
    return out;
}
 
// cout << unordered_set
template <typename T>
std::ostream & amp;operator<<(std::ostream & amp;out, const std::unordered_set<T> & amp;uset)
{
    out << '(';
    for (auto item : uset)
    {
        cout << item << ",";
    }
    out << "\b)"; // delete "," at the end
 
    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 Build redis client app

Use a static library:

g++ -std=c++17 -o app main.cpp /path/to/your/libredis++ + .a /path/to/your/libhiredis.a -pthread

Use a dynamic library:

$ g++ -std=c++17 -o app main.cpp -lredis++ -lhiredis -pthread

The running prompt cannot find “libredis++.so.1:

$ ./app

./app: error while loading shared libraries: libredis + + .so.1: cannot open shared object file: No such file or directory

Because the path of libredis++.so.1 cannot be found, its path needs to be added to LD_LIBRARY_PATH. The default installation path of the redis_plus_plus library is as follows:

Add the “/usr/local/lib” path to LD_LIBRARY_PATH at the end of “~/.bashrc”:

Save the “~/.bashrc” modification, close the terminal terminal, restart and open a new terminal terminal, and run the app in the new terminal: