Spring Boot + Redis: resist 10w people, second kill and grab orders!

Author: Shenniu 003\
Source: www.cnblogs.com com/wangrudong003/p/10627539. html

This article mainly explains the redis distributed lock, which is almost necessary for interviews in major factories. The following uses it in combination with the simulated order grabbing scenario; For the redis environment not covered in this article, it is recommended to use docker to quickly build a personal test environment; The content nodes of this chapter are as follows:

Jedis nx generated locks

  • How to delete a lock
  • Simulated order grabbing action (10w individual grabbing)
  • nx generation lock of jedis

A good way to operate redis in java is to use jedis. First, introduce dependencies into pom:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

I won't introduce the basics of Spring Boot. I recommend this practical tutorial:
https://github.com/javastacks...

For the generation of distributed locks, you usually need to pay attention to the following aspects:

Lock creation strategy: common keys of redis are generally allowed to be overwritten. After user A sets A key, B can also succeed in setting the same key. If it is A lock scenario, it is impossible to know which user set successfully; Here, jedis's setnx method solves this problem for us. The simple principle is: when user A sets successfully first, user B returns failure when setting. It is satisfied that only one user is allowed to get the lock at A certain point in time.

Lock expiration time: in A rush purchase scenario, if there is no concept of expiration, when user A generates A lock, but the subsequent process is blocked and the lock cannot be released, other users will always fail to obtain the lock and cannot complete the rush purchase activity; Of course, normally, it will not be blocked, and A user process will normally release the lock; The expiration time is just to be more secure.

Here is the code of setnx operation in the previous paragraph:

public boolean setnx(String key, String val) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis == null) {
                return false;
            }
            return jedis.set(key, val, "NX", "PX", 1000 * 60).
                    equalsIgnoreCase("ok");
        } catch (Exception ex) {
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;
    }

Note here that the set method of jedis, whose parameters are described as follows:

  • NX: whether there is a key. If there is a key, it will not be set successfully
  • PX: key expiration time is set in milliseconds (EX: in seconds)

If setnx fails, you can directly encapsulate and return false. Let's call the setnx method through a get api:

@GetMapping("/setnx/{key}/{val}")
public boolean setnx(@PathVariable String key, @PathVariable String val) {
     return jedisCom.setnx(key, val);
}

Access the following test url. Normally, true is returned for the first time and false is returned for the second time. Since the redis key already exists at the time of the second request, it cannot be set successfully

It can be seen from the figure above that only one set succeeds, and the key has an effective time. At this time, the distributed lock condition has been reached.

How to delete a lock

The above is creating A lock, which also has an effective time, but we can't completely rely on this effective time. For example, the effective time is set to 1 minute. After user A obtains the lock, it doesn't encounter any special circumstances. After the rush purchase order is generated normally, other users should be able to place orders normally, but the lock can be released automatically after 1 minute, Other users cannot place orders normally within one minute (because the lock is still owned by user A), so we need user A to take the initiative to unlock after the operation:

public int delnx(String key, String val) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis == null) {
                return 0;
            }

            //if redis.call('get','orderkey')=='1111' then return redis.call('del','orderkey') else return 0 end
            StringBuilder sbScript = new StringBuilder();
            sbScript.append("if redis.call('get','").append(key).append("')").append("=='").append(val).append("'").
                    append(" then ").
                    append("    return redis.call('del','").append(key).append("')").
                    append(" else ").
                    append("    return 0").
                    append(" end");

            return Integer.valueOf(jedis.eval(sbScript.toString()).toString());
        } catch (Exception ex) {
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return 0;
    }

jedis mode is also used here to directly execute lua script: judge whether it exists according to val, and del if it exists;

In fact, I personally think that after obtaining val through jedis's get method, I can compare whether value is the user who currently holds the lock. If so, I can delete it finally. The effect is actually quite good; Just execute the script directly through eval, which avoids one more operation of redis and shortens the interval of atomic operations. (if you have different opinions, please leave a message for discussion); Similarly, a get api is created here to test:

@GetMapping("/delnx/{key}/{val}")
public int delnx(@PathVariable String key, @PathVariable String val) {
   return jedisCom.delnx(key, val);
}

Note that in delnx, you need to pass the value when creating the lock, because whether it is an operation request to hold the lock is determined by the value of et and the value of delnx. del is allowed only if the value is the same;

Simulated order grabbing action (10w individual grabbing)

With the above rough foundation of distributed locks, we simulate the scenario of 10w people grabbing orders. In fact, it is just a concurrent operation request. Due to the limited environment, we can only test it in this way; Initialize 10w users as follows, and initialize inventory, commodity and other information, as shown in the following code:

//Total inventory
    private long nKuCuen = 0;
    //Item key name
    private String shangpingKey = "computer_key";
    //Timeout to acquire lock seconds
    private int timeout = 30 * 1000;

    @GetMapping("/qiangdan")
    public List<String> qiangdan() {

        //Users who grab goods
        List<String> shopUsers = new ArrayList<>();

        //Construct many users
        List<String> users = new ArrayList<>();
        IntStream.range(0, 100000).parallel().forEach(b -> {
            users.add("Divine cow-" + b);
        });

        //Initialize inventory
        nKuCuen = 10;

        //Simulated robbery
        users.parallelStream().forEach(b -> {
            String shopUser = qiang(b);
            if (!StringUtils.isEmpty(shopUser)) {
                shopUsers.add(shopUser);
            }
        });

        return shopUsers;
    }

With the above 10w different users, we set that there are only 10 inventories of goods, and then simulate rush purchase through parallel flow. The implementation of rush purchase is as follows:

/**
     * Simulated order grabbing action
     *
     * @param b
     * @return
     */
    private String qiang(String b) {
        //User startup time
        long startTime = System.currentTimeMillis();

        //If it is not captured, continue to acquire the lock within 30 seconds
        while ((startTime + timeout) >= System.currentTimeMillis()) {
            //Is the product remaining
            if (nKuCuen <= 0) {
                break;
            }
            if (jedisCom.setnx(shangpingKey, b)) {
                //User b gets the lock
                logger.info("user{}Get the lock...", b);
                try {
                    //Is the product remaining
                    if (nKuCuen <= 0) {
                        break;
                    }

                    //Simulate the time-consuming operation of generating orders for easy viewing: shenniu-50 has obtained lock records for many times
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //The rush purchase is successful, the goods are decreasing, and the user is recorded
                    nKuCuen -= 1;

                    //Jump out successfully
                    logger.info("user{}Jump out successfully...Remaining inventory:{}", b, nKuCuen);

                    return b + "Order grabbing succeeded, remaining inventory:" + nKuCuen;
                } finally {
                    logger.info("user{}Release lock...", b);
                    //Release lock
                    jedisCom.delnx(shangpingKey, b);
                }
            } else {
                //User b did not get the lock and continued to request the lock within the timeout range. No processing is required
//                if (b.equals("shenniu-50") | b.equals ("shenniu-69")){
//                    logger.info("user {} waiting for lock...", b);
//                }
            }
        }
        return "";
    }

The logic implemented here is:

1. Parallel stream (): parallel stream simulates multi-user rush buying

2,(startTime + timeout) >= System. Currenttimemillis(): judge the user who failed to grab the lock successfully, and continue to acquire the lock within timeout seconds

3. Judge whether the inventory is sufficient before and after obtaining the lock

4,jedisCom.setnx(shangpingKey, b): the user obtains the snap up lock

5. After obtaining the lock and placing the order successfully, finally release the lock: jediscom delnx(shangpingKey, b)

Let's look at the log results recorded:

Finally, users who have successfully snapped up are returned:

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Hot! The Java collaboration is coming...

3.Play big! Log4j 2.x re explosion...

4.Spring Boot 2.6 was officially released, a wave of new features..

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Keywords: Java

Added by anthrt on Thu, 30 Dec 2021 07:44:40 +0200