preface

Transactions are one of the characteristics of a relational database, so as a representative of Nosql Redis has a transaction? If so, do Redis transactions have the ACID properties of a relational database?

Does Redis have a transaction

The answer may surprise many, but there are “transactions” in Redis. Here I put Redis transactions in quotes for reasons that will be explained later.

Individual commands in Redis are atomic operations, but if you need to combine multiple commands with consistent data, you can use Redis transactions (or use the Lua scripts described earlier).

In Redis, transactions are implemented using the following four commands:

  • multi: Start transaction
  • exec: Execute transaction
  • discard: Cancel transaction
  • watchMonitor:

Redis transactions are divided into the following three steps:

  1. Execute the commandmultiStart a transaction.
  2. Commands executed after a transaction is started are placed in a queue and returned on successQUEUED.
  3. Execute the commandexecAfter committing the transaction,RedisThe commands in the queue are executed in sequence, and all command results are returned in sequence (you can do this if you want to abandon the transaction)discardCommand).

Let’s get a taste of Redis transactions by executing the following commands in sequence:

multi // Start the transaction
set name lonely_wolf // Set name, at which point Redis will queue the command
set age 18  // Set the value age, at which point Redis will queue the command
get name  // Get name, at which point Redis will queue the command
exec // Commit the transaction, which executes the commands in the queue and returns the results in turn
Copy the code

After execution, the following results are obtained:

Redis transaction implementation principle

Each client in Redis has a record of the current client transaction state multiState, the following is a client data structure definition:

typedef struct client {
    uint64_t id;// Unique id of the client
    multiState mstate; //MULTI and EXEC states
    / /... Omit other attributes
} client;
Copy the code

The multiState data structure is defined as follows:

typedef struct multiState {
    multiCmd *commands;// FIFO queues for storing commands
    int count;// Total number of commands
    / /... Other attributes are omitted
} multiState;
Copy the code

MultiCmd is a queue that receives and stores commands sent after a transaction is enabled. Its data structure is defined as follows:

typedef struct multiCmd {
    robj **argv;// An array to store parameters
    int argc;// The number of parameters
    struct redisCommand *cmd;// Command pointer
} multiCmd;
Copy the code

Using the transaction in the sample screenshot above as an example, we can get a schematic diagram as follows:

Redis transaction ACID properties

In traditional relational databases, a transaction typically has ACID properties. Now let’s analyze whether Redis also meets the four properties of ACID.

A – atomicity

Before we discuss atomicity of transactions, let’s look at two examples.

  • An exception occurred before the simulated transaction executed the command. Run the following commands in sequence:
multi // Start the transaction
set name lonely_wolf // Set name, at which point Redis will queue the command
get  // An error occurs when an incomplete command is executed
exec // Commit the transaction after an exception occurs
Copy the code

The result is as shown in the following figure, where we can see that the transaction has been cancelled when the command is queued:

  • An exception occurred before the simulated transaction executed the command. Run the following commands in sequence:
flushall // To prevent impact, clear the database first
multi // Start the transaction
set name lonely_wolf // Set name, at which point Redis will queue the command
incr name  // This command can only be used for string objects whose value is an integer
exec Execute the first command successfully, execute the second command failed
get name // Get the value of name
Copy the code

In other words, the failure of one command during the transaction does not affect the execution of other commands. In other words, the transaction of Redis will not be rolled back:

Why don’t transactions in Redis roll

The answer to this question is clearly explained on the Redis website:

To sum up, there are three main reasons:

  • RedisThe author believes that the majority of transaction rollbacks are due to program errors, which occur during development and testing, but rarely in production.
  • For logical errors, such as adding a number1But the program logic is written as plus2This error cannot be resolved by transaction rollback.
  • RedisThe pursuit is simple and efficient, while the implementation of traditional transactions is relatively complex, which andRedisIs contrary to the design philosophy of.

C – Consistency

Consistency means that the data before and after the transaction is executed conforms to the definition and requirements of the database. Transactions in Redis meet this requirement, as mentioned in the atomicity section above. The wrong command will not be executed, whether it is a syntax error or a runtime error.

I – isolation

All commands in a transaction are executed sequentially, and requests from another client cannot be served during a Redis transaction, ensuring that the command is executed as a separate, independent operation. So transactions in Redis comply with isolation requirements.

D – Persistence

If persistent is not enabled in Redis, then it is pure memory running, once restarted, all data will be lost, then it can be considered that Redis does not have transaction persistence; If Redis has persistence enabled, Redis can be considered persistent under certain conditions.

Watch command

Watch: Redis watch: Redis watch Let’s start with an example.

First open a client first and execute the following commands in sequence:

flushall  // Clear the database
multi     // Start the transaction
get name  // Get name, return nil normally
set name lonely_wolf / / set the name
get name // Get name, in which case lonely_wolf should be returned
Copy the code

The following renderings are obtained:

Set name zhangsan: set name zhangsan: set name zhangsan: set name zhangsan:

If client 2 is successful, go back to client 1 and execute exec:

You can see that the first sentence returns Zhangsan. In other words, the name key value changes after joining the queue and before exec. Once this happens, it may cause serious problems. Therefore, in the relational database, we can solve this problem by locking.

Yes, this scenario is handled in Redis with the watch command.

Function of the watch command

The watch command can provide CAS optimistic locking behavior for Redis transactions. It can monitor any change of key value before exec command execution. That is to say, when multiple threads update the same key value, they will compare it with the original value and refuse to execute the command once they find that it has been modified. And it’s going to return nil to the client. Let’s use an example to demonstrate this.

To open a client, run the following commands in sequence:

flushall  // Clear the database
watch name / / to monitor the name
multi     // Start the transaction
set name lonely_wolf / / set the name
set age 18 / / set the age
get name   / / get the name
get age    / / get the age
Copy the code

After execution, the following effect picture is obtained:

At this time, open another client 2 and run set name zhangsan:

Then go back to the client and execute the exec command. Nil is returned, that is, the transaction is not executed (as long as a key is changed, the transaction is not executed) :

Analysis of Watch Principle

Here is a data structure definition for a Redis service:

typedef struct redisDb {
    dict *watched_keys;  // Key monitored by the watch command
    int id;           //Database ID
    / /... Other attributes are omitted
} redisDb;
Copy the code

As you can see, watched_keys in redisDb stores a dictionary of monitored keys and client ids. Each client also has a tag attribute CLIENT_DIRTY_CAS, which will be modified once we run commands such as set and sadd to change the value of the key. The execution of the transaction commit command exec will reject the transaction if the token attribute of the client is modified (an indication of optimistic locking).

conclusion

This paper mainly introduces the transaction mechanism in Redis, while introducing the principle of transaction implementation from the traditional relational database ACID four characteristics comparative analysis of Redis in the transaction, and finally understand that Redis transaction seems not so “perfect”.