Do redis transactions satisfy atomicity?

Code nongshen Shang 2021-09-15 10:01:07

Talking about database transactions , It is estimated that the first reaction of many students is ACID, Platoon ACID First in A Atomicity , Require all operations in a transaction , Or it's all done , Or not at all . be familiar with redis My classmates must know , stay redis There are also transactions in , So does its transaction satisfy atomicity ? Let's take a look at .

What is? Redis Business ?

Similar to database transactions ,redis Transactions are also used to execute multiple commands at once . It's easy to use , It can be used MULTI Start a transaction , Then queue multiple commands into the transaction queue , Finally by EXEC Command triggers transaction , Execute all commands in the transaction . Look at a simple example of transaction execution :

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Hydra
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (integer) 19

You can see , When the data types of instructions and operands are normal , Input EXEC After that, all commands were executed successfully .

Redis Do transactions satisfy atomicity ?

If you want to verify redis Does the transaction satisfy atomicity , So you need to redis When the transaction execution is abnormal , Let's test for two different types of errors .

Grammar mistakes

First, test the syntax error in the command , In this case, the number of parameters of the command is incorrect or there is an error in the input command itself . Let's enter a malformed command in the transaction , Start the transaction and enter the following commands in turn :

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Hydra
QUEUED
127.0.0.1:6379> incr
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> set age 18
QUEUED

The command entered incr No parameters are added later , Syntax error due to incorrect command format , At this time, when you command to join the team, you will immediately detect the error and prompt error. Use exec Perform transactions , View result output :

127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

under these circumstances , As long as a command in the transaction has a syntax error , In execution exec An error will be returned directly after , All commands, including those with correct syntax, will not be executed . Verify this , Take a look at the execution of other instructions in the transaction , see set Command execution results , All empty , Indicates that the instruction was not executed .

127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> get age
(nil)

Besides , If there is a spelling error in the command itself 、 Or enter a command that does not exist , It also belongs to grammatical errors , An error will be reported directly when executing a transaction .

Running error

Run error means that the input instruction format is correct , But an error occurred during the execution of the command , A typical scenario is when the data type of the input parameter does not meet the parameter requirements of the command , A running error will occur . For example, in the following example , To a string The value of type performs the operation of the list , An error is as follows :

127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> lpush key1 value2
(error) WRONGTYPE Operation against a key holding the wrong kind of value

This kind of mistake lies in redis It cannot be found until the instruction is actually executed , Only when it is actually executed can it be found , Therefore, such commands can be received by the transaction queue , It will not immediately report an error like the syntax error above .

Let's take a look at the situation when there is a running error in the transaction , In the following transaction , Try to be on string Type data to carry out incr Self increment operation :

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Hydra
QUEUED
127.0.0.1:6379> set age eighteen
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> del name
QUEUED

redis Up to now, there is no indication of an error , perform exec Look at the output :

127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) (integer) 1

You can see the result of running , although incr age An error occurred with this command , But the commands before and after it are executed normally , Look at these again key Corresponding value , It does prove that the other instructions are executed successfully :

127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> get age
"eighteen"

Phased conclusion

Analyze the running results of the above transactions :

  • There is Grammar mistakes Under the circumstances , All commands will not be executed
  • There is Running error Under the circumstances , Except for commands with errors in execution , Other commands can be executed normally

Through analysis, we know redis Transactions in are not atomic , In case of running error , There is no rollback function similar to that in the database . So why redis Rollback is not supported , The official documentation gives instructions , The main idea is as follows :

  • redis Command failure will only occur in the case of syntax error or data type error , This result is caused by errors in the programming process , This should be detected in the development environment , Not the production environment
  • Do not use rollback , Can make redis Simpler interior design , Faster
  • Rollback cannot avoid errors in programming logic , If you want to increase the value of a key 2 But only increased 1, In this case, even rollback cannot help

For the above reasons ,redis The official choice is simpler 、 Faster way , Error rollback is not supported . In this case , If we need to ensure atomicity in our business scenario , Then the developer is required to ensure the success or failure of all commands by other means , For example, check the parameter type before executing the command , Or make transaction compensation in time when there are errors in transaction execution .

Mention other ways , I believe many friends have heard Use Lua Script to ensure the atomicity of the operation , For example, in distributed locks, we usually use Lua Script , that , magical Lua Can scripts really guarantee atomicity ?

ordinary Lua Script entry

In the verification of lua Before the atomicity of the script , We need a simple understanding of it .redis from 2.6 The version starts to support execution lua Script , Its functions are very similar to transactions , a section lua The script is executed as a command , This will multiple redis Command write lua, You can achieve the execution results of similar transactions . Let's take a look at the following common commands .

EVAL command

Most commonly used EVAL Used to execute a script , Its command format is as follows :

EVAL script numkeys key [key ...] arg [arg ...]

Briefly explain the parameters :

  • script Is a lua Script program
  • numkeys There are several parameters for specifying subsequent parameters key, If not key Then for 0
  • key [key …] Represents the used in the script redis The key , stay lua The script passes KEYS[i] In the form of
  • arg [arg …] Represents additional parameters , stay lua The script passes ARGV[i] obtain

Take a simple example :

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 value1 vauel2
1) "key1"
2) "key2"
3) "value1"
4) "vauel2"

In the above order , In double quotation marks lua Script program , hinder 2 Indicates that there are two key, Namely key1 and key2, The following parameters are additional parameters value1 and value2.

If you want to use it lua Script execution set command , It can be written like this :

127.0.0.1:6379> EVAL "redis.call('SET', KEYS[1], ARGV[1]);" 1 name Hydra
(nil)

It's used here redis Built in lua function redis.call To complete set command , The execution results are printed here nil Because there is no return value , If you're not used to it , In fact, we can add return 0; Return statement .

SCRIPT LOAD and EVALSHA command

These two commands are put together because they are usually used in pairs . First look at SCRIPT LOAD, It is used to load scripts into the cache , return SHA1 The checksum , At this time, only the command is cached , But the order was not executed immediately , Take an example :

127.0.0.1:6379> SCRIPT LOAD "return redis.call('GET', KEYS[1]);"
"228d85f44a89b14a5cdb768a29c4c4d907133f56"

Here comes back a SHA1 Checksum , You can use EVALSHA To execute the script :

127.0.0.1:6379> EVALSHA "228d85f44a89b14a5cdb768a29c4c4d907133f56" 1 name
"Hydra"

Use this here SHA1 The value is equivalent to importing the command cached above , Splice later numkeyskeyarg Equal parameter , The command can be executed normally .

Other commands

Use SCRIPT EXISTS Command to determine whether the script is cached :

127.0.0.1:6379> SCRIPT EXISTS 228d85f44a89b14a5cdb768a29c4c4d907133f56
1) (integer) 1

Use SCRIPT FLUSH Order clear redis Medium lua Script cache :

127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> SCRIPT EXISTS 228d85f44a89b14a5cdb768a29c4c4d907133f56
1) (integer) 0

You can see , Yes SCRIPT FLUSH after , Through again SHA1 The value no longer exists when viewing the script . Last , You can also use SCRIPT KILL The command kills the currently running lua Script , But it will only take effect if the script does not perform a write operation .

From these operations ,lua Scripts have the following advantages :

  • Multiple network requests can be completed in one request , Reduce network overhead , Reduced network latency
  • The script sent by the client will exist redis in , Other clients can reuse this script , Without repeated coding to complete the same logic

Java The code uses lua Script

stay Java You can use Jedis It's packaged in API To execute lua Script , Here is a use Jedis perform lua An example of a script :

public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String script="redis.call('SET', KEYS[1], ARGV[1]);"
+"return redis.call('GET', KEYS[1]);";
List<String> keys= Arrays.asList("age");
List<String> values= Arrays.asList("eighteen");
Object result = jedis.eval(script, keys, values);
System.out.println(result);
}

Execute the above code , The console prints get Results returned by command :

eighteen

After the simple bedding is completed , Let's take a look lua Whether the script can achieve rollback level atomicity . Modify the above code , Insert a command that runs incorrectly :

public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String script="redis.call('SET', KEYS[1], ARGV[1]);"
+"redis.call('INCR', KEYS[1]);"
+"return redis.call('GET', KEYS[1]);";
List<String> keys= Arrays.asList("age");
List<String> values= Arrays.asList("eighteen");
Object result = jedis.eval(script, keys, values);
System.out.println(result);
}

View the execution results :

Go to the client again get command :

127.0.0.1:6379> get age
"eighteen"

in other words , Although the program threw an exception , However, the command before the exception is executed normally and is not rolled back . Try again, directly in redis Run this command in the client :

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> eval "redis.call('SET', KEYS[1], ARGV[1]);redis.call('INCR', KEYS[1]);return redis.call('GET', KEYS[1])" 1 age eight
(error) ERR Error running script (call to f_c2ea9d5c8f60735ecbedb47efd42c834554b9b3b): @user_script:1: ERR value is not an integer or out of range
127.0.0.1:6379> get age
"eight"

Again , The instruction before the error is still not rolled back , So what we often heard before Lua What is it about scripts that guarantee atomic operations ?

Actually , stay redis Is the same one used in lua Interpreter to execute all commands , It ensures that when a period of time lua When the script executes , There won't be any other scripts or redis The order is executed at the same time , It ensures that the operation will not be inserted or disturbed by other instructions , What is achieved is only this degree of atomicity .

But unfortunately , If the script runs with an error and ends halfway , Subsequent operations will not be performed , However, previous writes will not be undone , So even if you use lua Script , Nor can it achieve atomicity similar to database rollback .

This article is based on redis 5.0.3 To test

Official document related instructions :https://redis.io/topics/transactions

Author's brief introduction , Manongshen (CODER_SANJYOU), An official account of sharing love , Interesting 、 thorough 、 direct , Talk to you about technology . Personal wechat DrHydra9, Welcome to add friends , Further communication .

Please bring the original link to reprint ,thank
Similar articles

2021-09-15

2021-09-15

2021-09-15

2021-09-15

2021-09-15