Git Product home page Git Product logo

cpp-hiredis-cluster's Introduction

Build Status

cpp-hiredis-cluster

c++ cluster wrapper for hiredis with async and unix sockets features

Features:

  • redis cluster support
  • async hiredis functions are supported
  • support of clustering through unix sockets (see examples)
  • threaded safe connection pool support
  • maximum hiredis compliance in functions invocations (easy to migrate from existing hiredis source code)
  • follow moved redirections
  • follow ask redirections
  • understandable sources
  • best performance (see performance test result here)

Dependencies:

  • library "hiredis" versioned >= 0.12.0
  • installed redis server versioned >= 3.0.0
  • configured cluster, see cluster tutorial on how to setup cluster
  • its better for you to know about "moved" and "asking" redirections clusterspec (not necessary for quick start)
  • libevent-2.0 (only if you choose asynchronous client, see "Asynchronous client example"), the latest stable version is libevent-2.0.22

Examples

Synchronous client example

    // Declare cluster pointer
    Cluster<redisContext>::ptr_t cluster_p;
    // Declare pointer to simple hiredis reply structure
    redisReply * reply;
    // Create cluster passing acceptable address and port of one node of the cluster nodes 
    cluster_p = HiredisCommand<>::createCluster( "127.0.0.1", 7000 );
    // send command to redis passing created cluster pointer, key which you wish to access in the command
    // and command itself with parameters with printf like syntax
    reply = static_cast<redisReply*>( HiredisCommand<>::Command( cluster_p, "FOO", "SET %s %s", "FOO", "BAR1" ) );
    // Check reply state and type
    if( reply->type == REDIS_REPLY_STATUS  || reply->type == REDIS_REPLY_ERROR )
    {
       // Process reply
        cout << " Reply to SET FOO BAR " << endl;
        cout << reply->str << endl;
    }
    // free hiredis reply structure
    freeReplyObject( reply );
    // delete cluster by its pointer
    delete cluster_p;

source code is available in src/examples/example.cpp

Asynchronous client example

// declare a callback to process reply to redis command
static void setCallback( typename Cluster<redisAsyncContext>::ptr_t cluster_p, void *r, void *data )
{
    // declare local pointer to work with reply
    redisReply * reply = static_cast<redisReply*>( r );
    // cast data that you pass as callback parameter below (not necessary)
    string *demoData = static_cast<string*>( data );
    // check redis reply usual
    if( reply->type == REDIS_REPLY_STATUS  || reply->type == REDIS_REPLY_ERROR )
    {
        // process reply
        cout << " Reply to SET FOO BAR " << endl;
        cout << reply->str << endl;
    }
    // process callback parameter if you want (not necessary)
    cout << *demoData << endl;
    delete demoData;
    // disconnecting cluster will brake the event loop
    cluster_p->disconnect();
}
// declare a functions that invokes redis commanf
void processAsyncCommand()
{
    // Declare cluster pointer with redisAsyncContext as template parameter
    Cluster<redisAsyncContext>::ptr_t cluster_p;
    // ignore sigpipe as we use libevent
    signal(SIGPIPE, SIG_IGN);
    // create libevent base
    struct event_base *base = event_base_new();
    // create custom data that will be passed to callback (not necessary)
    string *demoData = new string("Demo data is ok");
    // Create cluster passing acceptable address and port of one node of the cluster nodes
    cluster_p = AsyncHiredisCommand<>::createCluster( "127.0.0.1", 7000, static_cast<void*>( base ) );
    // send command to redis passing created cluster pointer, key which you wish to access in the command
    // callback function, that just already declared above, pointer to any user defined data
    // and command itself with parameters with printf like syntax
    AsyncHiredisCommand<>::Command( cluster_p,                      // cluster pointer
                                     "FOO",                             // key accessed in current command
                                 setCallback,                       // callback to process reply
                                 static_cast<void*>( demoData ),    // custom user data pointer
                                 "SET %s %s",                       // command
                                 "FOO",                             // paramener - key
                                 "BAR1" );                          // parameter - value
    // process event loop
    event_base_dispatch(base);
    // delete cluster object
    delete cluster_p;
    // free event base
    event_base_free(base);
}

source code is available in src/examples/asyncexample.cpp

it's easy to modify asynchronous client for use with another event loop libraries

Other examples

  • example showing how to create a threaded connection pool (src/examples/threadpool.cpp)
  • example showing how to user unix sockets (src/examples/unixsocketexample.cpp)
  • example showing how to process errors in case of asynchronous operation (src/examples/asyncerrorshandling.cpp)

Installing:

  • This is a header only library! No need to install, just include headers in your project
  • Run cmake if you want to build examples

Issuses:

  • If you have link errors for hiredis sds functions just wrap all hiredis headers in extern C in your project

  • If you used this library before note that usage of AsyncHiredisCommand and HiredisCommand little. AsyncHiredisCommand and HiredisCommand change to AsyncHiredisCommand<> HiredisCommand<>

AsyncHiredisCommand -> AsyncHiredisCommand<>
HiredisCommand -> HiredisCommand<>

#Mail shinmail at gmail dot com

cpp-hiredis-cluster's People

Contributors

hankwing avatar jinq0123 avatar mborromeo avatar pcman avatar pcman-appier avatar sb0y avatar shinberg avatar xanpeng 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

cpp-hiredis-cluster's Issues

Question about the library reaction/and usage following servers disconnection.

Hi,
I'm using the synchronous part of the library and I have issues to understand what I should do when the cluster is in degraded state (when one server of the cluster is gone (disconnceted/crashed,etc... but the slots served by that server are still available through a replica server).

exemple :
my redis cluster has 6 servers with replication to 1
A1 : 127.0.0.1:10001
B1 : 127.0.0.1:10002
C1 : 127.0.0.1:10003
A2 : 127.0.0.1:10011
B2 : 127.0.0.1:10012
C2 : 127.0.0.1:10013
A1 and A2 are replicates, and so on (B1-B2, C1-C2).

In my code I create my cluster like that as in exemple.cpp : cluster_p = HiredisCommand<>::createCluster( "127.0.01", 10001 );

Then server on 127.0.0.1:10001 goes down
Then send my command :
auto reply = HiredisCommand<>::AltCommand( cluster_p, "FOO", "SET %s %s", "FOO", "BAR1" );

which throws.
what's the next step ? is it my duty to redo
cluster_p = HiredisCommand<>::createCluster( ) using the ip:port of one the 5 other clusters ? until I find one that creates the cluster object successfully ? and only then I resend my
auto reply = HiredisCommand<>::AltCommand( cluster_p, "FOO", "SET %s %s", "FOO", "BAR1" );
command ?

Thank you for the help.

Double free memory error

After calling Async command for more than 20000000 times, I got memory corruption (double free error).
The gdb backtrace looks like this:

Program received signal SIGABRT, Aborted.
0x00007ffff6c2d428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb)
(gdb)
(gdb)
(gdb) bt
#0  0x00007ffff6c2d428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff6c2f02a in __GI_abort () at abort.c:89
#2  0x00007ffff6c6f7ea in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff6d882e0 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007ffff6c77e0a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7ffff6d883a8 "double free or corruption (fasttop)", action=3) at malloc.c:5004
#4  _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3865
#5  0x00007ffff6c7b98c in __GI___libc_free (mem=<optimized out>) at malloc.c:2966
#6  0x000000000041c2f3 in RedisCluster::ClusterException::ClusterException (this=0x1096fc90, reply=0x117df4e0, text="cluster is going down")
    at /home/appier-user/pcman/user_browse_history/logger/../cpp-hiredis-cluster/include/clusterexception.h:44
#7  0x000000000041c3a5 in RedisCluster::CriticalException::CriticalException (this=0x1096fc90, reply=0x117df4e0, text="cluster is going down")
    at /home/appier-user/pcman/user_browse_history/logger/../cpp-hiredis-cluster/include/clusterexception.h:52
#8  0x000000000041c9f0 in RedisCluster::ClusterDownException::ClusterDownException (this=0x1096fc90, reply=0x117df4e0)
    at /home/appier-user/pcman/user_browse_history/logger/../cpp-hiredis-cluster/include/clusterexception.h:105
#9  0x000000000041d189 in RedisCluster::HiredisProcess::checkCritical (reply=0x117df4e0, errorcritical=false, error="", con=0x0)
    at /home/appier-user/pcman/user_browse_history/logger/../cpp-hiredis-cluster/include/hiredisprocess.h:110
#10 0x00000000004216fc in RedisCluster::AsyncHiredisCommand<RedisCluster::Cluster<redisAsyncContext, RedisCluster::DefaultContainer<redisAsyncContext> > >::processCommandReply (con=0x657a00, r=0x117df4e0,
    data=0x2a5a9a0) at /home/appier-user/pcman/user_browse_history/logger/../cpp-hiredis-cluster/include/asynchirediscommand.h:290
#11 0x00007ffff7bd1e1d in redisProcessCallbacks () from /usr/lib/x86_64-linux-gnu/libhiredis.so.0.13
#12 0x000000000041a6c3 in redisLibuvPoll (handle=0x657b68, status=0, events=1) at /usr/include/hiredis/adapters/libuv.h:24
#13 0x00007ffff79bf055 in ?? () from /usr/lib/x86_64-linux-gnu/libuv.so.1
#14 0x00007ffff79b0efc in uv_run () from /usr/lib/x86_64-linux-gnu/libuv.so.1
#15 0x000000000041bc99 in Server::run (this=0x7fffffffdd30) at /home/appier-user/pcman/user_browse_history/logger/server.cpp:305
#16 0x000000000041a510 in main (argc=5, argv=0x7fffffffdeb8) at /home/appier-user/pcman/user_browse_history/logger/main.cpp:55

This seems to be caused by the double free of redisReply object.
In AsyncCommand::processCommandReply():

HiredisProcess::checkCritical( reply, false );

This line throws exceptions like this, with "reply" object passed into the exception.

        static void checkCritical(redisReply *reply, bool errorcritical, string error = "",
                                  redisContext *con = nullptr) {
...
            if (reply->type == REDIS_REPLY_ERROR) {
                if (errorcritical) {
                    throw LogicError(reply, error);
                } else if (string(reply->str).find("CLUSTERDOWN") != string::npos) {
                    throw ClusterDownException(reply);
                }
            }
        }

Then, in the constructor of ClusterException, the reply object is freed.

        ClusterException(redisReply *reply, const std::string &text) : runtime_error(text) {
            if (reply)
                freeReplyObject(reply);
        }

However, hiredis actually tries to free the object after the callback returns.
FYI: https://github.com/redis/hiredis/blob/master/async.c#L470

void redisProcessCallbacks(redisAsyncContext *ac) {
...
c->reader->fn->freeObject(reply);
...
}

This seems to be the cause of the double free.

support for hiredis pipelining

first of all thanks for this great project.

I was wondering if you have wrapped the redisAppendCommand(Argv) and redisGetReply function from hiredis that allow one to do pipelining..

thanks

Why retry() does not check "userErrorCb_ != NULL"

userErrorCb_ is checked before call:

    if ( that->userErrorCb_ != NULL && that->userErrorCb_(

But retry() doesn't check NULL. Is that OK?

    static void retry( Connection *con, void *r, void *data )
    {
        AsyncHiredisCommand* that = static_cast<AsyncHiredisCommand*>( data );
        
        if( that->processHiredisCommand( con ) != REDIS_OK )
        {
            that->userErrorCb_( *that, DisconnectedException(), HiredisProcess::FAILED );
            that->userCallback_p_( that->cluster_p_, r, that->userPrivData_ );
            delete that;
        }
    }

Issue with executing a script with multiple keys

Hi,

I have written a small script which has two keys. These keys have a tag to have them in a single node of cluster.
The script increments the values of those two keys -

local id = '12345'
local key1 = 'abc.{user1000}. followers'
local key2 = 'abc.{user1000}. following'
redis.call('INCR', key1)
redis.call('HINCRBY', key2, id, 1)

I have used tag which is given in the redis documentation example. I loaded the script and executed with your client. It works.
But when I change the tag to something else like {aser1000}.. IT DOESN'T WORK.

Is there any specific rule for defining the tag in a key?
Will you please give an example for executing a lua script with multi-key operation?

specifying the "key" access by the command as opposed to using {} inside the key to let redis use the content of the brackets to determine the hash slot

Hi,
I see that in your commands you need to specify the underyling "key" that is accessed by the command.
With the normal redis and redis cli you can control where keys are going by using brackets to enclose the portion of the key that must be used for the hashing.

I was wondering how you use this "key" internally just to make sure that it does not conflict with the one embedded in {} considered by redis.

Do you only use the provided key to cache to which node each key is stored in order to minimize redirects???

Another question I have is.. for commands that do not involve keys (like multi, exec, etc) is it find to specify an empty key?

Connecting to a non-cluster node

Is it possible to connect to a non-cluster node??

I have a program where depending on command line arguments I either want to connect to cluster or to a single instance outside of the cluster. While the version that connects to the cluster works just fine, the other gives me this and crashes...

terminate called after throwing an instance of 'RedisCluster::LogicError'
  what():
HERE??
Aborted (core dumped)

The HERE is a print statement that confirms the exact location of the crash which is:

printf("HERE??");
port = (port == NULL || port == 0) ? 6380 : port;
cluster_p = HiredisCommand<>::createCluster( "127.0.0.1", port );
printf("Redis Connection Made to %d ! \n", port);

Obviously, 'Connection Made is never printed"

ThreadedPool can not make Cluster thread-safe

ThreadedPool can not make Cluster thread-safe because neither redisContext nor redisAsyncContext is thread-safe.

From hiredis readme:

    Note: A redisContext is not thread-safe.
    Note: A redisAsyncContext is not thread-safe.

So threadedpool example is wrong. It executes 1000 commandThread() which calls redisAppendFormattedCommand() without mutex lock.

Find keyslot for key and override usual calculation for a command by telling it which node to connect to

Hi, I have a use case where I send many commands in a short period of time with the same keyslot.

I did some performance profiling (gprof) and it looks like slot-finding related functions are quite expensive.

What I would like to do is find the node for a give key once and then run a bunch of commands with that knowledge already.

Is this possible with this library?

If not, do you know what function is responsible for selecting the node from a given key? Then I could just use that one time and selectively send all the messages to the proper redis node.

invalid std::shared_ptr<redisReply> from AltCommand

Hi,
I have just begun to use cpp-hiredis-cluster with synchronous calls and I'm experiencing crashes linked to
reply = RedisCluster::HiredisCommand<>::AltCommand(redisClusterCtx, key.c_str(), "SET %s 0 NX", key.c_str());

it seems that following disconnection with a server from the cluster, AltCommand() passes an non-allocated redisReply object inside the std::shared_ptr<redisReply> object.

At the end of my function (end of scope of the the shared_ptr), the _shared_ptr's pointed object_ deleter is called and
SIGABRT is triggered in HiredisCommand::deleteReply
on freeReplyObject(reply)

Have you ever experienced anything like that ?
I see that while doing tests, by shuting down some redis servers.

Flushall nodes

Hi,

Is there a neat way to call flushall (and other functions without keys) on all the masters of my cluster?

Thanks

Uninitialized pointer in hirediscommand.h leads to SEGV

In the include/hirediscommand.h file, the following method is used heavily in all the hiredis cluster APIs.

    redisReply* processHiredisCommand( Connection *con ) {
        redisReply* reply;
        redisAppendFormattedCommand( con, cmd_, len_ );
        redisGetReply( con, (void**)&reply );
        return reply;
    }

redisReply* reply variable is not initialized. It works fine as long as all the master nodes in the Redis cluster are active. As soon as a master node goes down, redisGetReply method that is being called within the code block shown above returns without altering that uninitialized variable. Now, that uninitialized pointer gets returned to the caller and when accessed by the caller it frequently causes SEGV crash. Please change that one line of code as shown below. That is the safe thing to do.

redisReply* reply = NULL;

I did this change locally in my environment. That eliminated the crashes. After this minor code change, it now properly throws a DisconnectedException which can be caught and dealt with by the end user C/C++ application code.

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.