Relational databases generally support transactions, which, in simple terms, allow the batch execution of request submissions with a guarantee of success or failure. For Redis, it also provides simple implementation and support for transactions. See below.

Transaction implementation

Redis implements transactions through the watch, multi, exec commands. It implements a mechanism for executing a series of commands once, in sequence, with no other changes during execution.

Transaction command

watch

Data is stored

Watch monitors the state of a Key and marks it to ensure that it will not be modified by other operations during the transaction to damage the integrity of the transaction.

typedef struct redisDb {
	// Omit other information
	
    // The key being monitored by the WATCH command
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */

} redisDb;
Copy the code

As above, the Key about being monitored is stored inredisDbthewatched_keysIn the dictionary array, key1 and key2 are the monitored keys, and the values in the dictionary are as followsThe liststorageredisClientStructure references, which are mapped to indicate which clients are listening for which keys.

triggering

/* "Touch" a key, so that if this key is being WATCHed by some client the * next EXEC will fail. If the key is being monitored by some client, * then the transaction will fail when the client executes EXEC. * /
void touchWatchedKey(redisDb *db, robj *key) {
    list *clients;
    listIter li;
    listNode *ln;

    // The dictionary is empty and no keys are monitored
    if (dictSize(db->watched_keys) == 0) return;

    // Get all the clients monitoring this key
    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 */
    // Iterate over all clients and turn on their REDIS_DIRTY_CAS flag
    listRewind(clients,&li);
    while((ln = listNext(&li))) { redisClient *c = listNodeValue(ln); c->flags |= REDIS_DIRTY_CAS; }}Copy the code

The touchWatchedKey method is used to mark the watched_keys database by changing the flags attribute of the watched_keys object redisClient. When the watched_keys are changed, the touchWatchedKey method is used to mark the watched_keys database. Its client is marked REDIS_DIRTY_CAS, indicating that the client listening key has been modified. The touchWatchedKey method here is a bypass method that is triggered when the Redis server receives data change commands such as set, lpush, and zadd.

Transaction execution

watchIs aOptimistic lockingWhen multiple Client clients monitor the same database Key, all monitored clients and Key mapping relationships will be saved, and the Client Client will not change the blocking or rejection of the monitoring Key in the process of unsubmitted transactions. The Client checks the transaction status only when the Client commits the transaction and rejects the transaction only when the data is changed. Otherwise, the transaction is successfully executed.

multi

Multi Starts a transaction, similar to begin in a relational database. RedisClient flags as REDIS_MULTI during execution, indicating that the client has enabled transactions.

discard

Discard Cancels transactions, similar to rollback in a relational database. The action clears the transaction queue of all enqueued commands and cancels the client transaction status.

exec

Exec performs transactions, similar to the COMMIT in a relational database. The action commits all enqueued commands in the transaction queue and cancels the client transaction status.

Execute the process

Transaction execution is divided into three phases:Transaction start, command enqueue, transaction execution (transaction discard)

  • The start of a transaction indicates that the client has started a transaction
  • The command teamWhen the transaction state is enabled, all subsequent execution commands are placed into oneFIFOIn the queue, the commands will not be executed and will be executed in sequence after the transaction execution command is initiated
  • Transaction execution When a transaction is initiated, Redis takes out the commands previously placed in the command queue and executes them in the order in which they are stored
  • Transaction discarding When a transaction discarding is initiated, Redis empties the command queue and unflags the client transaction

  • It is executed immediately when the Redis server receives the client’s mark that the transaction status is enabledMulti, Watch, discard, and execEtc transaction command
  • When the Redis server receives the client marked as transaction enabled, the command is put into the transaction command queue and returned to the clientQUEUEDIndicates that the transaction command is enqueued
  • If not, it is not in the transaction state. Run the command normally.

The data structure

typedef struct redisClient {
    // Most other information is omitted here

    // Client status flag
    int flags;              /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... * /

    // Transaction status
    multiState mstate;      /* MULTI/EXEC state */

    // The monitored key
    list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */

} redisClient;
Copy the code

As mentioned above, redisClient is a client data structure, and Redis stores a lot of information for each client.

  • flagsRecord the client status flag, if includedREDIS_MULTI The flag indicates that the client is in a transactional state
  • mstateThe record transaction state isexecTo perform ormultiopen
  • watched_keysIs the key monitored after watch operation
/* * Transaction status */
typedef struct multiState {

    // Transaction queue, FIFO order
    multiCmd *commands;     /* Array of MULTI commands */

    // Queue command count
    int count;              /* Total number of MULTI commands */
} multiState;
Copy the code
  • commandsStores a collection of commands that are enqueued in the transaction state. Here is oneFIFOSequential arrays are executed first.
  • countLog the number of commands to join the queue
/* * transaction command */
typedef struct multiCmd {

    / / parameters
    robj **argv;

    // Number of arguments
    int argc;

    // Command pointer
    struct redisCommand *cmd;

} multiCmd;
Copy the code

multCmdStores a detailed description of the transaction command,argvIs an array of commands stored as string objects,argcIs the number of parameters,cmdPoint to command execution.

Exception handling

In the process of transaction execution, there are usually two kinds of errors: queue entry error and execution error.

  • Enqueue error Redis will check the enqueue command, once there is an exception, it will return a prompt, when the transaction is committed, the queue command will be rejected, all commands will not be submitted
  • Execution error For logical error commands, such as Key is a list object, but string object operation on it, this is mostly in the application running, but Redis in keeping it simple design philosophy, does not check the data type in the command queue stage, which can be completely avoided in the application. Unlike the enqueue error, the execution error will only fail to submit the execution of the error, and other normal commands will be submitted normally regardless of the order.

Team errors

When the transaction is not executed or a command error occurs in the queue phase, it will be prompted. When the transaction is submitted, it will be rejected directly, and the commands in the command queue will not take effect. As follows:

Perform error

The ACID to discuss

Through the above analysis of the implementation of Redis transaction mechanism, the characteristics of transaction ACID in Redis are discussed and summarized respectively.

Atomic nature

Redis can multi tag client transaction state, through the command queue to the staging all transactions during the open command, use the watch command to realize the protection for specific data based on optimistic locking implementation, in a transaction commit phase check status, check to monitor data changes the client affairs to ensure that in the process of transaction execution and submit transaction integrity, Because Redis works in a single-threaded environment, all operations are sequential, and its single-threaded design ensures that it does not have to worry about data inconsistencies due to races.

For execution errors, only correct commands can be executed correctly, while incorrect commands are ignored. This requires logical verification and fault tolerance for upper-layer applications

Isolation

Redis differs from relational databases such as MySQL in that it does not support data visibility across multiple dimensions of sessions. When not committed in the transaction state, the command will hold the command queue temporarily, while the changes in the transaction session are neither visible nor committed to the other transaction session, and the transaction commit will only actually occur during exec execution. Watch command provides a database Key Key monitoring function, it is equivalent to different multiple transactions session provides a monitoring communication’s ability to detect data changes, but it can only monitor submitted data changes to protect the transaction integrity, but can’t provide a different isolation level to MySQL to detect other uncommitted transaction session or submitted data changes.

“Durability”

The persistence of transactions in Redis depends on the opening and persistence mechanism of AOF or RDB. Even if an extreme exception occurs, as long as the AOF or RDB file is persistently flushed to disk, the transaction operation will be persistent and will be automatically loaded into memory for restoration after restart.

Consistency

Refer to the persistence section for data consistency.

To sum up the characteristics of Redis, compared with the transaction guarantee of MySQL relational database, it is still thin and simple. After all, the positioning and use scenarios of Redis are different, and the design complexity is also different. Transaction implementation is not its first priority. However, this does not affect our understanding of Redis transaction implementation mechanism and details, through the internal implementation of the scenario to make use of the transaction.

reference

Redis Design and Implementation

Github.com/huangz1990/…