With the help of Redis lock, the problem of high concurrency second kill is perfectly solved

catalogue

1 lock in single machine environment

2. Redis locks are used in distributed situations.

3. One service is down, resulting in failure to release the lock

4 add expiration time to each lock

5. Extend the expiration time of the lock to solve the lock failure

6 use Redisson to simplify code

Scene: an online mall makes limited second sales of goods.

1 lock in single machine environment

Save the quantity of goods in Redis. Before rush buying, each user needs to query the commodity quantity in Redis (instead of mysql database. Transactions are not considered). If the commodity quantity is greater than 0, it proves that the commodity is in stock. Then we are making inventory deduction and the next operation.

Because of multithreading concurrency, we have to use synchronous code blocks inside the get() method. This ensures the atomicity of inventory query and inventory reduction operations.

package springbootdemo.demo.controller;
/*
 * @auther Windward boy
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisLock  {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        synchronized (this) {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("Got it" + count + "Item No");
            }return "";
        }
    }
}

2. Redis locks are used in distributed situations.

However, due to the increase of business, the number of concurrent transactions becomes larger. The company had to make a copy of the original system and put it on the new server. Then use nginx for load balancing. In order to simulate the high concurrency environment, the Apache JMeter tool is used here.

Obviously, thread locks don't work anymore. So we need to change a lock, which must not have any coupling with the two systems.

Use the Redies API. If the key does not exist, set a key. This key is the lock we use now. When each thread comes here, set the lock first. If the lock setting fails, it indicates that a thread has obtained the lock and returns.

Finally, we throw exceptions for inventory reduction and other businesses without releasing the lock. The operation of releasing the lock is placed in the finally code block. It looks perfect.

package springbootdemo.demo.controller;
/*
 * @auther Windward boy
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "");
        if (!phoneLock) {
            return "";
        }
        try{
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("Got it" + count + "Item No");
            }
        }finally {
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

3. One service is down, resulting in failure to release the lock

If an exception is thrown in the try and finally enters, the lock will still be released and will not affect other threads to obtain the lock. If an exception is also thrown in finally, or the service is directly closed in finally, other services will no longer obtain the lock. Finally, the goods can not be sold.

 

package springbootdemo.demo.controller;
/*
 * @auther Windward boy
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        int i = 0;
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "");
        if (!phoneLock) {
            return "";
        }
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                i = count;
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("Got it" + count + "Item No");
            }
        } finally {
            if (i == 20) {
                System.exit(0);
            }
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

4 add expiration time to each lock

The problem is that if there is an accident, the lock cannot be released. Here, we are introducing the Redis API to set the expiration time of the key. In this way, if the thread that gets the lock does not have time to release the lock under any circumstances, the lock will be released automatically when the Redis key time expires. But there are still problems

If the lock is released after the key expires, but the current thread has not finished executing. Then other threads will get the lock and continue to rush to buy goods, while the slower thread will release others' lock after execution. Cause lock failure!

package springbootdemo.demo.controller;
/*
 * @auther Windward boy
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */

import javafx.concurrent.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@RestController
public class RedisLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
        if (!phoneLock) {
            return "";
        }
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                try {
                    Thread.sleep(99999999999L);
                } catch (Exception e) {

                }
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("Got it" + count + "Item No");
            }
        } finally {
          
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

5. Extend the expiration time of the lock to solve the lock failure

The problem is that when the key of a thread has expired, but the task of the thread has not been completed, the transaction has not ended. But the lock is gone. Now we must extend the lock time. When it is judged that the goods are in stock, a thread is created at the first time to continue the life of the key.

Prevent key expiration. Then, after the transaction, stop the timer and release the lock.

package springbootdemo.demo.controller;
/*
 * @auther Windward boy
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@RestController
public class RedisLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
        if (!phoneLock) {
            return "";
        }
        Timer timer = null;
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                timer = new Timer();
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        redisTemplate.opsForValue().set("phoneLock", "", 3, TimeUnit.SECONDS);
                    }
                }, 0, 1);

                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("Got it" + count + "Item No");
            }
        } finally {
            if (timer != null) {
                timer.cancel();
            }
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

6 use Redisson to simplify code

In step 5, our code is perfect and there will be no high concurrency problem. However, the code is too redundant. In order to use the Redis lock, we need to set a fixed length key, and then delete the key after the purchase. However, in order to prevent the key from expiring in advance, we have to add a thread to execute the scheduled task.

Next, we can use the Redisson framework to simplify the code. The getLock() method replaces Redis's setIfAbsent(), and lock() sets the expiration time. Finally, we release the lock after the transaction is over. The Redisson framework completes the operation of extending the lock for us. It uses polling to check whether the key has expired,

When the transaction is not completed, the key expiration time of Redis is automatically reset

package springbootdemo.demo.controller;
/*
 * @auther Windward boy
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@RestController
public class RedissonLock {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private Redisson redisson;

    @GetMapping(value = "buy2")
    public String get() {
        RLock phoneLock = redisson.getLock("phoneLock");
        phoneLock.lock(3, TimeUnit.SECONDS);
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("Got it" + count + "Item No");
            }
        } finally {
            phoneLock.unlock();
        }
        return "";
    }
}

Source: cnblogs.com/zumengjie/p/12187669.html  

Tags: Java Database Redis

Posted on Sun, 12 Sep 2021 02:51:03 -0400 by Anders_Scales