Giter Club home page Giter Club logo

hiredis-cluster's Introduction

Hiredis-cluster

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 hiredis is an external dependency rather than a builtin part of the cluster client, meaning that the latest hiredis can 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

Features

  • Redis Cluster

    • Connect to a Redis cluster and run commands.
  • Multi-key commands

    • Support MSET, MGET and DEL.
    • Multi-key commands will be processed and sent to slot owning nodes. (This breaks the atomicity of the commands if the keys reside on different nodes so if atomicity is important, use these only with keys in the same cluster slot.)
  • 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

Build instructions

Prerequisites:

Hiredis-cluster will be built as a shared library libhiredis_cluster.so and it depends on the hiredis shared library libhiredis.so.

When SSL/TLS support is enabled an extra library libhiredis_cluster_ssl.so is built, which depends on the hiredis SSL support library libhiredis_ssl.a.

A user project that needs SSL/TLS support should link to both libhiredis_cluster.so and libhiredis_cluster_ssl.so to enable the SSL/TLS configuration API.

$ mkdir build; cd build
$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=ON ..
$ make

Build options

The following CMake options are available:

  • DOWNLOAD_HIREDIS
    • OFF CMake will search for an already installed hiredis (for example the the Debian package libhiredis-dev) for header files and linkage.
    • ON (default) hiredis will be downloaded from Github, built and installed locally in the build folder.
  • ENABLE_SSL
    • OFF (default)
    • ON Enable SSL/TLS support and build its tests (also affect hiredis when DOWNLOAD_HIREDIS=ON).
  • DISABLE_TESTS
    • OFF (default)
    • ON Disable compilation of tests (also affect hiredis when DOWNLOAD_HIREDIS=ON).
  • ENABLE_IPV6_TESTS
    • OFF (default)
    • ON Enable IPv6 tests. Requires that IPv6 is setup in Docker.
  • ENABLE_COVERAGE
    • OFF (default)
    • ON Compile using build flags that enables the GNU coverage tool gcov to provide test coverage information. This CMake option also enables a new build target coverage to generate a test coverage report using gcovr.
  • USE_SANITIZER Compile 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 ..

Build details

The build uses CMake's find_package to search for a hiredis installation. CMake will search for a hiredis installation in the default paths, searching for a file called hiredis-config.cmake. The default search path can be altered via CMAKE_PREFIX_PATH or as described in the CMake docs; a specific path can be set using a flag like: -Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis

See examples/using_cmake_separate/build.sh or examples/using_cmake_externalproject/build.sh for alternative CMake builds.

Extend the list of supported commands

The list of commands and the position of the first key in the command line is defined in cmddef.h which is included in this repo. It has been generated using the JSON files describing the syntax of each command in the Redis repository, which makes sure hiredis-cluster supports all commands in Redis, at least in terms of cluster routing. To add support for custom commands defined in Redis modules, you can regenerate cmddef.h using the script gencommands.py. Use the JSON files from Redis and any additional files on the same format as arguments to the script. For details, see the comments inside gencommands.py.

Alternative build using Makefile directly

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

By default the hiredis library (and headers) installed on the system is used, but alternative installations can be used by defining the compiler flags CFLAGS and LDFLAGS.

See examples/using_make/build.sh for an example build using an alternative hiredis installation.

Build failures like hircluster_ssl.h:33:10: fatal error: hiredis/hiredis_ssl.h: No such file or directory indicates that hiredis is not installed on the system, or that a given CFLAGS is wrong. Use the previous mentioned build example as reference.

Running the tests

Prerequisites:

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 stop

If 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

Quick usage

Cluster synchronous API

Connecting

The function redisClusterContextInit is used to create a redisClusterContext. The context is where the state for connections is kept.

The function redisClusterSetOptionAddNodes is used to add one or many Redis Cluster addresses.

The functions redisClusterSetOptionUsername and redisClusterSetOptionPassword are used to configure authentication, causing the AUTH command to be sent on every new connection to Redis.

For more options, see the file hircluster.h.

The function redisClusterConnect2 is used to connect to the Redis Cluster.

The redisClusterContext struct 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
}

Events per cluster context

There is a hook to get notified when certain events occur.

int redisClusterSetEventCallback(redisClusterContext *cc,
                                 void(fn)(const redisClusterContext *cc, int event,
                                          void *privdata),
                                 void *privdata);

The callback is called with event set to one of the following values:

  • HIRCLUSTER_EVENT_SLOTMAP_UPDATED when the slot mapping has been updated;
  • HIRCLUSTER_EVENT_READY when the slot mapping has been fetched for the first time and the client is ready to accept commands, useful when initiating the client with redisClusterAsyncConnect2() where a client is not immediately ready after a successful call;
  • HIRCLUSTER_EVENT_FREE_CONTEXT when the cluster context is being freed, so that the user can free the event privdata.

Events per connection

There is a hook to get notified about connect and reconnect attempts. This is useful for applying socket options or access endpoint information for a connection to a particular node. The callback is registered using the following function:

int redisClusterSetConnectCallback(redisClusterContext *cc,
                                   void(fn)(const redisContext *c, int status));

The callback is called just after connect, before TLS handshake and Redis authentication.

On successful connection, status is set to REDIS_OK and the redisContext (defined in hiredis.h) can be used, for example, to see which IP and port it's connected to or to set socket options directly on the file descriptor which can be accessed as c->fd.

On failed connection attempt, this callback is called with status set to REDIS_ERR. The err field in the redisContext can be used to find out the cause of the error.

Sending commands

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);

Commands will be sent to the cluster node that the client perceives handling the given key. If the cluster topology has changed the Redis node might respond with a redirection error which the client will handle, update its slotmap and resend the command to correct node. The reply will in this case arrive from the correct node.

If a node is unreachable, for example if the command times out or if the connect times out, it can indicated that there has been a failover and the node is no longer part of the cluster. In this case, redisClusterCommand returns NULL and sets err and errstr on the cluster context, but additionally, hiredis cluster schedules a slotmap update to be performed when the next command is sent. That means that if you try the same command again, there is a good chance the command will be sent to another node and the command may succeed.

Sending multi-key commands

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);

Sending commands to a specific node

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");

This 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.

If the command times out or the connection to the node fails, a slotmap update is scheduled to be performed when the next command is sent. redisClusterCommandToNode also performs a slotmap update if it has previously been scheduled.

Teardown

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.

Cluster pipelining

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, redisClusterNode *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.

Warning: Calling redisClusterReset without pipelining first will reset all Redis connections.

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);

Cluster asynchronous API

Hiredis-cluster comes with an asynchronous cluster API that works with many event systems. Currently there are adapters that enables support for libevent, libev, libuv, glib and Redis Event Library (ae). For usage examples, see the test programs with the different event libraries tests/ct_async_{libev,libuv,glib}.c.

The hiredis library has adapters for additional event systems that easily can be adapted for hiredis-cluster as well.

Connecting

There are two alternative ways to initiate a cluster client which also determines how the client behaves during the initial connect.

The first alternative is to use the function redisClusterAsyncConnect, which initially connects to the cluster in a blocking fashion and waits for the slotmap before returning. Any command sent by the user thereafter will create a new non-blocking connection, unless a non-blocking connection already exists to the destination. The function returns a pointer to a newly created redisClusterAsyncContext struct and its err field should be checked to make sure the initial slotmap update was successful.

// Insufficient error handling for brevity.
redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL);
if (acc->err) {
    printf("error: %s\n", acc->errstr);
    exit(1);
}

// Attach an event engine. In this example we use libevent.
struct event_base *base = event_base_new();
redisClusterLibeventAttach(acc, base);

The second alternative is to use redisClusterAsyncContextInit and redisClusterAsyncConnect2 which avoids the initial blocking connect. This connection alternative requires an attached event engine when redisClusterAsyncConnect2 is called, but the connect and the initial slotmap update is done in a non-blocking fashion.

This means that commands sent directly after redisClusterAsyncConnect2 may fail because the initial slotmap has not yet been retrieved and the client doesn't know which cluster node to send the command to. You may use the eventCallback to be notified when the slotmap is updated and the client is ready to accept commands. An crude example of using the eventCallback can be found in this testcase.

// Insufficient error handling for brevity.
redisClusterAsyncContext *acc = redisClusterAsyncContextInit();

// Add a cluster node address for the initial connect.
redisClusterSetOptionAddNodes(acc->cc, "127.0.0.1:6379");

// Attach an event engine. In this example we use libevent.
struct event_base *base = event_base_new();
redisClusterLibeventAttach(acc, base);

if (redisClusterAsyncConnect2(acc) != REDIS_OK) {
    printf("error: %s\n", acc->errstr);
    exit(1);
}

Events per cluster context

Use redisClusterSetEventCallback with acc->cc as the context to get notified when certain events occur.

Events per connection

Because the connections that will be created are non-blocking, the kernel is not able to instantly return if the specified host and port is able to accept a connection. Instead, use a connect callback to be notified when a connection is established or failed. Similarily, a disconnect callback can be used to be notified about a disconnected connection (either because of an error or per user request). The callbacks are installed using the following functions:

int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc,
                                        redisConnectCallback *fn);
int redisClusterAsyncSetDisonnectCallback(redisClusterAsyncContext *acc,
                                          redisConnectCallback *fn);

The callback functions should have the following prototype, aliased to redisConnectCallback:

void(const redisAsyncContext *ac, int status);

Alternatively, if hiredis >= v1.1.0 is used, you set a connect callback that will be passed a non-const redisAsyncContext* on invocation (e.g. to be able to set a push callback on it).

int redisClusterAsyncSetConnectCallbackNC(redisClusterAsyncContext *acc,
                                          redisConnectCallbackNC *fn);

The callback function should have the following prototype, aliased to redisConnectCallbackNC:

void(redisAsyncContext *ac, int status);

On a connection attempt, the status argument is set to REDIS_OK when the connection was successful. The file description of the connection socket can be retrieved from a redisAsyncContext as ac->c->fd. 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 don't need to reconnect in the disconnect callback. Hiredis-cluster will reconnect by itself when the next command for this Redis node is handled.

Setting the connect and disconnect callbacks can only be done once per context. For subsequent calls it will return REDIS_ERR.

Sending commands and their callbacks

In an asynchronous cluster context, commands are automatically pipelined due to the nature of an event loop. Therefore, unlike the synchronous 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 most commonly used functions to issue commands in an asynchronous context are:

int redisClusterAsyncCommand(redisClusterAsyncContext *acc,
                             redisClusterCallbackFn *fn,
                             void *privdata, const char *format, ...);
int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc,
                                 redisClusterCallbackFn *fn, void *privdata,
                                 int argc, const char **argv,
                                 const size_t *argvlen);
int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc,
                                      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. When the connection is being disconnected per user-request, no new commands may be added to the output buffer and REDIS_ERR is returned.

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.

Sending commands to a specific node

When there is a need to send commands to a specific node, the following low-level API can be used.

int redisClusterAsyncCommandToNode(redisClusterAsyncContext *acc,
                                   redisClusterNode *node,
                                   redisClusterCallbackFn *fn, void *privdata,
                                   const char *format, ...);
int redisClusterAsyncCommandArgvToNode(redisClusterAsyncContext *acc,
                                       redisClusterNode *node,
                                       redisClusterCallbackFn *fn,
                                       void *privdata, int argc,
                                       const char **argv,
                                       const size_t *argvlen);
int redisClusterAsyncFormattedCommandToNode(redisClusterAsyncContext *acc,
                                            redisClusterNode *node,
                                            redisClusterCallbackFn *fn,
                                            void *privdata, char *cmd, int len);

These functions will only attempt to send the command to a specific node and will not perform redirects or retries, but communication errors will trigger a slotmap update just like the commonly used API.

Disconnecting

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.

Using event library X

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 libevent and a range of other event libraries.

Other details

Cluster node iterator

A redisClusterNodeIterator can be used to iterate on all known master nodes in a cluster context. First it needs to be initiated using redisClusterInitNodeIterator() and then you can repeatedly call redisClusterNodeNext() to get the next node from the iterator.

void redisClusterInitNodeIterator(redisClusterNodeIterator *iter,
                                  redisClusterContext *cc);
redisClusterNode *redisClusterNodeNext(redisClusterNodeIterator *iter);

The iterator will handle changes due to slotmap updates by restarting the iteration, but on the new set of master nodes. There is no bookkeeping for already iterated nodes when a restart is triggered, which means that a node can be iterated over more than once depending on when the slotmap update happened and the change of cluster nodes.

Note that when redisClusterCommandToNode is called, a slotmap update can happen if it has been scheduled by the previous command, for example if the previous call to redisClusterCommandToNode timed out or the node wasn't reachable.

To detect when the slotmap has been updated, you can check if the iterator's slotmap version (iter.route_version) is equal to the current cluster context's slotmap version (cc->route_version). If it isn't, it means that the slotmap has been updated and the iterator will restart itself at the next call to redisClusterNodeNext.

Another way to detect that the slotmap has been updated is to register an event callback and look for the event HIRCLUSTER_EVENT_SLOTMAP_UPDATED.

Random number generator

This library uses random() while selecting a node used for requesting the cluster topology (slotmap). A user should seed the random number generator using srandom() to get less predictability in the node selection.

Allocator injection

Hiredis-cluster uses hiredis allocation structure with configurable allocation and deallocation functions. By default they just point to libc (malloc, calloc, realloc, etc).

Overriding

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();

hiredis-cluster's People

Contributors

abrandoned avatar andreasgerstmayr avatar bjosv avatar charlesraymond1 avatar deep011 avatar dependabot[bot] avatar dluongiop avatar fortrue avatar heronr avatar kevinlussier-netskope avatar moo64c avatar natoscott avatar plainbanana avatar zhuizhuhaomeng avatar zuiderkwast avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hiredis-cluster's Issues

Gcc Error for the exmaple code

Hi there,

I finished installing the hiredis-cluster with the instructions. Now I am running the given example code(https://github.com/Nordix/hiredis-cluster/tree/master/examples/src)/example.c) to connect my redis cluster, but it always failed during gcc with errors. Does anyone have meet this issue? Thank you!

notls.c:(.text+0x20): undefined reference to redisClusterContextInit'
notls.c:(.text+0x37): undefined reference to redisClusterSetOptionAddNodes' notls.c:(.text+0x4e): undefined reference to redisClusterSetOptionConnectTimeout'
notls.c:(.text+0x5a): undefined reference to redisClusterSetOptionRouteUseSlots' notls.c:(.text+0x66): undefined reference to redisClusterConnect2'
notls.c:(.text+0xc3): undefined reference to redisClusterCommand' notls.c:(.text+0xef): undefined reference to freeReplyObject'
notls.c:(.text+0x10e): undefined reference to redisClusterCommand' notls.c:(.text+0x13a): undefined reference to freeReplyObject'
notls.c:(.text+0x146): undefined reference to redisClusterFree'

Regards

clarify redirect vs retry error messages and variables

Recently my Redis cluster was down (getting a CLUSTERDOWN reply), but hiredis-cluster showed the following error: too many cluster redirect.

Looking at the code, ie.

if (cad->retry_count > cc->max_redirect_count) {
it's comparing cad->retry_count with cc->max_redirect_count, and setting the error as REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT.

Retries can happen for other reasons as well (like CLUSTERDOWN), so I suggest to change max_redirect_count to max_retry_count and update the error messages (I assume initially there was only a retry on redirects?)

How to set timeout on a redis cluster client

Does hiredis-cluster fix this bug?

hiredis-vip : master
date : 2019-11-25
hiredis version in hircluster.h : 1.0.0

In my code, I got a redisClusterContext,
I called
struct timeval tv = { _timeout_ms / 1000, (_timeout_ms % 1000) * 1000};
_context = redisClusterConnectWithTimeout(_host.c_str(), tv, HIRCLUSTER_FLAG_NULL);
if (redisClusterSetOptionTimeout(_context,tv) != REDIS_OK) { /* log on error */ }

Why it won't work, if I set _timeout_ms=10 , my redis command timer still get a number bigger than 10, say 29 maybe...

redisClusterSetOptionEnableSSL Out of scope

I am trying to build "hiredis-cluster" with USE_SSL=1 to connect to an Elasticache Redis cluster and as a minimal test, I am trying to run example_tls.c with my specific cluster node/host and port number.

I am assuming it is a linking issue on my part, except that none of the other "hiredis-cluster" functions are triggering an out-of-scope. But, the crux of the issue is while compiling I get:

#27 68.99 app/src/test_tls_cluster.cpp: In function 'int main(int, char**)':
#27 68.99 app/src/test_tls_cluster.cpp:30:5: error: 'redisClusterSetOptionEnableSSL' was not declared in this scope
#27 68.99      redisClusterSetOptionEnableSSL(cc, ssl);
#27 68.99      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#27 69.00 app/src/test_tls_cluster.cpp:30:5: note: suggested alternative: 'redisClusterSetOptionMaxRetry'
#27 69.00      redisClusterSetOptionEnableSSL(cc, ssl);
#27 69.00      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#27 69.00      redisClusterSetOptionMaxRetry
#27 69.04 make: *** [makefile:39: app/bin/test_tls_cluster] Error 1
------
executor failed running [/bin/sh -c chmod u+x ./ILOG_COS_20.10_LINUX_X86_64.bin && 	./ILOG_COS_20.10_LINUX_X86_64.bin -f install.properties && 	make clean && make USE_SSL=1 && 	rm -rf ./CPLEX && 	rm -f ./ILOG_COS_20.10_LINUX_X86_64.bin && 	rm -f ./install.properties && 	rm -f ./makefile && 	rm -rf ./app/src && 	rm -rf ./hiredis && 	rm -rf /usr/include/hiredis;]: exit code: 2

Some details:
I am compiling in a docker container. The following is how I am building hiredis_cluster in the container:

... Some stuff above that works ...
RUN git clone https://github.com/redis/hiredis.git --depth 1 && \
	cd hiredis && \
	make USE_SSL=1 && make install && \
	mkdir /usr/include/hiredis && \
	cp libhiredis.so /usr/lib/ && \
	cp libhiredis_ssl.so /usr/lib && \
	cp libhiredis.a /usr/lib/ && \
	cp libhiredis_ssl.a /usr/lib && \
	cp hiredis.h /usr/include/hiredis/ && \
	cp hiredis_ssl.h /usr/include/hiredis/ && \
	cp read.h /usr/include/hiredis/ && \
	cp sds.h /usr/include/hiredis/ && \
	cp alloc.h /usr/include/hiredis/ && \
	cp async.h /usr/include/hiredis/ && \
	cp -r adapters /usr/include/hiredis/adapters/ && \
	ldconfig

WORKDIR /app

# Install hiredis-cluster dependencies:
RUN git clone https://github.com/Nordix/hiredis-cluster.git --depth 1 && \
	mkdir /usr/include/hiredis_cluster && \
	cd hiredis-cluster && \
	mkdir install && \
	make USE_SSL=1 CFLAGS="-I/usr/include -D_XOPEN_SOURCE=600" LDFLAGS="-L/usr/local/lib" clean install &&\
	cp -r /usr/local/include/hiredis_cluster/ /usr/include/hiredis_cluster/ && \
	cp /usr/local/lib/libhiredis_cluster.so /usr/lib/ && \
	cp /usr/local/lib/libhiredis_cluster.a /usr/lib/ && \
	ldconfig
... Some stuff below that installs other packages that another program requires ...

I then call my specific make file to build test_tls_cluster.cpp which is literally just the example_tls.c script from the repo with my specific Elasticache Server and a path pointing to a ca-certificate.crt file. The make file builds the binary from the source files via the g++ compiler as shown above in the error message:
g++ -std=c++17 app/src/test_tls_cluster.cpp -I./app/include -I/usr/include -L/usr/lib/libhiredis_cluster.a -L/usr/local/lib/libhiredis.a -L/usr/local/lib/libhiredis_ssl.a -lm -lpthread -o app/bin/test_tls_cluster

Can anyone tell me what I am doing wrong?

support format "127.0.0.1:1234, 127.0.0.2:5678", trim blank, tab

int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs) {
int ret;
sds *address = NULL;
int address_count = 0;
int i;

if (cc == NULL) {
    return REDIS_ERR;
}

address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR,
                      strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count);
if (address == NULL) {
    __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory");
    return REDIS_ERR;
}

if (address_count <= 0) {
    __redisClusterSetError(cc, REDIS_ERR_OTHER,
                           "invalid server addresses (example format: "
                           "127.0.0.1:1234,127.0.0.2:5678)");
    sdsfreesplitres(address, address_count);
    return REDIS_ERR;
}

for (i = 0; i < address_count; i++) {
sdstrim(address[i],"\t ");  // support format "127.0.0.1:1234, 127.0.0.2:5678",  trim blank, tab
    ret = redisClusterSetOptionAddNode(cc, address[i]);
    if (ret != REDIS_OK) {
        sdsfreesplitres(address, address_count);
        return REDIS_ERR;
    }
}

sdsfreesplitres(address, address_count);

return REDIS_OK;

}

Redis command failures & command timeouts after adding a new Master

Hello,

We have a 3-Node cluster on which we perform CRUD operations from our application using async hiredis-cluster API calls.

If we add a 4th Master node to this cluster while this test is still running, we start seeing redisClusterAsyncCommand failures for some of the new commands, and callbacks with NULL replies for some of commands already invoked for which we are waiting for replies.

It could be that hiredis-cluster is not in sync with the redistributed hash slots after addition of the new Master.

Kindly let us know if this scenario is supported, and works fine for you.

Thank you for the support.

redisClusterAsyncRetryCallback crash after command timeout

Hello,

We are using hiredis-cluster Release 0.8.1, and this issue is with regards to a crash we see in our performance testing.

We use async hiredis APIs, and invoke them both from the application and also from hiredis callbacks.
Our application is multi-threaded, and hiredis context object & hiredis API calls are mutex protected.
Our adapter is designed as per the reference hiredis adapter, and we don't do anything special besides calling into hiredis library.

We see the following crash a few hours into our test following an adapter timeout:

Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x1c
Process uptime: not available

Thread 0 (crashed)
0 libhiredis_cluster.so.0.8!redisClusterAsyncRetryCallback + 0x263
1 libhiredis.so.1.0.0!__redisAsyncFree + 0x69
2 libhiredis.so.1.0.0!redisProcessCallbacks + 0x1a0
3 redisLibmyeventHandler(int, unsigned int, base::SystemInfo) [hiredis_libmyevent.h : 89 + 0x5]

Please help debug the issue. Let me know if you need any further information.

Thank you.

get Error: Command(cluster slots) reply error: nodes sub_reply is not an correct array.

when run example_ipv4
seems can't connect redis server
redis-sever vesion 7.0

my code:
redisClusterContext *cc = redisClusterContextInit();
redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001");
redisClusterSetOptionConnectTimeout(cc, timeout);
redisClusterSetOptionRouteUseSlots(cc);
redisClusterConnect2(cc);
if (cc && cc->err) {
printf("Error: %s\n", cc->errstr);
// handle error
exit(-1);
}

Re-connect issues after Redis node restart

Hi,

We have a test where multiple clients are connected to the cluster and continuously performing CRUD operations on a set of keys.

When we restart a Redis node, we notice that some clients re-connect to the node within seconds after the node has come back up.

Some other clients on the other hand take up to 5 minutes to re-connect.
On these nodes, we see that we keep receiving connect callbacks with status being REDIS_ERR, and errstr of redisAsyncContext says "Connection timed out".
The connection eventually gets re-established.

Kindly help debug the issue.

Thank you.

Support cluster scale down (AWS)

Hey hiredis-cluster team,

We encountered an issue where hiredis-cluster did not update the cluster routing after it was scaled down (one redis node removed from cluster).
The issue results in a failure to connect to (now) the non-existing node, specifically for our use case (AWS managed Redis) - DNS resolution error: Redis error: Name or service not known. For other services it might be an IP address not responding (timeout error).

It seems that the issue is due to the list of nodes already being populated, but the trigger for a remapping is only MOVED errors. In our case the driver would return to redisReconnect() for the configured node and would try the same host again and again.

This situation is quite standard when using Redis clusters in autoscale mode, please consider supporting it.

Thanks!

P.S we are using a Lua wrapper for the hiredis-cluster - which uses hiredis-cluster 0.6.0.

redisClusterAsyncConnect() timeout option

We have noticed that redisClusterAsyncConnect() blocks if the server is unavailable/unreachable. We understand that the timeout option is available for commands only after context creation, but is there a way to make redisClusterAsyncConnect() return error if the connection creation has not succeeded after a certain amount of time?

Thank you.

Number of master nodes

Kindly help with the following queries:

  1. Is there a way to figure out the number of master nodes at any given point?
  2. Is there a way to figure out the number of active nodes (master + replica) nodes at any given point?

Thank you.

Assert while the Nodes are down

Hello,

We have a test that repeatedly kills all the Master Nodes & brings the cluster back up every 5 seconds, while the application is continuously making Async API calls using redisClusterAsyncCommand()

During this we noticed the following assert:

File: /home/osboxes/hiredis-cluster/hircluster.c Line: 3694: 0 [0] /usr/local/lib/libhiredis_cluster.so.0.7(actx_get_by_node+0x69) [0x7ffff7f92cd4] [1] /usr/local/lib/libhiredis_cluster.so.0.7(redisClusterAsyncFormattedCommand+0x25e) [0x7ffff7f93963] [2] /usr/local/lib/libhiredis_cluster.so.0.7(redisClustervAsyncCommand+0xc2) [0x7ffff7f93d68] [3] /usr/local/lib/libhiredis_cluster.so.0.7(redisClusterAsyncCommand+0xca) [0x7ffff7f93e5a]

The code at the above line points to NOT_REACHED() call inside actx_get_by_node()

How do we avoid the assert here?
We understand redisClusterAsyncCommand() will fail in this case which is fine, but we would like to continue waiting till the Nodes are back up rather that exiting at this point.

Thank you.

The file dict.h is not installed

It seems that cmake is not installing the file dict.h because it's missing from CMakeLists.h?

install(FILES hircluster.h adlist.h hiarray.h dict.h
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_cluster)

Setting redis password for redisClusterAsyncContext

I see redisClusterSetOptionPassword(redisClusterContext *cc, ) in hircluster.h but how this can be used with redisClusterAsyncContext ?

In async example the context is created together with connect

redisClusterAsyncContext *cc =
redisClusterAsyncConnect(nodes.c_str(), HIRCLUSTER_FLAG_NULL);
if (cc && cc->err) {
printf("Error: %s\n", cc->errstr);
return 1;
}

Is it possible to pass redisClusterContext with password to redisClusterAsyncContext before connect ?

"cluster info" command

Hi,

We are trying to issue 'cluster info' as follows:

int status = redisClusterAsyncCommand(cc, clusterInfoCallback, (char *)"THE_ID", "CLUSTER INFO");

This returns REDIS_ERR, with cc->err set to "Parse command error. Cmd type: 0, state: 5, break position: 15."

What could be the issue here?

Thank you.

Support AUTH with username

Background
Redis 6 introduced Access Control List that can allow certain connections to be limited in terms of the commands that can be executed and the keys that can be accessed.

The Redis AUTH command was also extended in Redis 6, to enable authentication as a specific user:

AUTH <username> <password>

Requested functionality
Currently the API only supports the legacy authentication method using password via:

int redisClusterSetOptionPassword(redisClusterContext *cc,
                                  const char *password);

API support for specifying a username is needed.

Misc
Redis 6 will internally use the username default if no username is given in AUTH

Links
https://redis.io/topics/acl
https://redis.io/commands/auth

Async support for cluster nodes call

Hi,

cluster_update_route_by_addr() function today invokes a 'cluster nodes' command and waits synchronously till the response is available. Since its a blocking call, and hiredis context is single threaded, all other operations go for a halt till the response is received. This impacts the performance of the system, since all other calls application uses are async implemented, and any code flow that reaches here has to wait till the call gets over.

This ticket is a request for an async implementation for supporting the cluster nodes/slots command.

Thanks.

redis-cluster attempting to connect to a stale redis server IP and not recovering

Hi @bjosv,

We came across a case wherein hiredis-cluster seems to be trying to connect to a stale redis-server ip while issuing commands to redis server, this connection flapping kept on happening (approx 2hrs) until one more redeploy was done for the application image (which has redis-cluster integrated).
From the server-side logs, it looks like this ip was alive earlier for some period of time in the past, but not for the previous couple of hours atleast when the issue was seen. There was an image upgrade done on the redis server nodes which could have resulted in the change of ips.

As part of the callbacks registered by application with hiredis, the below prints were seen as part of the redis command send calls.

[2023-01-12T07:47:30.263102][disconnectCallback]Error: Server closed the connection

...connectCallback print for 192.168.228.26

[2023-01-12T07:48:26.189608][disconnectCallback]Error: Timeout

...connectCallback print for 192.168.228.26

[2023-01-12T07:48:28.191117][disconnectCallbackk]Error: Timeout

...connectCallback print for 192.168.228.26

[2023-01-12T07:48:34.186333][disconnectCallback]Error: Connection reset by peer

...connectCallback print for 192.168.228.26

[2023-01-12T09:12:53.184634][disconnectCallback][Disconnected from 192.168.228.26:6379

kubectl get pods output was collected when the issue was seen, and at that point the list of ips are as captured below. 3 primary nodes and 2 replica per node (adds up to total 9 nodes) as shown below. Here, the ip which redis-cluster is trying to connect - 192.168.228.26 is not appearing.

  1 NAME                                READY   STATUS    RESTARTS   AGE    IP                NODE                                    NOMINATED NODE   READINESS GATES
  2 rediscluster-node-for-redis-9cj4g   2/2     Running   0          169m   192.168.229.229   cl-60-node-5-dcc808-4b2c09f7b029c460    <none>           <none>
  3 rediscluster-node-for-redis-9vvzf   2/2     Running   0          165m   192.168.228.4     cl-60-node-9-dcc808-4b2c09f7b029c460    <none>           <none>
  4 rediscluster-node-for-redis-h5lw6   2/2     Running   0          168m   192.168.100.63    cl-60-node-4-dcc808-4b2c09f7b029c460    <none>           <none>
  5 rediscluster-node-for-redis-mx8gs   2/2     Running   0          168m   192.168.149.14    cl-60-node-1-dcc808-4b2c09f7b029c460    <none>           <none>
  6 rediscluster-node-for-redis-qwrqg   2/2     Running   0          168m   192.168.33.99     cl-60-node-6-dcc808-4b2c09f7b029c460    <none>           <none>
  7 rediscluster-node-for-redis-slbj7   2/2     Running   0          166m   192.168.164.92    cl-60-node-3-dcc808-4b2c09f7b029c460    <none>           <none>
  8 rediscluster-node-for-redis-t92lx   2/2     Running   0          169m   192.168.100.3     cl-60-node-4-dcc808-4b2c09f7b029c460    <none>           <none>
  9 rediscluster-node-for-redis-wgbdq   2/2     Running   0          166m   192.168.41.246    cl-60-node-10-dcc808-4b2c09f7b029c460   <none>           <none>
 10 rediscluster-node-for-redis-z5dr7   2/2     Running   0          169m   192.168.124.200   cl-60-node-2-dcc808-4b2c09f7b029c460    <none>           <none>

Do you have some clue on what could be resulting in this behaviour?

Crash in actx_get_by_node()

Hello,

We are using hiredis-cluster Release 0.8.1, and this issue is with regards to a crash we see in our performance testing.

We use async hiredis APIs, and invoke them both from the application and also from hiredis callbacks.
Our application is multi-threaded, and hiredis context object & hiredis API calls are mutex protected.

We noticed the following crash a few hours into our test:

Crash reason: SIGSEGV /0x00000080
Crash address: 0x0
Process uptime: not available

Thread 13 (crashed)
0 libhiredis_cluster.so.0.8!actx_get_by_node [hircluster.c : 3630 + 0x0]
1 libhiredis_cluster.so.0.8!redisClusterAsyncFormattedCommand [hircluster.c : 4117 + 0x8]
2 libhiredis_cluster.so.0.8!redisClustervAsyncCommand [hircluster.c : 4244 + 0x16]
3 libhiredis_cluster.so.0.8!redisClusterAsyncCommand [hircluster.c : 4258 + 0x5]

Kindly help resolve this crash.

Thank you.

async api

Will it block when the cluster node connection fails?

return error when master node failover

hello,
i have a redis cluster with three master and three slave. when one master or slave node is down, function command() will return error "server closed connection". i try to set retry option but useless, is there any ways to solve it? thanks (my request way is Synchronous blocking )

Error on building library from source code

cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=OFF ..

-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Detected version: 0.8.1
Downloading dependency 'hiredis'..
Scanning dependencies of target hiredis-populate
[ 11%] Creating directories for 'hiredis-populate'
[ 22%] Performing download step (git clone) for 'hiredis-populate'
Cloning into 'hiredis'...
Note: switching to 'v1.1.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

git switch -c

Or undo this operation with:

git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at c14775b Prepare for v1.1.0 GA
[ 33%] No patch step for 'hiredis-populate'
[ 44%] Performing update step for 'hiredis-populate'
fatal: invalid upstream 'origin/v1.1.0'
fatal: No rebase in progress?
CMake Error at /root/hiredis-cluster/build/_deps/hiredis-subbuild/hiredis-populate-prefix/tmp/hiredis-populate-gitupdate.cmake:102 (message):

Failed to rebase in: '/root/hiredis-cluster/build/_deps/hiredis/'.

You will have to resolve the conflicts manually

make[2]: *** [CMakeFiles/hiredis-populate.dir/build.make:97: hiredis-populate-prefix/src/hiredis-populate-stamp/hiredis-populate-update] Error 1
make[1]: *** [CMakeFiles/Makefile2:76: CMakeFiles/hiredis-populate.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

CMake Error at /usr/share/cmake-3.16/Modules/FetchContent.cmake:915 (message):
Build step for hiredis failed: 2
Call Stack (most recent call first):
/usr/share/cmake-3.16/Modules/FetchContent.cmake:1006 (__FetchContent_directPopulate)
CMakeLists.txt:91 (FetchContent_Populate)

-- Configuring incomplete, errors occurred!
See also "/root/hiredis-cluster/build/CMakeFiles/CMakeOutput.log".

Does anybody know, why I'm having this dependancy problem while trying to compile library from a source?

Crude support for BITFIELD/BITFIELD_RO

There's no current way to support the use case where we want to have support for -ASK or -MOVED with the BITFIELD command. The sole option is to send the BITFIELD command to a single node having the knowledge where this specific key resides (as explained in #50 ). This forces the client to have a sub-optimal flow when using the command.

As the BITFIELD command will always apply to a single key (single slot), there is also no need to parse the sub-commands. Also, redis executes all bitfield sub-commands in the same execution (see https://github.com/redis/redis/blob/unstable/src/bitops.c#L1136)

Optionally, we can support the BITFIELD_RO as a bonus because the only difference is that it supports the GET command.

How to set timeout on a redis cluster client?

hey, bjosv
It does not work with command time opt, here's my code
handle_ = redisClusterContextInit();
redisClusterSetOptionAddNodes(handle_, addrs_.c_str());
struct timeval timeout = {0, 5000};
redisClusterSetOptionTimeout(handle_, timeout);
redisClusterConnect2(handle_);
redisReply *reply = (redisReply *)redisClusterCommand(handle_, "GET %s", key.c_str());

Some response still cost more than 5 ms !

Incorrect version

Just a minor issue I noticed while building the project. It's supposed to be version 0.6.0, but it's building as version 0.5.0. It looks like CMakeLists.txt is getting the version from the preprocessor directives in hicluster.h. They should be updated to:

#define HIREDIS_CLUSTER_MAJOR 0
#define HIREDIS_CLUSTER_MINOR 6
#define HIREDIS_CLUSTER_PATCH 0
#define HIREDIS_CLUSTER_SONAME 0.6

Issues with pub/sub

We are running Redis server ver 6.2.7 with hiredis ver 1.1.0 & hiredis-cluster ver 0.9

1] In our existing load test we create a single async context (redisClusterAsyncContext *asyncCtx) using which we perform SET/GET operations on a 3 Node cluster. This test works well. We have performed multiple long duration runs with this test on > 1 million keys without any issues.

2] For testing pub/sub, we have now created another connection with a new context: redisClusterAsyncContext *asyncCtxPS:

asyncCtxPS = redisClusterAsyncContextInit();
redisClusterSetOptionAddNodes(asyncCtxPS->cc, cluster_ip);
redisClusterConnect2(asyncCtxPS->cc);

As connections with Redis Nodes get established as a result of the key operations performed in step 1 above, we trigger subscription in the connect callback:

void connectCallback(const redisAsyncContext *ac, int status) {
nodeIterator ni;
cluster_node *clusterNode;
....
....
initNodeIterator(&ni, asyncCtx->cc);
while ((clusterNode = nodeNext(&ni)) != NULL) {
if (ac == clusterNode->acon) {
redisClusterAsyncCommandToNode(asyncCtx, clusterNode, configCb, NULL, "CONFIG SET notify-keyspace-events Eh");
redisClusterAsyncCommandToNode(asyncCtxPS, clusterNode, eventCb, NULL, "PSUBSCRIBE __key*__:*");
....
....

At present, the above two pub/sub commands are invoked only on the first connected Node, not on all 3 Nodes.

void eventCb(redisClusterAsyncContext *cc, void *r, void *privdata) {
redisReply *reply = (redisReply *)r;

if (r == NULL) {
return;
}

if (reply->type == REDIS_REPLY_ARRAY) {
for (int j = 0; j < reply->elements; j++) {
printf("%u) %s\n", j, reply->element[j]->str);
}
}
}

In response, we see the following in the callback:

0) psubscribe
1) __key*__:*
2) (null)

(null) here doesn't seem correct. Subscription is successful if we run the above PSUBSCRIBE directly on redis-cli. What could be the issue here?

A couple of seconds later, we notice a crash:

#0 0x00007ffff7fa70d3 in redisClusterAsyncCallback (ac=0x5d94d0, r=0x0, privdata=0x5da940)
at /....../hircluster.c:3794
#1 0x00007ffff7fb45f2 in __redisRunCallback (ac=0x5d94d0, cb=0x5da9a0, reply=0x0)
at /....../async.c:311
#2 0x00007ffff7fb4979 in __redisAsyncFree (ac=0x5d94d0)
at /....../async.c:386
#3 0x00007ffff7fb4bbf in __redisAsyncDisconnect (ac=0x5d94d0)
at /....../async.c:450
#4 0x00007ffff7fb52b2 in redisProcessCallbacks (ac=0x5d94d0)
at /....../async.c:625
#5 0x00007ffff7fb560a in redisAsyncRead (ac=0x5d94d0)
at /....../async.c:717
#6 0x00007ffff7fb569b in redisAsyncHandleRead (ac=0x5d94d0)
at /....../async.c:738
#7 0x00000000004eb13d in redisLibmyeventHandlerRead (fd=<optimized out>, arg=0x5d7de0)
at /....../hiredis_libmyevent.h:27

Line 625 of async.c is:

605 if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
606 /*
607 * A spontaneous reply in a not-subscribed context can be the error
608 * reply that is sent when a new connection exceeds the maximum
609 * number of allowed connections on the server side.
610 *
611 * This is seen as an error instead of a regular reply because the
612 * server closes the connection after sending it.
613 *
614 * To prevent the error from being overwritten by an EOF error the
615 * connection is closed here. See issue #43.
616 *
617 * Another possibility is that the server is loading its dataset.
618 * In this case we also want to close the connection, and have the
619 * user wait until the server is ready to take our request.
620 */
621 if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
622 c->err = REDIS_ERR_OTHER;
623 snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
624 c->reader->fn->freeObject(reply);
625 __redisAsyncDisconnect(ac);
626 return;
627 }

If we disable pub/sub in step 2 above, the test runs well again.
Please help debug the issue. Let us know if any addition information is required.

Thank you.

[QUESTION] READONLY requests using Async API

Hello,

I'm trying to use the Async API. Everything works fine, the load is balanced on my masters. My test software shows an output of 160k req/s (only GET commands)
If I set my connection to READONLY, only the 3 master instances seems to be used (doing a simple "top" shows only 3 instances working)

Here is a code extract of what i do (removed error checks):

redisClusterAsyncContext * cc = redisClusterAsyncConnect("127.0.0.1:30001", HIRCLUSTER_FLAG_NULL);
uv_loop_t * loop = (uv_loop_t *)malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
redisClusterLibuvAttach(cc, loop);
redisClusterAsyncContext(cc, "READONLY");
redisClusterAsyncCommand(cc, &__cb_getKey, &priv, "GET key");

Is this the correct way to handle this ? Also what is the purpose of the second parameter of redisClusterAsyncConnect ?

Thanks

License

Hi!

We're looking into using hiredis-cluster in our application, but we're unclear on the copyright on a few files without license header (command.c, hiarray.c, hircluster.c, hiutil.c, win32.h and their headers).

Based on the COPYING file in the root folder we believe it's BSD 3-Clause. Could we have this clarified by adding a license header to all files? Some files already have a license header (adlist.c, crc16.c etc.).

Thank you!

What can i do with multiple threads?

Excuse me, my application scenario is that multiple threads will call the redis api to write message. What can I do with the connection handle "redisClusterContext *cc"? Create one per thread

Steps to compile it on Windows 10

I am looking for correct tools to compile it on Windows 10. Any help ? Tried by through cmake and following

pkg-config_0.26-1_win32.zip, gettext-runtime_0.18.1.1-2_win32.zip, glib_2.28.8-1_win32.zip for pkg-config with cmake but it still fails with following error:

D:\_redis_lib>cmake.exe -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_HIREDIS=ON ..
Detected version: 0.7.0
Downloading dependency 'hiredis'..
Detected version: 1.0.0
-- Checking for module 'glib-2.0'
--   No package 'glib-2.0' found
-- Configuring done
CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
Please set them or make sure they are set and tested correctly in the CMake files:
hiredis_cluster;hiredis;LIBEVENT_LIBRARY
    linked by target "ct_connection" in directory D:/_redis_lib/tests
    linked by target "ct_out_of_memory_handling" in directory D:/_redis_lib/tests
    linked by target "ct_connection_ipv6" in directory D:/_redis_lib/tests
    linked by target "ct_pipeline" in directory D:/_redis_lib/tests
    linked by target "ct_async" in directory D:/_redis_lib/tests
    linked by target "ct_specific_nodes" in directory D:/_redis_lib/tests
    linked by target "clusterclient_async" in directory D:/_redis_lib/tests
    linked by target "clusterclient_reconnect_async" in directory D:/_redis_lib/tests

CMake Error at CMakeLists.txt:132 (TARGET_LINK_LIBRARIES):
  Target "hiredis_cluster" links to:

    hiredis::hiredis

  but the target was not found.  Possible reasons include:

    * There is a typo in the target name.
    * A find_package call is missing for an IMPORTED target.
    * An ALIAS target is missing.



-- Generating done
CMake Generate step failed.  Build files cannot be regenerated correctly.

MULTI/EXEC with async

We are using hiredis-cluster/hiredis C client library to access a Redis server (version 5.0.7)
It has been observed when the library makes an async connection to the server, the MULTI command is failing (the library returns a NULL reply pointer)
However, when the library makes a synchronous connection to the server, the MULTI command works as expected.
We need to understand if it is a known paradigm that MULTI will not work on asynchronous connections with Redis server or if this might be a limitation of the hiredis-cluster/hiredis library itself.

We used the following --

redisClusterAsyncCommand(context, callback, client_data, "MULTI");

In the callback
void callback(redisClusterAsyncContext *cc, void *r, void *client_data)

redisReply *reply = (redisReply *)r; <--- this comes as NULL

Thanks for the help.

Crash while doing dictRelease

Hi,

Came across one more crash scenario.
The top frames look similar to ticket raised before: #113

However, the bt leading to it is different.

[Current thread is 1 (Thread 0x7f22b4e9ba40 (LWP 38))]
(gdb) bt
#0  actx_get_by_node (acc=acc@entry=0x7b1a20, node=0x1c351e0)
    at hircluster.c:3631
#1  0x00007f22b84ef01c in redisClusterAsyncFormattedCommand (acc=acc@entry=0x7b1a20,
    fn=fn@entry=0x7f22b815d040 <optimized out>, privdata=privdata@entry=0x0,
    cmd=0xac9c90 <optimized out>, len=86)
    at hircluster.c:4124
#2  0x00007f22b84ef31b in redisClustervAsyncCommand (acc=0x7b1a20,
    fn=0x7f22b815d040 <optimized out>, privdata=0x0, format=<optimized out>,
    ap=ap@entry=0x7ffdb96af2b0) at hircluster.c:4251
#3  0x00007f22b84ef42e in redisClusterAsyncCommand (acc=<optimized out>, fn=<optimized out>, privdata=<optimized out>, format=<optimized out>)
    at hircluster.c:4265

Frames 4 and 5 are application frames, where it processes callback from hiredis and again issues a new command for execution.

#6  0x00007f22b84ef6e5 in redisClusterAsyncRetryCallback (ac=<optimized out>, r=0x0, privdata=0x7b75b0)
    at hircluster.c:4016
#7  0x00007f22b84d0e09 in __redisRunCallback (reply=0x0, cb=0x7ffdb96af690, ac=0x1e091a0)
    at hiredis/async.c:288
#8  __redisAsyncFree (ac=0x1e091a0) at hiredis/async.c:310
#9  0x00007f22b84d1618 in redisAsyncFree (ac=0x1e091a0) at hiredis/async.c:375
#10 0x00007f22b84e5ee5 in cluster_node_deinit (node=node@entry=0x8865c0)
    at hircluster.c:281
#11 0x00007f22b84e6015 in cluster_node_deinit (node=0x8865c0)
    at hircluster.c:264
#12 dictClusterNodeDestructor (privdata=<optimized out>, val=0x8865c0)
    at hircluster.c:133
#13 0x00007f22b84e5616 in _dictClear (ht=0x88ef80) at dict.c:179
#14 dictRelease (ht=0x88ef80) at dict.c:194
#15 0x00007f22b84eac44 in cluster_update_route_by_addr (port=<optimized out>, ip=<optimized out>, cc=<optimized out>)
    at hircluster.c:1390
#16 cluster_update_route (cc=cc@entry=0x7b1920) at hircluster.c:1463
#17 0x00007f22b84efa65 in actx_get_after_update_route_by_slot (slot_num=4682, acc=0x7b1a20)
    at hircluster.c:3737
#18 redisClusterAsyncRetryCallback (ac=<optimized out>, r=0x919570, privdata=0x169d230)
    at hircluster.c:3972
#19 0x00007f22b84d190c in __redisRunCallback (reply=<optimized out>, cb=0x7ffdb96af920, ac=0x7b9650)
    at hiredis/async.c:288
#20 redisProcessCallbacks (ac=0x7b9650) at hiredis/async.c:580

Below frames, eventloop detects a socket event and calls into hiredis.

Looks like there is multiple back and forth jumping between hiredis and application, and eventually leading to a crash. The node, table, slots are all having junk data at frame 0. However, nodes dictionary is intact similar to how ticket #113 was having.

Atleast the below piece of code explains why nodes dictionary is intact.

    /* Replace cc->nodes before releasing the old dict since
     * the release procedure might access cc->nodes. */
    oldnodes = cc->nodes;
    cc->nodes = nodes;
    if (oldnodes != NULL) {
        dictRelease(oldnodes);
    }

But we don't update slots and table as part of this, not sure if that is intentional. Can you please check this stack and see what can lead to this/suggest a solution?

cc: @bjosv

Thanks.

Better support for standalone mode

Although hiredis-cluster is mainly for Redis Cluster, it is useful to also support Redis standalone instances. The benefit is that the same client and application code can be used regardless of the Redis configuration.

  • When connecting, we send a CLUSTER SLOTS command to figure out the slot mapping. If Redis replies with an error indicating that it's not in cluster mode, we can handle this implicitly and return a success code to the caller.
  • When updating the slot mapping, if we know that we're in standalone mode, there is no point sending the CLUSTER SLOTS command to Redis. It also does no harm doing so, but it's an extra call to Redis, so this bullet point is not of the highest priority.
  • We could allow a standalone option when connecting to the cluster, which is just an indication meaning that we guess that we're not in cluster mode. CLUSTER SLOTS is not sent when connecting, but if we later receive a MOVED redirect, the slot mapping is fetched and we switch to cluster mode. The default should be cluster mode though, since this is primarily a cluster client.

Unclear on how pipelining works with Cluster mode

Hello,

I am having trouble understanding how to use pipelining for bulk uploading keys. As I have recently discovered, the redis-cli does not support pipelining for cluster mode, unless I can ensure that keys are pushed to the same slots. But I cannot do this as I am pushing several million keys in a bulk upload using --pipe.

I have an awk script that with cluster mode disabled works really well with the sh awk_script.sh | redis-cli --pipe command structure, because it can upload my ~2million keys (hashes - HSET) in under a second.

It seems as though the hiredis-cluster library supports pipelining in cluster mode, so my plan was to call the script from within my C/C++ code and pipeline/stream it to redisClusterAppendCommand or redisClusterAppendCommandArgv. But how would freeing the reply object sequentially work in this case, as I see in the README that after every appendCommand I will need to free the reply object? The reason I use awk is for the speed and a one-liner that can parse my .txt file with ease. Will it be possible to pipe the output of my awk script to one of the pipelining commands for redis?

how to use 'KEYS *'

I want to get all keys in redis cluster,and i use "(redisReply *)redisClusterCommand(g_redis_cluster_conn, "KEYS *")", but , this returns NULL

please help me, much THX

Get from Slaves

Hello,

Is it possible to connect to Slaves in order to perform some GET operations?
If yes, how can this be achieved? Also, will we get the connect/disconnect callbacks from the Slaves too?
Kindly advise.

Thank you.

Does hiredis cluster support reconnect after disconnect

i use hiredis cluster like this

ctx = redisClusterContextInit();
redisClusterSetOptionAddNodes(ctx, redis_url.c_str());
if (!password.empty()) {
redisClusterSetOptionPassword(ctx, password.c_str());
}
redisClusterSetOptionRouteUseSlots(ctx);
redisClusterConnect2(ctx);`

my question is redisClusterAsyncContext have redisClusterAsyncSetDisconnectCallback process dissconnect event, but redisClusterContext not have correspond method, does it means when i use redisClusterContext, it automatic processing dissconnect?

Add a static analyzer to CI

By running a static analyzer tool during CI we could find dead code, faulty initiations and more.
Options might be Coverity or Clang static analyzer, or other.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.