diff --git a/README.md b/README.md index ef6eb84..743ecf7 100644 --- a/README.md +++ b/README.md @@ -1478,15 +1478,36 @@ You can start using the cluster right away. You can obtain a connected `Redis` i // Run your query on using the given Redis key / keys. RESP *reply = redisxRequest(shard, "GET", key, NULL, NULL, &status); + ... +``` + +The interactive queries handle both `MOVED` and `ASK` redirections automatically. However, asynchronous queries do not +since they return before receiving a response. Thus, when using `redisxReadReplyAsync()` later to process replies, you +should check for redirections: + +```c + RESP *reply = redisxReadReplyAsync(...); + if(redisxClusterMoved(reply)) { // The key is now served by another shard. - // You might want to obtain the new shard and try again... + // You might want to obtain the new shard and repeat the failed + // transaction again (interactively or pipelined)... + ... + } + if(redisxClusterIsMigrating(reply)) { + // The key's slot is currently migrating. You may try the redirected + // address indicated in the reply, with the ASKING command, e.g. via a + // redisxClusterAskMigrating() interactive transaction. ... } - ... + ``` +As a matter a best practice you should never assume that a given keyword is persistently served by the same shard. +Rather, you should obtain the current shard for the key each time you want to use it with the cluster, and always +check for errors on shard requests, and repeat failed requests on a newly obtained shard if necessary. + Finally, when you are done using the cluster, simply discard it: ```c @@ -1498,11 +1519,12 @@ Finally, when you are done using the cluster, simply discard it: ### Detecting cluster reconfiguration In the above example we have shown one way you might check for errors that result from cluster being reconfigured -on-the-fly, using `redisxClusterMoved()` on the `RESP` reply obtained from the shard. +on-the-fly, using `redisxClusterMoved()` and/or `redisxClusterIsMigrating()` on the `RESP` reply obtained from the +shard. Equivalently, you might use `redisxCheckRESP()` or `redisxCheckDestroyRESP()` also for detecting a cluster -reconfiguration. Both of these will return a designated `REDIS_MOVED` error code if the keyword is now served on -another shard: +reconfiguration. Both of these will return a designated `REDIS_MOVED` or `REDIS_MIGRATING` error code if the keyword +has moved or is migrating, respectively, to another node, e.g.: ```c ... @@ -1511,6 +1533,10 @@ another shard: // The key is now served by another shard. ... } + if(s == REDIS_MIGRATING) { + // The key is migrating and may be accessed from new location via an ASKING directive + ... + } if(s != X_SUCCESS) { // The reply is no good for some other reason... ... @@ -1518,13 +1544,13 @@ another shard: ... ``` -A `REDIS_MOVED` error code will be returned by higher-level functions also, which ingest the `RESP` replies from the -shard and return a digested error code. For example, `redisxGetStringValue()` will set the output `len` value to -`REDIS_MOVED` if the value could not be obtained because of a cluster reconfiguration. +To help manage redirection responses for asynchronous requests, we provide `redisxClusterGetRedirection()` to obtain +the redirected Redis instance based on the redirection `RESP`. Once the redirected cluster shard is identified you may +either resubmit the same query as before (e.h. with `redisxSendArrayRequestAsync()`) if `MOVED`, or else repeat the +query via an interactive `ASKING` directive using `redisxClusterAskMigrating()`. -As a matter a best practice you should never assume that a given keyword is persistently served by the same shard. -Rather, you should obtain the current shard for the key each time you want to use it with the cluster, and always -check for errors on shard requests, and repeat failed requests on a newly obtained shard if necessary. +A `REDIS_MOVED` error code may be returned by higher-level functions also, which ingest the `RESP` replies from the +shard and return a digested error code. diff --git a/include/redisx.h b/include/redisx.h index a8df7b6..e4a9683 100644 --- a/include/redisx.h +++ b/include/redisx.h @@ -129,6 +129,7 @@ enum resp_type { #define REDIS_UNEXPECTED_RESP (-105) ///< \hideinitializer Got a Redis response of a different type than expected #define REDIS_UNEXPECTED_ARRAY_SIZE (-106) ///< \hideinitializer Got a Redis response with different number of elements than expected. #define REDIS_MOVED (-107) ///< \hideinitializer The requested key has moved to another cluster shard. +#define REDIS_MIGRATING (-108) ///< \hideinitializer The requested key is importing, and you may query with ASKED on the specified node. /** * RedisX channel IDs. RedisX uses up to three separate connections to the server: (1) an interactive client, in which @@ -418,10 +419,14 @@ int redisxSetTLSSkipVerify(Redis *redis, boolean value); RedisCluster *redisxClusterInit(Redis *node); Redis *redisxClusterGetShard(RedisCluster *cluster, const char *key); +boolean redisxClusterIsRedirected(const RESP *reply); boolean redisxClusterMoved(const RESP *reply); +boolean redisxClusterIsMigrating(const RESP *reply); int redisxClusterConnect(RedisCluster *cluster); int redisxClusterDisconnect(RedisCluster *cluster); void redisxClusterDestroy(RedisCluster *cluster); +Redis *redisxClusterGetRedirection(RedisCluster *cluster, const RESP *redirect, boolean refresh); +RESP *redisxClusterAskMigrating(Redis *redis, const char **args, const int *lengths, int n, int *status); int redisxPing(Redis *redis, const char *message); enum redisx_protocol redisxGetProtocol(Redis *redis); @@ -507,6 +512,7 @@ int redisxUnlockClient(RedisClient *cl); // Asynchronous access routines (use within redisxLockClient()/ redisxUnlockClient() blocks)... int redisxSendRequestAsync(RedisClient *cl, const char *command, const char *arg1, const char *arg2, const char *arg3); int redisxSendArrayRequestAsync(RedisClient *cl, const char **args, const int *length, int n); +int redisxClusterAskMigratingAsync(RedisClient *cl, const char **args, const int *lengths, int n); int redisxSetValueAsync(RedisClient *cl, const char *table, const char *key, const char *value, boolean confirm); int redisxMultiSetAsync(RedisClient *cl, const char *table, const RedisEntry *entries, int n, boolean confirm); RESP *redisxReadReplyAsync(RedisClient *cl, int *pStatus); @@ -516,6 +522,7 @@ int redisxIgnoreReplyAsync(RedisClient *cl); int redisxSkipReplyAsync(RedisClient *cl); int redisxPublishAsync(Redis *redis, const char *channel, const char *data, int length); + // Error generation with stderr message... int redisxError(const char *func, int errorCode); const char* redisxErrorDescription(int code); diff --git a/man/man1/redisx-cli.1.gz b/man/man1/redisx-cli.1.gz index a52b389..ce13c14 100644 Binary files a/man/man1/redisx-cli.1.gz and b/man/man1/redisx-cli.1.gz differ diff --git a/src/redisx-cli.c b/src/redisx-cli.c index 344edbf..95caf09 100644 --- a/src/redisx-cli.c +++ b/src/redisx-cli.c @@ -5,6 +5,16 @@ #define _POSIX_C_SOURCE 199309L ///< for nanosleep() +// We'll use gcc major version as a proxy for the glibc library to decide which feature macro to use. +// gcc 5.1 was released 2015-04-22... +#ifndef __GNUC__ +# define _DEFAULT_SOURCE ///< strcasecmp() feature macro starting glibc 2.20 (2014-09-08) +#elif __GNUC__ >= 5 +# define _DEFAULT_SOURCE ///< strcasecmp() feature macro starting glibc 2.20 (2014-09-08) +#else +# define _BSD_SOURCE ///< strcasecmp() feature macro for glibc <= 2.19 +#endif + #include #include #include @@ -29,6 +39,9 @@ static char *delim = "\\n"; static char *groupDelim = "\\n"; static int attrib = 0; +static Redis *redis; +static RedisCluster *cluster; + static void printVersion(const char *name) { printf("%s %s\n", name, REDISX_VERSION_STRING); } @@ -56,10 +69,23 @@ static void printRESP(const RESP *resp) { } } -static void process(Redis *redis, const char **cmdargs, int nargs) { +static void process(const char **cmdargs, int nargs) { int status = X_SUCCESS; RESP *reply, *attr = NULL; + if(cluster) { + const char *key = cmdargs[1]; + + if(nargs > 3) if(strcasecmp("EVAL", cmdargs[0]) == 0 || strcasecmp("EVALSHA", cmdargs[0]) || strcasecmp("FCALL", cmdargs[0])) + key = cmdargs[3]; + + redis = redisxClusterGetShard(cluster, key); + if(!redis) { + fprintf(stderr, "ERROR! No suitable cluster node found for transaction."); + return; + } + } + reply = redisxArrayRequest(redis, cmdargs, NULL, nargs, &status); if(!status && attrib) attr = redisxGetAttributes(redis); @@ -103,18 +129,18 @@ static int interactive(Redis *redis) { for(;;) { char *line = readline(prompt); - char **args; + const char **args; int nargs; if(!line) continue; if(strcmp("quit", line) == 0 || strcmp("exit", line) == 0) break; - poptParseArgvString(line, &nargs, (const char ***) &args); + poptParseArgvString(line, &nargs, &args); if(args) { if(nargs > 0) { - process(redis, (const char **) args, nargs); + process(args, nargs); add_history(line); } free(args); @@ -164,15 +190,16 @@ static char *readScript(const char *eval) { * @return The argument list to pass to Redis , of the form: * `EVAL