This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

1. Introduction

The problem of high concurrency and locking is illustrated by simulating the practice of buying commodities. Here we assume the scene of shopping spree on e-commerce websites. E-commerce websites often have a lot of commodities, some of which will be promoted at a low price and in limited quantities, and will advertise before promotion to attract website members to buy. Especially for hot products, there is likely to be a sudden rush of high concurrency, which is often referred to as “commodity killing in seconds”. This situation is very common in the work, but also in the interview is often a hot topic, the following by me to explain to you how to deal with this kind of high concurrency problem.

2. Project preparation

First, I put stock and the quantity of goods in Redis, which is 100. High concurrency scenarios are then simulated using JMeter.

3. Project practice

3.1 Single-node Service Version code

First, let’s look at a basic version.

@RequestMapping("/getStock")
    public String getStock(a){
        / / lock
        synchronized (this) {// Fetch inventory from Redis
            int stock = Integer.parseInt(redisTemplate.opsForValue().get(STOCK));
            // Determine the remaining inventory
            if(stock>0) {// If available, buy
                int newStock = stock-1;
                // Put it back in redis
                redisTemplate.opsForValue().set(STOCK,String.valueOf(newStock));
                System.out.println("The deduction is successful and the current inventory is :"+newStock);
            }else {
                System.out.println(Deducting [failed], the current inventory is:+stock);
            }
            return "success";
        }
Copy the code

Deduction is successful, the current inventory is :99 deduction is successful, the current inventory is :98 deduction is successful, the current inventory is :97 ……………… Deduction is successful, current inventory is :9 deduction is successful, current inventory is :8 deduction is successful, current inventory is :7 deduction is successful, current inventory is :5 deduction is successful, current inventory is :4 deduction is successful, current inventory is :3 deduction is successful, current inventory is :2 deduction is successful, Current inventory is :1 deduction successful, current inventory is :0 deduction [failed], current inventory is :0 deduction [failed], current inventory is :0 deduction [failed], current inventory is :0 deduction [failed], current inventory is :0 deduction [failed], current inventory is :0 …………

As it turns out, this code is fine in a standalone environment but oversold in a distributed deployment!

Obviously, the above code does not meet the requirements of the system in a distributed environment, and we will use distributed locks to improve this problem.

3.2 Distributed service version code

In version 2.0, we use the SETNx data structure in Redis for distributed locking.

    private static String LOCK_KEY = "lockKey";

    @RequestMapping("/getStock2")
    public String getStock2(a) {
        // If the expiration time is not set here, it is very likely that there will be problems, such as one of the machines failing to release the lock or down, and subsequent requests will not be able to purchase goods
        Boolean result = redisTemplate.opsForValue()
                .setIfAbsent(LOCK_KEY, "lock".10, TimeUnit.SECONDS);
        if(! result) {return "The event is too popular. Please try again later.";
        }

        try {
            int stock = Integer.parseInt(redisTemplate.opsForValue().get(STOCK));
            if (stock > 0) {
                int newStock = stock - 1;
                redisTemplate.opsForValue().set(STOCK, String.valueOf(newStock));
                System.out.println("The deduction is successful and the inventory after deduction is :" + newStock);
            } else {
                System.out.println(Deducting [failed], the current inventory is:+ stock); }}finally {
            // The lock must be released as soon as possible
            redisTemplate.delete(LOCK_KEY);
        }
        return "success";
    }
Copy the code

The above code, in the case of not too high concurrency, basically can be used, but in the case of high concurrency still have lock failure problem.

The key to solving this problem is that the lock I put on myself should only be released by me.

3.3 Using Redisson to Implement Distributed Locking

To implement a good distributed lock, include the following features:

  1. Specify a key as the lock marker, store it in Redis, and specify a unique user id as value.
  2. The value can be set only when the key does not exist. This ensures that only one client process obtains the lock at a time, meeting the mutual exclusion feature.
  3. Set an expiration time to prevent the key from being deleted due to system exceptions.
  4. After services are processed, the key must be cleared to release the lock. The value of the key must be verified. Only the lock holder can release the lock.

Redisson makes it easy to use distributed locks as follows:

 @RequestMapping("/getStock3")
    public String getStock3(a) {
        RLock rLock = redisson.getLock(LOCK_KEY);
        try {
            rLock.lock();
            int stock = Integer.parseInt(redisTemplate.opsForValue().get(STOCK));
            if (stock > 0) {
                int newStock = stock - 1;
                redisTemplate.opsForValue().set(STOCK, String.valueOf(newStock));
                System.out.println("The deduction is successful and the inventory after deduction is :" + newStock);
            } else {
                System.out.println(Deducting [failed], the current inventory is:+ stock); }}finally {
            rLock.unlock();
        }
        return "success";
    }
Copy the code

In most cases, we can use Redisson for our distributed locks to deal with high concurrency,