Hiredis-cluster is a C client library for cluster deployments of the Redis database.
Hiredis-cluster is using Hiredis for the connections to each Redis node.
Hiredis-cluster is a fork of Hiredis-vip, with the following improvements:
- The C library
hiredisis an external dependency rather than a builtin part of the cluster client, meaning that the latesthirediscan be used. - Support for SSL/TLS introduced in Redis 6
- Support for IPv6
- Support authentication using AUTH
- Uses CMake (3.11+) as the primary build system, but optionally Make can be used directly
- Code style guide (using clang-format)
- Improved testing
- Memory leak corrections and allocation failure handling
- Low-level API for sending commands to specific node
-
Redis Cluster
- Connect to a Redis cluster and run commands.
-
Multi-key commands
- Support
MSET,MGETandDEL. - Multi-key commands will be processed and sent to slot owning nodes.
- Support
-
Pipelining
- Send multiple commands at once to speed up queries.
- Supports multi-key commands described in above bullet.
-
Asynchronous API
- Send commands asynchronously and let a callback handle the response.
- Needs an external event loop system that can be attached using an adapter.
-
SSL/TLS
- Connect to Redis nodes using SSL/TLS (supported from Redis 6)
-
IPv6
- Handles clusters on IPv6 networks
Prerequisites:
- A C compiler (GCC or Clang)
- CMake and GNU Make
- hiredis; downloaded automatically by default, but see build options below
- libevent (
libevent-devin Debian); can be avoided if building without tests (DISABLE_TESTS=ON) - OpenSSL (
libssl-devin Debian) if building with TLS support
Hiredis-cluster will be built as a shared library and the test suites will additionally depend on the shared library libhiredis.so, and libhiredis_ssl.so when SSL is enabled.
$ mkdir build; cd build
$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=ON ..
$ makeThe following CMake options are available:
DOWNLOAD_HIREDISOFFCMake will search for an already installed hiredis (for example the the Debian packagelibhiredis-dev) for header files and linkage.ON(default) hiredis will be downloaded from Github, built and installed locally in the build folder.
ENABLE_SSLOFF(default)ONEnable SSL/TLS support and build its tests (also affect hiredis whenDOWNLOAD_HIREDIS=ON).
DISABLE_TESTSOFF(default)ONDisable compilation of tests (also affect hiredis whenDOWNLOAD_HIREDIS=ON).
ENABLE_IPV6_TESTSOFF(default)ONEnable IPv6 tests. Requires that IPv6 is setup in Docker.
ENABLE_COVERAGEOFF(default)ONCompile using build flags that enables the GNU coverage toolgcovto provide test coverage information. This CMake option also enables a new build targetcoverageto generate a test coverage report using gcovr.
USE_SANITIZERCompile using a specific sanitizer that detect issues. The value of this option specifies which sanitizer to activate, but it depends on support in the compiler. Common option values are:address,thread,undefined,leak
Options needs to be set with the -D flag when generating makefiles, e.g.
cmake -DENABLE_SSL=ON -DUSE_SANITIZER=address ..
The build uses CMake's find_package
to search for a hiredis installation. When building and installing hiredis a
file called hiredis-config.cmake will be installed and this contains relevant information for users.
As described in the CMake docs a specific path can be set using a flag like:
-Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis
When a simpler build setup is preferred a provided Makefile can be used directly when building. A benefit of this, instead of using CMake, is that it also provides a static library, a similar limitation exists in the CMake files in hiredis v1.0.0.
The only option that exists in the Makefile is to enable SSL/TLS support via USE_SSL=1
See examples/using_make/build.sh for an example build.
Some tests needs a Redis cluster and that can be setup by the make targets
start/stop. The clusters will be setup using Docker and it may take a while
for them to be ready and accepting requests. Run make start to start the
clusters and then wait a few seconds before running make test.
To stop the running cluster containers run make stop.
$ make start
$ make test
$ make stopIf you want to set up the Redis clusters manually they should run on localhost using following access ports:
| Cluster type | Access port |
|---|---|
| IPv4 | 7000 |
IPv4, authentication needed, password: secretword |
7100 |
| IPv6 | 7200 |
| IPv4, using TLS/SSL | 7300 |
The function redisClusterContextInit is used to create a redisClusterContext.
The function redisClusterSetOptionAddNodes is used to add one or many Redis Cluster addresses.
The function redisClusterConnect2 is used to connect to the Redis Cluster.
The context is where the state for connections is kept.
The redisClusterContextstruct has an integer err field that is non-zero when the connection is
in an error state. The field errstr will contain a string with a description of the error.
After trying to connect to Redis using redisClusterContext you should check the err field to see
if establishing the connection was successful:
redisClusterContext *cc = redisClusterContextInit();
redisClusterSetOptionAddNodes(cc, "127.0.0.1:6379,127.0.0.1:6380");
redisClusterConnect2(cc);
if (cc != NULL && cc->err) {
printf("Error: %s\n", cc->errstr);
// handle error
}The function redisClusterCommand takes a format similar to printf.
In the simplest form it is used like:
reply = redisClusterCommand(clustercontext, "SET foo bar");The specifier %s interpolates a string in the command, and uses strlen to
determine the length of the string:
reply = redisClusterCommand(clustercontext, "SET foo %s", value);Internally, hiredis-cluster splits the command in different arguments and will convert it to the protocol used to communicate with Redis. One or more spaces separates arguments, so you can use the specifiers anywhere in an argument:
reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value);Hiredis-cluster supports mget/mset/del multi-key commands. The command will be splitted per slot and sent to correct Redis nodes.
Example:
reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4);When there is a need to send commands to a specific node, the following low-level API can be used.
reply = redisClusterCommandToNode(clustercontext, node, "DBSIZE");The function handles printf like arguments similar to redisClusterCommand(), but will
only attempt to send the command to the given node and will not perform redirects or retries.
To disconnect and free the context the following function can be used:
void redisClusterFree(redisClusterContext *cc);This function closes the sockets and deallocates the context.
The function redisClusterGetReply is exported as part of the Hiredis API and can be used
when a reply is expected on the socket. To pipeline commands, the only things that needs
to be done is filling up the output buffer. For this cause, the following commands can be used that
are identical to the redisClusterCommand family, apart from not returning a reply:
int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv);
/* Send a command to a specific cluster node */
int redisClusterAppendCommandToNode(redisClusterContext *cc, cluster_node *node,
const char *format, ...);After calling either function one or more times, redisClusterGetReply can be used to receive the
subsequent replies. The return value for this function is either REDIS_OK or REDIS_ERR, where
the latter means an error occurred while reading a reply. Just as with the other commands,
the err field in the context can be used to find out what the cause of this error is.
void redisClusterReset(redisClusterContext *cc);Warning: You must call redisClusterReset function after one pipelining anyway.
The following examples shows a simple cluster pipeline:
redisReply *reply;
redisClusterAppendCommand(clusterContext,"SET foo bar");
redisClusterAppendCommand(clusterContext,"GET foo");
redisClusterGetReply(clusterContext,&reply); // reply for SET
freeReplyObject(reply);
redisClusterGetReply(clusterContext,&reply); // reply for GET
freeReplyObject(reply);
redisClusterReset(clusterContext);Hiredis-cluster comes with an asynchronous cluster API that works with many event systems. Currently there are adapters that enables support for libevent and Redis Event Library (ae), but more can be added. The hiredis library has adapters for additional event systems that easily can be adapted for hiredis-cluster as well.
The function redisAsyncConnect can be used to establish a non-blocking connection to
Redis. It returns a pointer to the newly created redisAsyncContext struct. The err field
should be checked after creation to see if there were errors creating the connection.
Because the connection that will be created is non-blocking, the kernel is not able to
instantly return if the specified host and port is able to accept a connection.
redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL);
if (acc->err) {
printf("Error: %s\n", acc->errstr);
// handle error
}The cluster asynchronous context can hold a disconnect callback function that is called when the connection is disconnected (either because of an error or per user request). This function should have the following prototype:
void(const redisAsyncContext *c, int status);On a disconnect, the status argument is set to REDIS_OK when disconnection was initiated by the
user, or REDIS_ERR when the disconnection was caused by an error. When it is REDIS_ERR, the err
field in the context can be accessed to find out the cause of the error.
You dont need to reconnect in the disconnect callback, hiredis-cluster will reconnect by itself when next command for this Redis node is handled.
Setting the disconnect callback can only be done once per context. For subsequent calls it will
return REDIS_ERR. The function to set the disconnect callback has the following prototype:
int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);In an asynchronous cluster context, commands are automatically pipelined due to the nature of an event loop. Therefore, unlike the synchronous cluster API, there is only a single way to send commands. Because commands are sent to Redis Cluster asynchronously, issuing a command requires a callback function that is called when the reply is received. Reply callbacks should have the following prototype:
void(redisClusterAsyncContext *acc, void *reply, void *privdata);The privdata argument can be used to carry arbitrary data to the callback from the point where
the command is initially queued for execution.
The functions that can be used to issue commands in an asynchronous context are:
int redisClusterAsyncCommand(redisClusterAsyncContext *acc,
redisClusterCallbackFn *fn,
void *privdata, const char *format, ...);
int redisClusterAsyncCommandToNode(redisClusterAsyncContext *acc,
cluster_node *node,
redisClusterCallbackFn *fn, void *privdata,
const char *format, ...);
int redisClusterAsyncFormattedCommandToNode(redisClusterAsyncContext *acc,
cluster_node *node,
redisClusterCallbackFn *fn,
void *privdata, char *cmd, int len);These functions works like their blocking counterparts. The return value is REDIS_OK when the command
was successfully added to the output buffer and REDIS_ERR otherwise. Example: when the connection
is being disconnected per user-request, no new commands may be added to the output buffer and REDIS_ERR is
returned on calls to the redisClusterAsyncCommand family.
If the reply for a command with a NULL callback is read, it is immediately freed. When the callback
for a command is non-NULL, the memory is freed immediately following the callback: the reply is only
valid for the duration of the callback.
All pending callbacks are called with a NULL reply when the context encountered an error.
Asynchronous cluster connections can be terminated using:
void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);When this function is called, connections are not immediately terminated. Instead, new
commands are no longer accepted and connections are only terminated when all pending commands
have been written to a socket, their respective replies have been read and their respective
callbacks have been executed. After this, the disconnection callback is executed with the
REDIS_OK status and the context object is freed.
There are a few hooks that need to be set on the cluster context object after it is created.
See the adapters/ directory for bindings to ae and libevent.
Hiredis-cluster uses hiredis allocation structure with configurable allocation and deallocation functions. By default they just point to libc (malloc, calloc, realloc, etc).
If you have your own allocator or if you expect an abort in out-of-memory cases, you can configure the used functions in the following way:
hiredisAllocFuncs myfuncs = {
.mallocFn = my_malloc,
.callocFn = my_calloc,
.reallocFn = my_realloc,
.strdupFn = my_strdup,
.freeFn = my_free,
};
// Override allocators (function returns current allocators if needed)
hiredisAllocFuncs orig = hiredisSetAllocators(&myfuncs);To reset the allocators to their default libc functions simply call:
hiredisResetAllocators();