Redis - transaction processing practice

Posted by kristo5747 on Tue, 12 Oct 2021 08:27:05 +0200

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]
    

Topics: Redis