Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to deeply understand Redis transactions

2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Database >

Share

Shulou(Shulou.com)05/31 Report--

This article focuses on "how to deeply understand Redis affairs". Interested friends may wish to have a look at it. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "how to deeply understand Redis affairs".

Redis can be regarded as a database system of NoSQL type, and Redis also provides transactions, but there are both similarities and differences between Redis and traditional relational database transactions. Because the architecture of Redis is based on the multiplexed IO interface of the operating system, the main processing flow is a single thread, so for a complete command, its processing is atomic, but if multiple commands are needed as an indivisible processing sequence, transactions are needed.

Redis transactions have the following characteristics:

The sequence of commands in a transaction is atomic when executed, that is, it is not interrupted by commands from other clients. This is similar to the properties of traditional database transactions.

Although the command sequence in the Redis transaction is executed by atoms, the command sequence execution in the transaction can be partially successful, in which case the Redis transaction does not perform a rollback operation. This is different from the transactions of traditional relational databases.

Although Redis has two data persistence mechanisms: RDB and AOF, its design goal is an efficient cache system. Redis transactions only guarantee that the results of operations in its command sequence are committed to memory and do not guarantee persistence to disk files. Further, Redis transactions have nothing to do with the RDB persistence mechanism, because the RDB mechanism is a full snapshot of in-memory data structures. Because the AOF mechanism is an incremental persistence, the sequence of commands in the transaction is committed to the AOF cache. However, the AOF mechanism writes its cache to disk files is determined by its configured implementation strategy, and has nothing to do with Redis transactions.

Redis transaction API

From a macro point of view, after the Redis transaction starts, the subsequent operation commands and their operation data are cached, and when the transaction is committed, the cached command sequence is executed atomically.

Since version 2.2, Redis provides an optimistic locking mechanism. With this mechanism, Redis transaction commits become the conditional execution of the transaction. Specifically, if the optimistic lock fails, the command sequence in the transaction is discarded when the transaction commits. If the optimistic lock is successful, the command sequence will be executed when the transaction commits. Of course, the command sequence of the transaction can be executed unconditionally when the transaction is committed without using optimistic locking mechanism.

The Redis transaction involves five commands: MULTI, EXEC, DISCARD, WATCH, and UNWATCH:

The command at the beginning of the transaction is MULTI, which returns an OK prompt. Redis does not support transaction nesting. Executing multiple MULTI commands has the same effect as executing once. When a nested MULTI command is executed, Redis simply returns an error message.

EXEC is the commit command of a transaction, and the sequence of commands in the transaction will be executed (or not executed, such as optimistic lock failure, etc.). This command returns an array of responses whose contents correspond to the results of the command execution in the transaction.

The WATCH command starts to execute optimistic locks. The parameter of this command is key (there can be more than one). Redis associates the client object executing the WATCH command with the key. If other clients modify these key, the client executing the WATCH command will be set the optimistic lock failure flag. The command must be executed before the transaction starts, that is, the WATCH command is executed before the MULTI command is executed, otherwise the execution is invalid and an error message is returned.

The UNWATCH command cancels the optimistic lock key of the current client object, and the transaction commit of the client object becomes unconditional.

The DISCARD command ends the transaction and discards all command sequences.

It should be noted that when the EXEC command and the DISCARD command end the transaction, the UNWATCH command is called to cancel all optimistic locks on the client object, key. Exe.

Unconditional submission

If an optimistic lock is not used, the transaction commits unconditionally. Here is an example of transaction execution:

Multi + OK incr key1 + QUEUED set key2 val2 + QUEUED exec * 2: 1 + OK

When the client starts the transaction, the subsequent commands will be cached by Redis, and Redis will return the response prompt string QUEUED. When the EXEC commit transaction is executed, the cached commands are executed in turn, and the execution result of the command sequence is returned.

Error handling of transactions

The transaction commit command EXEC may fail, and there are three types of failure scenarios:

The command cache executed by the client failed before the transaction was committed. For example, the syntax error of the command (the wrong number of command parameters, unsupported commands, etc.). If this type of error occurs, Redis returns a response with an error message to the client.

When a transaction commits, the previously cached command may fail.

Due to optimistic lock failure, all previously cached command sequences will be discarded when the transaction commits.

When the first failure occurs, when the client executes the transaction commit command EXEC, all command sequences in the transaction are discarded. Here is an example:

Multi + OK incr num1 num2-ERR wrong number of arguments for 'incr' command set key1 val1 + QUEUED exec-EXECABORT Transaction discarded because of previous errors.

The command incr num1 num2 is not cached successfully because the incr command allows only one parameter, which is a command with syntax error. Redis cannot successfully cache the command and send an error prompt response to the client. The next set key1 val1 command was cached successfully. Finally, when the transaction commits, all command sequences in the transaction are discarded because of a command cache failure.

If all command sequences in the transaction are cached successfully, execution of the cached command may still fail when the transaction is committed. However, Redis does not do any rollback remediation on the transaction. Here is an example of this:

Multi + OK set key1 val1 + QUEUED lpop key1 + QUEUED incr num1 + QUEUED exec * 3 + OK-WRONGTYPE Operation against a key holding the wrong kind of value: 1

All command sequences are cached successfully, but when the transaction is committed, the command set key1 val1 and incr num1 execute successfully, and Redis saves the result of its execution, but the command lpop key1 execution fails.

Optimistic locking mechanism

When a Redis transaction is used with an optimistic lock, the transaction becomes conditional commit.

With regard to optimistic locks, it is important to note that:

The WATCH command must be executed before the MULTI command. The WATCH command can be executed multiple times.

The WATCH command can specify multiple key of optimistic lock. If any key is changed by other clients during the transaction, the optimistic lock of the current client fails, and all command sequences will be discarded when the transaction commits.

Multiple client WATCH commands can specify the same key.

After the WATCH command specifies the optimistic lock, you can then execute the MULTI command to enter the transaction context, or you can execute other commands between the WATCH command and the MULTI command. Depending on the requirements of the scenario, commands that are not in the transaction will be executed immediately.

If the key of the optimistic lock specified by the WATCH command is changed by the current client, the optimistic lock will not fail when the transaction commits.

Optimistic locks will not fail if the key of the optimistic lock specified by the WATCH command has a timeout attribute and the key times out after the WATCH command and before the transaction commit command EXEC. If the key is modified by another client object, the optimistic lock fails.

An example of a transaction that implements an optimistic locking mechanism:

Rpush list v1 v2 v3: 3 watch list + OK multi + OK lpop list + QUEUED exec * 1 $2 v1

Here is another example where the optimistic lock is changed by the current client and the transaction commits successfully:

Watch num + OK multi + OK incr num + QUEUED exec * 1: 2

When Redis transactions are used with optimistic locks, you can construct more complex logic that cannot be completed by a single Redis command.

Source code implementation mechanism of Redis transaction

First, the MULTI command at the beginning of the transaction executes the function multiCommand, which is implemented as (multi.c):

Void multiCommand (redisClient * c) {if (c-> flags & REDIS_MULTI) {addReplyError (c, "MULTI calls can not be nested"); return;} c-> flags | = REDIS_MULTI; addReply (cMagneShared.ok);}

This command simply adds a REDIS_MULTI flag to the current client object to indicate that the client has entered the transaction context.

After the client enters the transaction context, subsequent commands will be cached. The function processCommand is the entry function for Redis to handle client commands, which is implemented as (redis.c):

Int processCommand (redisClient * c) {/ * The QUIT command is handled separately. Normal command procs will * go through checking for replication and QUIT will cause trouble * when FORCE_REPLICATION is enabled and would be implemented in * a regular command proc. * / if (! strcasecmp (c-> argv [0]-> ptr, "quit") {addReply (cMagneShared.ok); c-> flags | = REDIS_CLOSE_AFTER_REPLY; return REDIS_ERR;} / * Now lookup the command and check ASAP about trivial error conditions * such as wrong arity, bad command name and so forth. * / c-> ccmd = c-> lastcmd = lookupCommand (c-> argv [0]-> ptr); if (! c-> cmd) {flagTransaction (c); addReplyErrorFormat (c, "unknown command'% s'", (char*) c-> argv [0]-> ptr); return REDIS_OK } else if ((c-> cmd- > arity > 0 & & c-> cmd- > arity! = c-> argc) | | (c-> argc)

< -c->

Cmd- > arity) {flagTransaction (c); addReplyErrorFormat (c, "wrong number of arguments for'% s' command", c-> cmd- > name); return REDIS_OK;} / * Check if the user is authenticated * / if (server.requirepass & & c-> authenticated & & c-> cmd- > proc! = authCommand) {flagTransaction (c) AddReply (cmaine shared.noautherr); return REDIS_OK;} / * Handle the maxmemory directive. * * First we try to free some memory if possible (if there are volatile * keys in the dataset) If there are not the only thing we can do * is returning an error. * / if (server.maxmemory) {int retval = freeMemoryIfNeeded (); / * freeMemoryIfNeeded may flush slave output buffers. This may result * into a slave, that may be the active client, to be freed. * / if (server.current_client = = NULL) return REDIS_ERR; / * It was impossible to free enough memory, and the command the client * is trying to execute is denied during OOM conditions? Error. * / if ((c-> cmd- > flags & REDIS_CMD_DENYOOM) & & retval = = REDIS_ERR) {flagTransaction (c); addReply (c, shared.oomerr); return REDIS_OK;}} / * Don't accept write commands if there are problems persisting on disk * and if this is a master instance. * / if (server.stop_writes_on_bgsave_err & & server.saveparamslen > 0 & & server.lastbgsave_status = = REDIS_ERR) | | server.aof_last_write_status = = REDIS_ERR) & & server.masterhost = = NULL & & (c-> cmd- > flags & REDIS_CMD_WRITE | | c-> cmd- > proc = = pingCommand)) {flagTransaction (c) If (server.aof_last_write_status = = REDIS_OK) addReply (c, shared.bgsaveerr); else addReplySds (c, sdscatprintf (sdsempty (), "- MISCONF Errors writing to the AOF file:% s\ r\ n", strerror (server.aof_last_write_errno); return REDIS_OK } / * Don't accept write commands if there are not enough good slaves and * user configured the min-slaves-to-write option. * / if (server.masterhost = = NULL & & server.repl_min_slaves_to_write & & server.repl_min_slaves_max_lag & & c-> cmd- > flags & REDIS_CMD_WRITE & & server.repl_good_slaves_count

< server.repl_min_slaves_to_write) { flagTransaction(c); addReply(c, shared.noreplicaserr); return REDIS_OK; } /* Don't accept write commands if this is a read only slave. But * accept write commands if this is our master. */ if (server.masterhost && server.repl_slave_ro && !(c->

Flags & REDIS_MASTER) & & c-> cmd- > flags & REDIS_CMD_WRITE) {addReply (c, shared.roslaveerr); return REDIS_OK } / * Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub * / if (c-> flags & REDIS_PUBSUB & & c-> cmd- > proc! = pingCommand & & c-> cmd- > proc! = subscribeCommand & & c-> cmd- > proc! = unsubscribeCommand & & c-> cmd- > proc! = psubscribeCommand & & c > cmd- > proc! = punsubscribeCommand) {addReplyError (c) "only (P) SUBSCRIBE / (P) UNSUBSCRIBE / QUIT allowed in this context") Return REDIS_OK;} / * Only allow INFO and SLAVEOF when slave-serve-stale-data is no and * we are a slave with a broken link with master. * / if (server.masterhost & & server.repl_state! = REDIS_REPL_CONNECTED & & server.repl_serve_stale_data = = 0 & & (c-> cmd- > flags & REDIS_CMD_STALE)) {flagTransaction (c); addReply (c, shared.masterdownerr); return REDIS_OK;} / * Loading DB? Return an error if the command has not the * REDIS_CMD_LOADING flag. * / if (server.loading & &! (c-> cmd- > flags & REDIS_CMD_LOADING)) {addReply (c, shared.loadingerr); return REDIS_OK;} / * Lua script too slow? Only allow a limited number of commands. * / if (server.lua_timedout & & c-> cmd- > proc! = authCommand & & c-> cmd- > proc! = replconfCommand & &! (c-> cmd- > proc = = shutdownCommand & & c-> argc = = 2 & & tolower (char*) c-> argv [1]-> ptr) [0]) = ='n') & &! (c-> cmd- > proc = = scriptCommand & & c-> argc = = 2 & & tolower (char*) c-> argv [1]-> ptr) [0]) = ='k')) {flagTransaction (c) AddReply (c, shared.slowscripterr); return REDIS_OK;} / * Exec the command * / if (c-> flags & REDIS_MULTI & c-> cmd- > proc! = execCommand & & c-> cmd- > proc! = discardCommand & & c-> cmd- > proc! = multiCommand & & c-> cmd- > proc! = watchCommand) {queueMultiCommand (c); addReply (cmore shared.queued) } else {call (c _ listLength (server.ready_keys)) handleClientsBlockedOnLists (); handleClientsBlockedOnLists ();} return REDIS_OK;}

Line145:151 when the client is in the transaction context, if the client receives a non-transactional command (MULTI, EXEC, WATCH, DISCARD), then call queueMultiCommand to cache the command, and then send a successful response to the client.

In the function processCommand, before caching commands, if it is checked that the command sent by the client does not exist or the number of command parameters is incorrect, the function flagTransaction will be called to mark the command cache failure. In other words, in the function processCommand, all conditional branches that call the function flagTransaction return a failure response.

The implementation of the cached command function queueMultiCommand is (multi.c):

/ * Add a new command into the MULTI commands queue * / void queueMultiCommand (redisClient * c) {multiCmd * mc; int j; c-> mstate.commands = zrealloc (c-> mstate.commands, sizeof (multiCmd) * (c-> mstate.count+1)); mc = c-> mstate.commands+c- > mstate.count; mc- > ccmd = c-> cmd; mc- > argc = c-> argc; mc- > argv = zmalloc (sizeof (robj*) * c-> argc) Memcpy (mc- > argv,c- > argv,sizeof (robj*) * c-> argc); for (j = 0; j)

< c->

Argc; jacks +) incrRefCount (mc- > argv [j]); c-> mstate.count++;}

In the context of a transaction, use the multiCmd structure to cache commands, which is defined as (redis.h):

/ * Client MULTI/EXEC state * / typedef struct multiCmd {robj * * argv; int argc; struct redisCommand * cmd;} multiCmd

The argv field points to the memory address of the command parameters, argc is the number of command parameters, and cmd is the command description structure, including name and function pointer.

The memory space of command parameters has been dynamically allocated to the argv field of the client object. The argv field of the multiCmd structure points to the argv of the client object redisClient.

When the command cannot be cached, the function flagTransaction is called, which is implemented as (multi.c):

/ * Flag the transacation as DIRTY_EXEC so that EXEC will fail. * Should be called every time there is an error while queueing a command. * / void flagTransaction (redisClient * c) {if (c-> flags & REDIS_MULTI) c-> flags | = REDIS_DIRTY_EXEC;}

This function sets the REDIS_DIRTY_EXEC flag in the client object. If this flag is set, the command sequence will be discarded when the transaction commits.

Finally, when the transaction commits, the function processCommand will call call (cdirection redirected call full);, which is implemented as (redis.c):

/ * Call () is the core of Redis execution of a command * / void call (redisClient * c, int flags) {long long dirty, start, duration; int cclient_old_flags = c-> flags; / * Sent the command to clients in MONITOR mode, only if the commands are * not generated from reading an AOF. * / if (listLength (server.monitors) & &! server.loading & &! (c-> cmd- > flags & (REDIS_CMD_SKIP_MONITOR | REDIS_CMD_ADMIN)) {replicationFeedMonitors (c-db- > id,c- > argv,c- > argc);} / * Call the command. * / c-> flags & = ~ (REDIS_FORCE_AOF | REDIS_FORCE_REPL); redisOpArrayInit (& server.also_propagate); dirty = server.dirty; start = ustime (); c-> cmd- > proc (c); duration = ustime ()-start; dirty = server.dirty-dirty; if (dirty

< 0) dirty = 0; /* When EVAL is called loading the AOF we don't want commands called * from Lua to go into the slowlog or to populate statistics. */ if (server.loading && c->

Flags & REDIS_LUA_CLIENT) flags & = ~ (REDIS_CALL_SLOWLOG | REDIS_CALL_STATS); / * If the caller is Lua, we want to force the EVAL caller to propagate * the script if the command flag or client flag are forcing the * propagation. * / if (c-> flags & REDIS_LUA_CLIENT & & server.lua_caller) {if (c-> flags & REDIS_FORCE_REPL) server.lua_caller- > flags | = REDIS_FORCE_REPL; if (c-> flags & REDIS_FORCE_AOF) server.lua_caller- > flags | = REDIS_FORCE_AOF } / * Log the command into the Slow log if needed, and populate the * per-command statistics that we show in INFO commandstats. * / if (flags & REDIS_CALL_SLOWLOG & & c-> cmd- > proc! = execCommand) {char * latency_event = (c-> cmd- > flags & REDIS_CMD_FAST)? "fast-command": "command"; latencyAddSampleIfNeeded (latency_event,duration/1000); slowlogPushEntryIfNeeded (c-> argv,c- > argc,duration);} if (flags & REDIS_CALL_STATS) {c-> cmd- > microseconds + = duration; c-> cmd- > calls++ } / * Propagate the command into the AOF and replication link * / if (flags & REDIS_CALL_PROPAGATE) {int flags = REDIS_PROPAGATE_NONE; if (c-> flags & REDIS_FORCE_REPL) flags | = REDIS_PROPAGATE_REPL; if (c-> flags & REDIS_FORCE_AOF) flags | = REDIS_PROPAGATE_AOF; if (dirty) flags | = (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF) If (flags! = REDIS_PROPAGATE_NONE) propagate (c-> cmd,c- > db- > id,c- > argv,c- > argc,flags);} / * Restore the old FORCE_AOF/REPL flags, since call can be executed * recursively. * / c-> flags & = ~ (REDIS_FORCE_AOF | REDIS_FORCE_REPL); c-> flags | = client_old_flags & (REDIS_FORCE_AOF | REDIS_FORCE_REPL); / * Handle the alsoPropagate () API to handle commands that want to propagate * multiple separated commands. * / if (server.also_propagate.numops) {int j; redisOp * rop; for (j = 0; j)

< server.also_propagate.numops; j++) { rop = &server.also_propagate.ops[j]; propagate(rop->

Cmd, rop- > dbid, rop- > argv, rop- > argc, rop- > target);} redisOpArrayFree (& server.also_propagate);} server.stat_numcommands++;}

Call the specific command function in the function call by executing c-> cmd- > proc (c). The execution function corresponding to the transaction commit command EXEC is execCommand, which is implemented as (multi.c):

Void execCommand (redisClient * c) {int j; robj * * orig_argv; int orig_argc; struct redisCommand * orig_cmd; int must_propagate = 0; / * Need to propagate MULTI/EXEC to AOF / slaves? * / if (! (c-> flags & REDIS_MULTI)) {addReplyError (c, "EXEC without MULTI"); return } / * Check if we need to abort the EXEC because: * 1) Some WATCHed key was touched. * 2) There was a previous error while queueing commands. * A failed EXEC in the first case returns a multi bulk nil object * (technically it is not an error but a special behavior), while * in the second an EXECABORT error is returned. * / if (c-> flags & (REDIS_DIRTY_CAS | REDIS_DIRTY_EXEC)) {addReply (c, c-> flags & REDIS_DIRTY_EXEC? Shared.execaborterr: shared.nullmultibulk); discardTransaction (c); goto handle_monitor;} / * Exec all the queued commands * / unwatchAllKeys (c); / * Unwatch ASAP otherwise we'll waste CPU cycles * / orig_argv = c-> argv; orig_argc = c-> argc; orig_cmd = c-> cmd AddReplyMultiBulkLen (c-> mstate.count); for (j = 0; j)

< c->

Mstate.count; jacks +) {c-> argc = c-> mstate.commands.argc; c-> argv = c-> mstate.commands.argv; c-> ccmd = c-> mstate.commands.cmd; / * Propagate a MULTI request once we encounter the first write op. * This way we'll deliver the MULTI/..../EXEC block as a whole and * both the AOF and the replication link will have the same consistency * and atomicity guarantees. * / if (! must_propagate & &! (c-> cmd- > flags & REDIS_CMD_READONLY)) {execCommandPropagateMulti (c); must_propagate = 1;} call (c Commands may alter argc/argv, restore mstate. * / c-> mstate.commands.argc = c-> argc; c-> mstate.commands.argv = c-> argv; c-> mstate.commands.cmd = c-> cmd;} c-> argv = orig_argv; c-> argc = commandsc-> cmd = orig_cmd; discardTransaction (c); / * Make sure the EXEC command will be propagated as well if MULTI * was already propagated. * / if (must_propagate) server.dirty++; handle_monitor: / * Send EXEC to clients waiting data from MONITOR. We do it here * since the natural order of commands execution is actually: * MUTLI, EXEC,... Commands inside transaction... * Instead EXEC is flagged as REDIS_CMD_SKIP_MONITOR in the command * table, and we do it here with correct ordering. * / if (listLength (server.monitors) & &! server.loading) replicationFeedMonitors (cJournal server.monitorsJournal c-> db- > id,c- > argv,c- > argc);}

LINE8:11 checks whether the EXEC command and the MULTI command are used in pairs. It is pointless to execute the EXEC command alone.

LINE19:24 checks whether the client object has a REDIS_DIRTY_CAS or REDIS_DIRTY_EXEC flag, and if so, it calls the function discardTransaction to discard the command sequence and return a failed response to the client.

If no errors are detected, execute unwatchAllKeys (c) first; cancel all optimistic locks on the client, key.

LINE32:52 executes the cached command sequence in turn. Here are two things to note:

Transactions may need to be synchronized to the AOF cache or replica backup node. If the sequence of commands in a transaction is read, there is no need to synchronize to AOF and replica. If the command sequence of the transaction contains write commands, MULTI, EXEC and related write commands synchronize to AOF and replica. According to the condition of LINE41:44, execute execCommandPropagateMulti (c); ensure MULTI command synchronization, LINE59 check whether EXEC command needs synchronization, that is, MULTI command and EXEC command must ensure pairing synchronization. Exec command synchronization is executed in the call of the function LINE62propagate (c-> cmd,c- > db- > id,c- > argv,c- > argc,flags), and the specific write commands are synchronized by their respective execution functions.

When the command sequence is executed here, by executing call (crecoverable call full); so the call function is called recursively.

Therefore, to sum up, the essence of Redis transaction is to execute the cached command sequence in an uninterruptible manner and save the results to memory cache.

When a transaction commits, the discarding command sequence calls the function discardTransaction, which is implemented as (multi.c):

Void discardTransaction (redisClient * c) {freeClientMultiState (c); initClientMultiState (c); c-> flags & = ~ (REDIS_MULTI | REDIS_DIRTY_CAS | REDIS_DIRTY_EXEC); unwatchAllKeys (c);}

This function calls freeClientMultiState to free the memory of the multiCmd object. Call initClientMultiState to reset the cache command management structure of the client object. Call unwatchAllKeys to cancel the optimistic lock for the client.

The WATCH command executes optimistic locks, and its corresponding execution function is watchCommand, which is implemented as (multi.c):

Void watchCommand (redisClient * c) {int j; if (c-> flags & REDIS_MULTI) {addReplyError (c, "WATCH inside MULTI is not allowed"); return;} for (j = 1; j)

< c->

Argc; jacks +) watchForKey (cMagnec-> argv [j]); addReply (cMagneShared.ok);}

Then call the function watchForKey, which is implemented as (multi.c):

/ * Watch for the specified key * / void watchForKey (redisClient * c, robj * key) {list * clients = NULL; listIter li; listNode * ln; watchedKey * wk; / * Check if we are already watching for this key * / listRewind (c-> watched_keys,&li); while ((ln = listNext (& li) {wk = listNodeValue (ln) If (wk- > db = = c-> db & & equalStringObjects (key,wk- > key) return; / * Key already watched * /} / * This key is not already watched in this DB. Let's add it * / clients = dictFetchValue (c-> db- > watched_keys,key); if (! clients) {clients = listCreate (); dictAdd (c-> db- > watched_keys,key,clients); incrRefCount (key);} listAddNodeTail (clients,c); / * Add the new key to the list of keys watched by this client * / wk = zmalloc (sizeof (* wk)) Wk- > keykey = key; wk- > db = c-> db; incrRefCount (key); listAddNodeTail (c-> watched_keys,wk);}

The key of optimistic lock is saved not only in the watched_keys linked list of its client object, but also in the watched_keys hash table of the global database object.

LINE10:14 checks whether the key already exists in the linked list of client objects, and if it already exists, it directly returns .LINE16 returns the client object linked list corresponding to the key in the global database. If the linked list does not exist, other clients do not use the key as an optimistic lock. If the linked list exists, other clients have used the key as an optimistic lock. LINE22 records the current client object in the linked list corresponding to the key. LINE28 records the key in the current client's key linked list.

After the current client performs an optimistic lock, write commands from other clients may modify the key value. All commands with write attributes execute the function signalModifiedKey, which is implemented as (db.c):

Void signalModifiedKey (redisDb * db, robj * key) {touchWatchedKey (db,key);}

The implementation of the function touchWatchedKey is (multi.c):

/ * "Touch" a key, so that if this key is being WATCHed by some client the * next EXEC will fail. * / void touchWatchedKey (redisDb * db, robj * key) {list * clients; listIter li; listNode * ln; if (dictSize (db- > watched_keys) = = 0) return; clients = dictFetchValue (db- > watched_keys, key); if (! clients) return; / * Mark all the clients watching this key as REDIS_DIRTY_CAS * / / * Check if we are already watching for this key * / listRewind (clients,&li) While ((ln = listNext (& li) {redisClient * c = listNodeValue (ln); c-> flags | = REDIS_DIRTY_CAS;}}

The statement if (dictSize (db- > watched_keys) = = 0) return; checks whether the hash table watched_keys in the global database is empty. If it is empty, it means that no client executes the WATCH command and returns directly. If the hash table is not empty, retrieve the client linked list structure corresponding to the key and set the REDIS_DIRTY_CAS flag for each client object in the linked list. Earlier, in the execution command of EXEC, the condition was determined, and if the client object has this flag, the command sequence in the transaction is discarded.

When the EXEC, DISCARD, UNWATCH commands are executed, and when the client ends the connection, the optimistic lock is cancelled, and the function unwatchAllKeys is eventually executed, which is implemented as (multi.c):

/ * Unwatch all the keys watched by this client. To clean the EXEC dirty * flag is up to the caller. * / void unwatchAllKeys (redisClient * c) {listIter li; listNode * ln; if (listLength (c-> watched_keys) = = 0) return; listRewind (c-> watched_keys,&li); while ((ln = listNext (& li) {list * clients; watchedKey * wk / * Lookup the watched key-> clients list and remove the client * from the list * / wk = listNodeValue (ln); clients = dictFetchValue (wk- > db- > watched_keys, wk- > key); redisAssertWithInfo (cmeme NULLMAG! = NULL); listDelNode (clients,listSearchKey (clients,c)) / * Kill the entry at all if this was the only client * / if (listLength (clients) = = 0) dictDelete (wk- > db- > watched_keys, wk- > key); / * Remove this watched key from the client- > watched list * / listDelNode (c-> watched_keys,ln); decrRefCount (wk- > key); zfree (wk);}}

The statement if (listLength (c-> watched_keys) = = 0) return; determines that if the watched_keys linked list of the current client object is empty, the current client does not execute the WATCH command and returns directly. If the linked list is not empty, the key in the linked list is traversed in turn, and the key is deleted from the linked list. At the same time, the client linked list corresponding to the key in the hash table watched_keys in the global database is obtained and the current client object is deleted.

At this point, I believe you have a deeper understanding of "how to deeply understand Redis transactions". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Database

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report