Redis Cluster Client¶
uRedis includes a production-grade async Redis Cluster router built on top of RedisClient:
- CRC16 slot hashing
- Full MOVED / ASK redirection support
- Automatic
CLUSTER SLOTSdiscovery - Individual async
RedisClientper node - Per-node async connection pool (MPMC + async mutex)
- Lazy connection buildup with optional prewarm during
connect() - Zero extra dependencies
Cluster mode is sharded across 16384 slots.
RedisClusterClient selects the correct node for a given key and returns a regular RedisClient
or executes the command through the node’s connection pool, depending on API used.
Architecture¶
-
RedisClusterClient -
connect()– runs initialCLUSTER SLOTSdiscovery (also prewarms pools up tomax_connections_per_node) command(cmd, args...)– routes the command through the per-node pooled connectionsget_client_for_key(key)– returns the node’s main client (not from pool)-
get_random_client()– same (main client), for keyless commands -
Slot table:
slot_to_node[16384] -
Dynamic node list with lazy creation of:
-
main_client -
pooled clients in
idlequeue -
Redirection handling:
-
MOVED → update slot mapping and retry
- ASK → send
ASKINGto target node and retry once
Connection Pool¶
Each cluster node contains:
main_client— persistent non-pooled connectionidle— MPMC queue for idle pooled connectionslive_count— number of created pooled connectionsmax_connections_per_node— pool capacity
Acquire logic¶
- If queue is not empty → use connection
- Else if
live_count < max→ open a new pooled connection - Else → coroutine briefly sleeps and retries
This ensures excellent parallelism for workloads with high concurrency.
Fallback Mode (Cluster Disabled)¶
If all seeds return:
or if CLUSTER SLOTS command fails, the client automatically switches to single-node mode.
Behavior in fallback mode¶
- Slot table is ignored
- Only one logical node exists (from the first seed)
- All routing logic collapses to that node
- MOVED/ASK redirections are disabled
command()works exactly the same, using that node’s poolget_client_for_key()andget_random_client()both return the same node- Pooling remains fully functional
Logging¶
When fallback activates, logs include:
This ensures you always see when cluster autodetection failed.
RedisValue Extensions (New)¶
RedisValue now has safe helpers for extracting data.
as_string_optional()¶
Returns:
Rules¶
- Null →
nullopt - BulkString or SimpleString → value
- Any other type →
nullopt
Used for HGET:
auto res = co_await cluster.command("HGET", "fx:rates", "KGS");
if (!res) ...
auto s = res->as_string_optional();
as_map()¶
Converts array reply from HGETALL into:
Skipping invalid entries.
as_unordered_map()¶
Same as as_map(), but returns:
Usually preferred for performance.
Example: key routing with pooled connections¶
#include "uvent/Uvent.h"
#include "uredis/RedisClusterClient.h"
#include <ulog/ulog.h>
using namespace usub::uvent;
using namespace usub::uredis;
namespace task = usub::uvent::task;
task::Awaitable<void> cluster_example()
{
RedisClusterConfig cfg;
cfg.seeds = { {"127.0.0.1", 7000} };
cfg.max_redirections = 8;
cfg.max_connections_per_node = 4;
RedisClusterClient cluster{cfg};
auto dc = co_await cluster.connect();
if (!dc) co_return;
std::array<std::string_view, 2> args_set{"user:42", "Kirill"};
auto set_res = co_await cluster.command("SET", args_set);
if (!set_res) co_return;
std::array<std::string_view, 1> args_get{"user:42"};
auto get_res = co_await cluster.command("GET", args_get);
if (get_res && get_res->is_bulk_string())
usub::ulog::info("value = {}", get_res->as_string());
co_return;
}
Example: Using main-client directly¶
task::Awaitable<void> cluster_raw_client_example()
{
RedisClusterConfig cfg;
cfg.seeds = { {"127.0.0.1", 7000} };
RedisClusterClient cluster{cfg};
co_await cluster.connect();
auto cli_res = co_await cluster.get_client_for_key("user:42");
if (!cli_res) co_return;
auto cli = cli_res.value();
co_await cli->set("user:42", "Kirill");
auto v = co_await cli->get("user:42");
if (v && v->has_value())
usub::ulog::info("raw: {}", **v);
co_return;
}