Transactions and locking mechanisms in Redis

Transaction and lock mechanism in Redis

Redis’s transaction mechanism is a mechanism that packages multiple commands for execution, ensuring that all of these commands either execute successfully or fail without any intermediate status. In Redis, transactions are implemented through four commands: MULTI, EXEC, DISCARD and WATCH.

  1. MULTI: This command marks the beginning of a transaction. Once MULTI is called, subsequent commands will not be executed immediately, but will be placed in a queue waiting for execution.
  2. EXEC: This command is used to execute all commands in the transaction. Once EXEC is called, Redis will execute all commands in the queue in the order of the commands.
  3. DISCARD: If you need to cancel a transaction before executing EXEC, you can use the DISCARD command.
    This clears the transaction queue and cancels the transaction.
  4. WATCH: This command is used to monitor one or more keys. Once other clients modify these keys, the current transaction will be interrupted. The WATCH command can be used to implement optimistic locking, that is, to check whether the monitored key has been modified by other clients before the transaction is executed.

multi, exec, discard

Starting from the input of the Multi command, the entered commands will be entered into the command queue at one time, but will not be executed. After entering Exec, Redis will execute the commands in the previous command queue in sequence.
image.png

Case

image.png
There were no errors in the team formation phase and the execution phase, and the execution was successful!
image.png
An error was reported during the team formation stage and the execution failed!
image.png
An error was reported during the execution phase, but part of the execution was successful!

Why transactions are needed

Classic bank transfer problems:
Suppose you have two accounts, account1 and account2, and you want to transfer funds between them. In this case, you can use Redis transactions to ensure that the transfer is atomic.
Assume that the balance of account1 is 100 yuan and the balance of account2 is 50 yuan. Now, you want to transfer 10 yuan from account1 to account2. Here is an example using Redis transactions:

redis
MULTI
DECRBY account1 10 #Decrease 10 yuan from account1
INCRBY account2 10 # Add 10 yuan to account2
EXEC

In the above example, the DECRBY and INCRBY commands are used to decrease 10 yuan in account1 and increase 10 yuan in account2 respectively. These two commands are wrapped between MULTI and EXEC to form a transaction. If no errors occur while executing the transaction, the transfer will be completed atomically. If an error occurs while executing a transaction, the entire transaction will be rolled back to ensure that no partial transfer occurs.

Transaction conflict

Let’s first look at an example of transaction conflict:

Consider the scenario of a simple online shopping system where there is an inventory system to track the inventory levels of items. In this system, multiple users may try to purchase the same item at the same time, which may cause transaction conflicts.

  1. Initial status: The inventory of product A is 10.
  2. User A attempts to purchase:
  • User A checks the inventory of product A and finds that the inventory is 10.
  • User A decides to purchase a product A, reducing the inventory by 1.
WATCH inventory:A
val = GET inventory:A
val = val - 1
MULTI
SET inventory:A $val
EXEC
  1. User B attempts to purchase (conflict occurs):
  • While User A is executing the transaction, User B also tries to purchase Product A.
  • User B checks the inventory of item A and finds that the inventory is still 10.
  • User B decides to purchase a product A, reducing the inventory by 1.
WATCH inventory:A
val = GET inventory:A
val = val - 1
MULTI
SET inventory:A $val
EXEC

In this example, because User B also attempted to purchase Product A during User A’s transaction execution, a conflict occurred between the two transactions. When User B executes a transaction, because the key (inventory:A) monitored by WATCH has changed (modified by User A’s transaction), User B’s transaction will will fail, avoiding erroneous reductions in product inventory.

Pessimistic lock

Pessimistic Lock, as the name suggests, is very pessimistic. Every time I go to get the data, I think that others will modify it, so I will lock it every time I get the data. Lock, so that if someone wants to get the data, they will block until they get the lock. Many such lock mechanisms are used in traditional relational databases, such as row locks, table locks, read locks, write locks, etc., which are all locked before operations.
image.png
Pessimistic locking is suitable for the following scenarios:

  1. Frequent writing scenarios: When frequent writing operations are performed on shared resources, pessimistic locks can effectively prevent multiple transactions from modifying the same resource at the same time and avoid concurrency conflicts. This is important for systems that require strict consistency.
  2. Long transaction scenario: In a long transaction scenario, if a transaction requires multiple reads and writes to some shared resources during execution, pessimistic locking can ensure that the resources are not used by other users during the transaction execution. Transaction modification.
  3. Scenarios sensitive to resource modifications: When modifications to shared resources have a significant impact on the system, pessimistic locking can be used to ensure that other transactions do not interfere during the modification. This prevents data inconsistencies from occurring at critical moments.
  4. Exclusive resource scenario: When an operation requires exclusive access to a resource, pessimistic locking is an effective method. For example, if a thread is modifying a file, in order to prevent other threads from modifying it at the same time, pessimistic locking can be used.
  5. Scenarios to avoid concurrency conflicts: In some cases, although pessimistic locking may cause certain performance losses, in order to avoid concurrency conflicts and ensure data integrity, pessimistic locking is still a suitable choice.

It should be noted that pessimistic locking may introduce performance overhead because it attempts to acquire the lock before operating, and other transactions may need to wait. Therefore, when choosing whether to use pessimistic locking, there is a trade-off between consistency and performance, depending on the requirements of the application.

Optimistic locking

**Optimistic Lock, **As the name suggests, it is very optimistic. Every time you go to get the data, you think that others will not modify it, so you will not lock it, but when you update it, you will judge whether others have changed it during this period. This data is not updated. For example, watch is a lightweight optimistic locking operation that can use mechanisms such as version numbers.
Optimistic locking is suitable for multi-read application types, which can improve throughput. Redis uses this check-and-set mechanism to implement transactions.
image.png
Optimistic locking is suitable for the following scenarios:

  1. High concurrent read scenario: Optimistic locking is a more suitable mechanism when read operations are more frequent than write operations. Multiple transactions can read from the same resource simultaneously, and writes are checked for conflicts.
  2. Short transaction scenario: When transaction execution time is short and writing operations to shared resources are infrequent, optimistic locking can reduce lock contention and improve concurrency performance.
  3. Lock-free algorithm: Lock-free algorithm is used in some distributed systems, among which optimistic locking is a common implementation method. In this case, the system focuses more on avoiding the use of explicit locks as much as possible, and instead handles concurrent access through version numbers, timestamps, etc.
  4. Scenarios with fewer data conflicts: If there are relatively few write conflicts for shared resources in the application, and the system can handle them by detecting conflicts and retrying, then optimistic locking is a suitable s Choice.
  5. Scenarios that support conflict detection and retry: Optimistic locking is suitable for scenarios that can detect conflicts and handle them appropriately when conflicts occur (for example, rolling back the transaction or retrying the operation).

Generally speaking, optimistic locking is more suitable for handling scenarios with frequent read operations and few write operations, and the system can tolerate a certain degree of conflicts and retries. In such an environment,optimistic locking can improve concurrency performance and avoid the performance overhead that pessimistic locking may introduce.

watch key [key …]

Before executing multi, execute watch key1 [key2] first, which can monitor one (or more) keys. If this (or these) keys are changed by other commands before the transactionis executed, the transaction will be interrupted.
Example:

127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379>incrby balance 10
QUEUED
127.0.0.1:6379>exec
1) (integer) 11
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 10
QUEUED
127.0.0.1:6379>exec
(nil)

WATCH is usually classified as optimistic locking in Redis

  • Optimistic locking: In the case of optimistic locking, the system assumes that the occurrence of concurrency conflicts is unlikely. Therefore, it allows multiple transactions to access resources simultaneously and checks for conflicts while executing transactions. If a conflict is found, the system rolls back the transaction and requires a retry.
  • When using WATCH, Redis does not lock resources immediately. Instead, it monitors one or more keys without preventing other clients from modifying them until the transaction is executed. Only when executing a transaction, Redis will check whether the monitored key has changed. If there is a change, the transaction will be canceled and needs to be retried.

unwatch

Cancel the monitoring of all keys by the WATCH command.
If after executing the WATCH command, the EXEC command or the DISCARD command is executed first, then there is no need to execute UNWATCH again.

Three major characteristics of Redis transactions

  1. Individual quarantine operations

All commands in a transaction are serialized and executed sequentially. During the execution of the transaction, it will not be interrupted by command requests sent by other clients.

  1. No concept of isolation level

The commands in the queue will not be actually executed until they are submitted, because no instructions will be actually executed before the transaction is submitted.

  1. Atomicity not guaranteed

If one command in the transaction fails to execute, subsequent commands will still be executed without rollback.