How does Redis handle requests?

Server. c determines the cluster mode and processes requests in a unified manner

int processCommand(client *c) { ... /* If cluster mode is enabled, the switch is performed here. * However, the node does not turn if: */ / If (server.cluster_enabled &&!) {if (server.cluster_enabled &&! (c->flags & CLIENT_MASTER) && ! (c->flags & CLIENT_LUA && server.lua_caller->flags & CLIENT_MASTER) && ! (c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0)) { int hashslot; If (server.cluster->state! = CLUSTER_OK) { flagTransaction(c); clusterRedirectClient(c,NULL,0,CLUSTER_REDIR_DOWN_STATE); return C_OK; } else {int error_code; clusterNode *n = getNodeByQuery(c, c->cmd, c->argv, c->argc, &hashslot, &error_code); If (n == NULL) {flagTransaction(c); if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) { addReplySds(c, sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n")); } else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) { /* The request spawns mutliple keys in the same slot, * but the slot is not "stable" currently as there is * a migration or import in progress. */ addReplySds(c, sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n")); } else { redisPanic("getNodeByQuery() unknown error."); } return REDIS_OK; } else if (n! = server.cluster->myself) { flagTransaction(c); // -<ASK or MOVED> <slot> < IP >:<port> // example -ask 10086 127.0.0.1:12345 addReplySds(c, sdSCATprintf (sdsempty(), "-%s %d %s:%d\r\n", (error_code == REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED", hashslot, n->ip, n->port)); return REDIS_OK; } // If this command is executed, the slot where the key resides is processed by the node // Or the client executes the no-parameter command}}... }Copy the code

Cluster. c searches for the Redis node corresponding to the key and returns to the node

clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, Int *error_code) {// initialize to NULL, // If the input command is a no-argument command, then n will continue to be NULL. ClusterNode *n = NULL; robj *firstkey = NULL; int multiple_keys = 0; multiState *ms, _ms; multiCmd mc; int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0; /* Set error code optimistically for the base case. */ if (error_code) *error_code = REDIS_CLUSTER_REDIR_NONE; /* We handle all the cases as if they were EXEC commands, so We have * a common code path for everything */ / Cluster can execute transactions, If (CMD ->proc == execCommand) {/* if REDIS_MULTI flag is. If (CMD ->proc == execCommand)  not set EXEC is just going to return an * error. */ if (! (c->flags & REDIS_MULTI)) return myself; ms = &c->mstate; } else { /* In order to have a single codepath create a fake Multi State * structure if the client is not in MULTI/EXEC state, this way * we have a single codepath below. */ ms = &_ms; _ms.commands = &mc; _ms.count = 1; mc.argv = argv; mc.argc = argc; mc.cmd = cmd; } /* Check that all the keys are in the same hash slot, and obtain this * slot and the node associated. */ for (i = 0; i < ms->count; i++) { struct redisCommand *mcmd; robj **margv; int margc, *keyindex, numkeys, j; mcmd = ms->commands[i].cmd; margc = ms->commands[i].argc; margv = ms->commands[i].argv; Keyindex = getKeysFromCommand(MCMD,margv,margc,&numkeys); For (j = 0; j < numkeys; j++) { robj *thiskey = margv[keyindex[j]]; Int thisslot = keyHashSlot((char*) thisKey -> PTR, sdslen(thiskey-> PTR)); If (firstkey == NULL) {if (firstkey == NULL) {// this is the firstkey to be processed in the transaction. slot = thisslot; n = server.cluster->slots[slot]; redisAssertWithInfo(c,firstkey,n ! = NULL); /* If we are migrating or importing this slot, we need to check * if we have all the keys in the request (the only way we * can safely serve the request, otherwise we return a TRYAGAIN * error). To do so we set the importing/migrating state and * increment a counter for every missing key. */ if (n == myself && server.cluster->migrating_slots_to[slot] ! = NULL) { migrating_slot = 1; } else if (server.cluster->importing_slots_from[slot] ! = NULL) { importing_slot = 1; } // Non-firstKey} else {// But each key pair is checked. // if (! EqualStringObjects (firstKey, thisKey) {// If (slot! = thisslot) { getKeysFreeResult(keyindex); if (error_code) *error_code = REDIS_CLUSTER_REDIR_CROSS_SLOT; return NULL; } else {// Whether there is a compound key multiple_keys = 1; } } } /* Migarting / Improrting slot? Count keys we don't have. */ if ((migrating_slot || importing_slot) && lookupKeyRead(&server.db[0],thiskey) == NULL) { missing_keys++; } } getKeysFreeResult(keyindex); } /* No key at all in command? then we can serve the request * without redirections or errors. */ if (n == NULL) return myself; /* Return the hashslot by reference. */ if (hashslot) *hashslot = slot; /* This request is about a slot we are migrating into another instance? * Then if we have all the keys. */ /* If we don't have all the keys and we are migrating the slot, send * an ASK redirection. */ if (migrating_slot && missing_keys) { if (error_code) *error_code = REDIS_CLUSTER_REDIR_ASK; return server.cluster->migrating_slots_to[slot]; } /* If we are receiving the slot, and the client correctly flagged the * request as "ASKING", we can serve the request. However if the request * involves multiple keys and we don't have them all, the only option is * to send a TRYAGAIN error. */ if (importing_slot && (c->flags & REDIS_ASKING || cmd->flags & REDIS_CMD_ASKING)) { if (multiple_keys && missing_keys) { if (error_code) *error_code = REDIS_CLUSTER_REDIR_UNSTABLE; return NULL; } else { return myself; } } /* Handle the read-only client case reading from a slave: if this * node is a slave and the request is about an hash slot our master * is serving, we can reply without redirection. */ if (c->flags & REDIS_READONLY && cmd->flags & REDIS_CMD_READONLY && nodeIsSlave(myself) && myself->slaveof == n) { return myself; } /* Base case: just return the right node. However if this node is not * myself, set error_code to MOVED since we need to issue a rediretion. */ if (n ! = myself && error_code) *error_code = REDIS_CLUSTER_REDIR_MOVED; // Return the node responsible for processing slots n return n; }Copy the code

Calculate the hashSlot using the CRC16 algorithm

Special syntax: {key_with_hash} key_without_hash If the key has a “{}” part, the “{}” part is used to hash to locate the key of the same Redis node. Note: only the first “{}” is useful

unsigned int keyHashSlot(char *key, int keylen) {
    int s, e; /* start-end indexes of { and } */

    for (s = 0; s < keylen; s++)
        if (key[s] == '{') break;

    /* No '{' ? Hash the whole key. This is the base case. */
    if (s == keylen) return crc16(key,keylen) & 0x3FFF;

    /* '{' found? Check if we have the corresponding '}'. */
    for (e = s+1; e < keylen; e++)
        if (key[e] == '}') break;

    /* No '}' or nothing betweeen {} ? Hash the whole key. */
    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;

    /* If we are here there is both a { and a } on its right. Hash
     * what is in the middle between { and }. */
    return crc16(key+s+1,e-s-1) & 0x3FFF;
}

Copy the code

It can be analyzed from the above code:

  1. When the command reaches the Redis server, Redis checks for cluster status.
  2. If the status is not cluster, run the command directly
  3. If it is in the cluster state, it will find the corresponding hash slot based on the key and then find the corresponding Redis server for forwarding
  4. Note that Redis will only match the hash slot of the first key
  5. However, this command will fail if other keys hash to different slots. (This is important because many times we use compound commands for aspects, or lua scripts. This is common in redis standalone to cluster. What’s more, after the communication with operation and maintenance sister, even if we use Tencent cloud and Ali Cloud redis, they are only compatible with compound commands, not compatible with Lua scripts (later).
  6. If the key has a “{}” part, the “{}” part is hashed to locate the key to the same Redis node. (If you really want to compound keys, consider this. Lua scripts are also available.)

Redis common command for single key

Some common commands will be fetched to the corresponding slot according to the firstkey value and forwarded to the corresponding Redis service

The server is forwarded to the corresponding slot based on the firstkey value

Redis 49.234.101.179:6376> Set name zhangsan directly forwarded to the corresponding slot server -> Redirected to slot [5798] located at 49.234.101.179:6372 49.234.101.179:6372> lpush list value1 directly forwarded to the server of the corresponding slot -> Redirected to slot [12291] located at 49.234.101.179:6373 (INTEGER) 1 127.0.0.1:6376> sadd set value1 -> Redirected to slot [2964] located at 49.234.101.179:6371 (INTEGER) 1 49.234.101.179:6371> hset map1 key1 Value1 Directly forward to the corresponding slot -> Redirected to slot [8740] Located at 49.234.101.179:6372 (integer) 1Copy the code

Redis multiple key compound command

Redis 127.0.0.1:6379> MGET KEY1 KEY2.. KEYNCopy the code

This command fails if other keys hash to slot inconsistencies (this is important, because many times when we use compound commands for aspects an exception occurs:

(error) CROSSSLOT Keys in request don't hash to the same slot
Copy the code

When defining data structures that require Lua, prefix the key with {key} as the slot to be placed in

{key} will be allocated to the same slot, which does not achieve the same effect.

The official explanation:

Calculating the hash slot can implement hash tags, with one exception. Hash tags are a way of making sure that both keys are in the same hash slot. Hash tags may be used in the future, for example to allow some multi-key operations if the cluster is stable (no shard operations are being performed). To implement hash labels, hash slots are computed in a different way. Basically, if a key contains a "{... } ", only the string between {and} will be used to hash to get the hash slot. But since there can be more than one {or}, the algorithm is as follows: If the key contains a {character. There will be a} to the right of {. There will be one or more characters between {and}, and the first} must appear after the first {.Copy the code

Redis Lua script

Lua scripts are essentially multi-key composite commands, so they are affected along with reused key commands

Redis distributed lock why our Redis distributed lock implementation is also lua script, also have multiple parameters, it is not affected?

Redisson Lua script implements distributed lock: local key = KEYS[1]; Key local threadId = ARGV[1]; Local releaseTime = ARGV[2]; Call ('exists', key, threadId, '1') if(redis. Call ('exists', key) == 0) then Call ('expire', key, releaseTime); Return 1; -- returns the result end; Call ('hincrby', key, threadId, '1') if('hexists', key, threadId, '1') then Redis. Call ('expire', key, releaseTime); Return 1; -- returns the result end; return 0; The lock was not acquired by the userCopy the code

“If the slots calculated by the hash algorithm for the firstkey and other keys are different, the command fails to be executed”?

The answer is no:

  • Because there’s only one key in this code
  • local key = KEYS[1]; The first argument, the key of the lock
  • local threadId = ARGV[1]; — The second argument, thread-unique identifier
  • local releaseTime = ARGV[2]; — The third parameter, the automatic release time of the lock

The second and third arguments are in the ARGV array.

Refer to the article: for class network “advanced Redis applied advanced courses One-stop Redis solution “coding.imooc.com/learn/list/…

What are the advantages and limitations of Redis Cluster? www.cnblogs.com/lovezbs/p/1…

Redis 3.0 annotated source gitee.com/ckl111/redi…

Redis 6.0 source github.com/redis/redis…

Redis cluster specification: redis.cn/topics/clus…

Redis clustering tutorial: redis.cn/topics/clus…