redis-plus-plus

repository·master·Indexed 24 days ago

https://github.com/sewenew/redis-plus-plus

A high-level C++ client library for Redis and RESP-compatible stores (such as Valkey, DragonflyDB, and KeyDB) built on top of hiredis. It supports C++11 and later, providing features such as connection pooling, coroutine support, STL-like interfaces, Redis Cluster, Redis Sentinel, and TLS/SSL connectivity.

Tokens
36.8K
Snippets
80
Records
165
Agent score
83%

What's inside redis-plus-plus

  1. Overview of redis-plus-plus

    master
    redis-plus-plus is a C++ client library for Redis based on hiredis. It is compatible with C++11 and later versions. While designed for Redis, it is also compatible with other key-value stores that support the RESP (REdis Serialization Protocol), such as Valkey, DragonflyDB, and KeyDB.
  2. Understand Redis return types and mapping

    master

    The library parses Redis protocol replies into C++ types. The mapping depends on the specific command's expected reply:

    Return TypeRedis Reply TypeExample Commands
    voidStatus Reply (always "OK")RENAME, SETEX
    std::stringStatus Reply (not always "OK") or Bulk StringPING, INFO
    boolInteger Reply (0 or 1)EXPIRE, HSET
    long longInteger ReplyDEL, APPEND
    doubleBulk String (representing a double)INCRBYFLOAT, ZINCRBY
    std::pairArray Reply (exactly 2 elements)BLPOP
    std::tupleArray Reply (fixed length > 2)BZPOPMAX
    output iteratorArray Reply (dynamic length)MGET, LRANGE
    Optional<T>Any type T that might be NULLGET, LPOP, BLPOP
    Variant<Args...>Reply that might be several different typesMEMORY STATS
    STL containerGeneral Array ReplyCONFIG GET
  3. Features of redis-plus-plus

    master

    The library provides a wide range of Redis capabilities and C++ features:

    • Core Redis Support: Most Redis commands, Redis scripting, Redis Stream, Redis Modules, and Redis ACL.
    • Advanced Redis Patterns: Connection pooling, Pipeline, Transaction, Redlock, and Publish/Subscribe.
    • High Availability: Support for Redis Cluster and Redis Sentinel.
    • Connectivity: TLS/SSL support.
    • Programming Models: Sync and Async interfaces, Coroutine support, and STL-like interfaces.
    • Safety: Thread-safe operations (unless otherwise stated) and a Generic command interface.
  4. Limitations of Variant support

    master

    When using Variant, observe these two constraints:

    1. No duplicates: Type arguments cannot have duplicate items (e.g., Variant<double, long long, double> is invalid).
    2. Type ordering: double must be placed before std::string. Because double replies are actually string replies in the protocol, the parser tries types from left to right. If std::string comes first, the reply will always be parsed as a string and never reach the double type.
  5. Understand command overloads via output iterators

    master

    The library uses the type of the provided output iterator to automatically decide which Redis command options to include. For example:

    • zrange with an iterator of std::string sends ZRANGE.
    • zrange with an iterator of std::pair<std::string, double> sends ZRANGE with the WITHSCORES option.
    • georadius with an iterator of std::tuple<std::string, double, std::string> sends GEORADIUS with WITHDIST and WITHHASH options.
    // Automatically includes WITHSCORES
    std::vector<std::pair<std::string, double>> res_with_score;
    redis.zrange("list", 0, -1, std::back_inserter(res_with_score));
  6. Implement Publish/Subscribe with Subscriber

    master

    To use Pub/Sub, use Redis::publish to send messages and Redis::subscriber() to create a Subscriber object for receiving them.

    Key Concepts:

    • Redis::publish picks a connection from the pool. Multiple publishes might use different connections.
    • Subscriber maintains its own dedicated connection (not from the pool). It inherits the ConnectionOptions from the Redis object used to create it.
    • Thread Safety: Subscriber is NOT thread-safe. You must synchronize access manually if used in multi-threaded environments.
    • Exception Handling: If a Subscriber throws an exception (other than ReplyError or TimeoutError), it is no longer usable and must be recreated.

    Callbacks: You can register callbacks to handle different message types:

    • on_message: For MESSAGE type.
    • on_pmessage: For PMESSAGE type.
    • on_meta: For meta-messages (SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE).
  7. Share an EventLoop between AsyncRedis objects

    master

    By default, AsyncRedis and AsyncRedisCluster create their own dedicated event loop thread. To optimize resource usage, you can share a single std::shared_ptr<EventLoop> across multiple AsyncRedis or AsyncRedisCluster instances.

    Warning: You must ensure the event_loop object outlives all AsyncRedis and AsyncRedisCluster objects that use it.

    auto event_loop = std::make_shared<EventLoop>();
    
    auto redis = AsyncRedis(connection_opts, pool_opts, loop);
    
    auto cluster = AsyncRedisCluster(connection_opts, pool_opts, Role::MASTER, loop);
  8. Understand StringView and string parameter types

    master

    Most methods requiring string parameters use StringView.

    • If you build with C++17 (default), StringView is an alias for std::string_view.
    • If you build with C++11 or C++14 (via -DREDIS_PLUS_PLUS_CXX_STANDARD=11), redis-plus-plus provides a custom StringView implementation.

    You can pass std::string, c-style strings (const char*), or a mix of both directly to any method expecting a StringView.

    // Pass c-style string to StringView.
    redis.hset("key", "field", "value");
    
    // Pass std::string to StringView.
    std::string key = "key";
    std::string field = "field";
    std::string val = "val";
    redis.hset(key, field, val);
    
    // Mix std::string and c-style string.
    redis.hset(key, field, "value");
  9. Handle NULL replies with Optional<T>

    master

    When a Redis command can return a NULL REPLY (like GET for a non-existent key), the library returns an Optional<T>.

    • In C++17, Optional<T> is an alias for std::optional<T>.
    • In C++11/14, it is a custom implementation.

    You can check if a value exists by evaluating the Optional object in a boolean context and access the value using the dereference operator *.

    // Or just: auto val = redis.get("key");
    Optional<std::string> val = redis.get("key");
    
    // Optional<T> has a conversion to bool.
    if (val) {
        // Key exists. Dereference val to get the string result.
        std::cout << *val << std::endl;
    } else {
        // Redis server returns a NULL Bulk String Reply.
        std::cout << "key doesn't exist." << std::endl;
    }
    
    std::vector<Optional<std::string>> values;
    redis.mget({"key1", "key2", "key3"}, std::back_inserter(values));
    for (const auto &val : values) {
        if (val) {
            // Key exist, process the value.
        }
    }
  10. Implement Check-and-Set (CAS) using WATCH

    master

    To implement optimistic locking (Check-and-Set) using the WATCH command, you must use a Redis object that shares the same connection as your Transaction object. This is achieved via tx.redis().

    Because WATCH relies on the connection state, you cannot get results from commands inside a transaction until .exec() is called. Using the Redis object returned by tx.redis() allows you to send WATCH and GET commands and receive immediate results within the same connection.

    If a WatchError is caught, it means the watched key was modified by another client, and you should retry the transaction loop.

    auto redis = Redis("tcp://127.0.0.1");
    auto tx = redis.transaction();
    auto r = tx.redis(); // Shares connection with tx
    
    while (true) {
        try {
            r.watch("key");
            auto val = r.get("key");
            auto num = val ? std::stoi(*val) : 0;
            ++num;
    
            auto replies = tx.set("key", std::to_string(num)).exec();
            assert(replies.size() == 1 && replies.get<bool>(0) == true);
            break;
        } catch (const WatchError &err) {
            continue; // Retry
        } catch (const Error &err) {
            throw; // Transaction is invalid
        }
    }
  11. Handle Redis exceptions and connection errors

    master

    The Redis and RedisCluster objects are exception-safe and can be reused even if a connection is broken (e.g., an IoError is thrown); the client will attempt to reconnect automatically on the next command.

    Important Exception Rule: If a Pipeline, Transaction, or Subscriber throws an exception, you must destroy that object and create a new one. You cannot reuse them after an exception.

    try {
        redis.set("key", "value");
        // Wrong type error
        redis.lpush("key", {"a", "b", "c"});
    } catch (const ReplyError &err) {
        // WRONGTYPE Operation against a key holding the wrong kind of value
        std::cout << err.what() << std::endl;
    } catch (const TimeoutError &err) {
        // reading or writing timeout
    } catch (const ClosedError &err) {
        // the connection has been closed.
    } catch (const IoError &err) {
        // there's an IO error on the connection.
    } catch (const Error &err) {
       // other errors
    }
  12. Use Pipeline to reduce RTT

    master

    The Pipeline class allows you to batch multiple Redis commands to reduce Round Trip Time (RTT).

    • Creation: Use Redis::pipeline() to create a pipeline. By default, this creates a new connection (not from the pool), which can be expensive.
    • Sending Commands: Methods on the Pipeline object (like set, incr, etc.) do not return the reply; they return the Pipeline object itself, allowing for method chaining.
    • Execution: Commands are only sent to Redis when Pipeline::exec() is called. This method returns a QueuedReplies object containing all results.
    • Discarding: Use Pipeline::discard() to cancel all queued commands without sending them.
    • Thread Safety: Pipeline is NOT thread-safe. Manual synchronization is required if used across threads.
    • Exceptions: If a method throws an exception other than ReplyError, the Pipeline enters an invalid state and must be destroyed and recreated.