Redis transaction introduction
1. General
- Redis uses optimistic locking for transaction control. It uses the watch command to monitor a given key. When exec (commit transaction), if the monitored key changes after calling watch, the whole transaction will fail. You can also call watch to monitor multiple keys multiple times. Note that the key of watch is valid for the whole connection. If the connection is disconnected, monitoring and transactions will be automatically cleared. Of course, the exec, discard and unwatch commands will clear all monitoring in the connection.
2. Basic instructions
- When redis performs transaction control, it is usually implemented based on the following instructions, for example:
- multi: start transaction
- exec: commit transaction
- discard: cancel transaction
- watch: monitor. If the monitored value changes, the transaction submission will fail
- unwatch: cancel monitoring
- Redis ensures that all commands in a transaction are executed or not executed (atomicity). If the client is disconnected before sending the exec command, redis will empty the transaction queue and all commands in the transaction will not be executed. Once the client sends the exec command, all commands will be executed. Even if the client is disconnected, it doesn't matter, because all commands to be executed have been recorded in redis.
Redis transaction control
3. exec commit transaction
- For example: analog transfer, tony 500, jack 200, tony to Jack 100. The process is as follows:
127.0.0.1:6379> set tony 500
OK
127.0.0.1:6379> set jack 200
OK
127.0.0.1:6379> mget tony jack
1) "500"
2) "200"
127.0.0.1:6379> multi #Open transaction
OK
127.0.0.1:6379(TX)> decrby tony 100 #All instruction operations are queued
QUEUED
127.0.0.1:6379(TX)> incrby jack 100
QUEUED
127.0.0.1:6379(TX)> mget tony jack
QUEUED
127.0.0.1:6379(TX)> exec #Commit transaction
1) (integer) 400
2) (integer) 300
3) 1) "400"
2) "300"
127.0.0.1:6379> mget tony jack
1) "400"
2) "300"
127.0.0.1:6379>
4. discard cancel transaction
- Redis has no transaction rollback, only canceling the transaction
127.0.0.1:6379> mget tony jack
1) "400"
2) "300"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby jack 100
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get jack
"300"
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> get jack
"300"
127.0.0.1:6379>
- When an incorrect instruction occurs, the transaction will also be cancelled automatically
127.0.0.1:6379> mget tony jack
1) "400"
2) "300"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby jack 100
QUEUED
127.0.0.1:6379(TX)> sadas
(error) ERR unknown command `sadas`, with args beginning with:
127.0.0.1:6379(TX)> get jack
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get jack
"300"
127.0.0.1:6379>
5. Second kill ticket grabbing transaction
- Based on a spike buying case, demonstrate the optimistic lock mode of redis, such as
- Open client 1 and perform the following operations:
127.0.0.1:6379> set ticket 1
OK
127.0.0.1:6379> set money 0
OK
127.0.0.1:6379> watch ticket #Optimistic lock. Observe the value. If the value changes, the transaction fails
OK
127.0.0.1:6379> multi #Open transaction
OK
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> incrby money 100
QUEUED
- Open client 2 and perform the following operations. Before client 1 submits the transaction, client 2 buys the ticket.
127.0.0.1:6379> get ticket
"1"
127.0.0.1:6379> decr ticket
(integer) 0
- Back to client 1: commit the transaction and check the value of ticket
127.0.0.1:6379> exec
(nil) #Execute transaction, failed
127.0.0.1:6379> get ticket
"0"
127.0.0.1:6379> unwatch #Cancel monitoring
6. Jedis client transaction operation
package com.jt;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* @Author Sky-haohao
* @Date 2021/10/11 20:05
* @Version 1.0
*/
public class JedisTransactionTests {
@Test
public void testTransaction(){
Jedis jedis = new Jedis("192.168.126.128",6379 );
jedis.auth("baobao");
jedis.set("jack", "300");
jedis.set("rose", "500");
//To implement the operation, rose transfers 100 to Jack
//Open transaction
Transaction transaction = jedis.multi();
try {
//Perform business operations
transaction.decrBy("rose", 100);
transaction.incrBy("jack",100);
int n = 100/0; //Simulation anomaly
//Commit transaction
transaction.exec();
} catch (Exception e) {
e.printStackTrace();
//An exception occurred to cancel the transaction
transaction.discard();
}
String jack = jedis.get("jack");
String rose = jedis.get("rose");
System.out.println("jack:"+jack);
System.out.println("rose:"+rose);
jedis.close();
}
}
- Running result: exceptions can be found and the transaction will be cancelled
java.lang.ArithmeticException: / by zero
at com.jt.JedisTransactionTests.testTransaction(JedisTransactionTests.java:26)
......
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
jack:300
rose:500
7. Jedis client second kill operation practice
package com.jt.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
/**
* @Author Sky-haohao
* @Date 2021/10/11 20:29
* @Version 1.0
*
* Redis Second kill exercise:
* Simulate that two threads rush to buy the same ticket (consider optimistic lock)
* A simple multi-threaded second kill and ticket grabbing operation based on Redis
* This operation mainly demonstrates the application of optimistic lock
* Optimistic lock: multiple threads are allowed to modify a record at the same time, but only one thread can modify it successfully
*/
public class SecondsKillDemo02 {
//Define ticket grabbing logic
public static void secondsKillTickets(){
//1. Establish Redis connection
Jedis jedis = new Jedis("192.168.126.128",6379 );
jedis.auth("baobao");
//2. Monitor the specified key in Redis
String ticket = jedis.get("ticket");
if (ticket != null && "".equals(ticket) && Integer.valueOf(ticket)==0)
throw new RuntimeException("Insufficient remaining tickets");
jedis.watch("ticket","money");
//3. Start the transaction and execute the business
Transaction transaction = jedis.multi();
try {
transaction.decr("ticket");
transaction.incrBy("money", 100);
//4. Submission of services
List<Object> exec = transaction.exec();
System.out.println("transaction ok");
System.out.println(exec);
} catch (Exception e) {
e.printStackTrace();
} finally {
//5. Cancel monitoring
jedis.unwatch();
//6. Release resources
jedis.close();
}
}
public static void main(String[] args) {
//1. Define Redis initialization data
Jedis jedis = new Jedis("192.168.126.128",6379 );
jedis.auth("baobao");
jedis.set("ticket", "1");
jedis.set("money", "0");
jedis.close();
//2. Create multiple threads and perform ticket grabbing in the threads
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//Execute ticket grabbing
secondsKillTickets();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//Execute ticket grabbing
secondsKillTickets();
}
});
//3. Start multiple threads
t1.start();
t2.start();
}
}
- Operation results
transaction ok
null
transaction ok
[0, 100]