preface
Recently, small make up in the company have a demand, now share it out to everybody together discuss, user demand: inspection of equipment assets scanning to do a number of restrictions, such as our security guard patrol, patrol fixed several times a day, not beyond the limit, and there are many example, limit the number of requests such as an API interface, probably this is the business logic.
Requirement 1: Limit the number of scans
First of all, the Redis environment preparation, here small make up no more to say, just look for my CSDN blog, SpringBoot integration Redis many articles, are the basic work, we are using the way of RedisTemplate to achieve our needs.
1.1 Service Analysis
Code is based on the above demand, so we can work, every task, every function must first analyze the requirements, to achieve specific process, small make up such ideas often, not blind to write code, you write the code, at least not entirely abandoned code, but a continuous improvement, this code is good, Because this interface scenario may work today, but tomorrow you need to add new parameters, new business logic, and so on, incremental code is the way to go.
My analysis: Use Redis’s increment mode (counter) to count the number of scans and limit the number of requests.
Counter: Is the most intuitive pattern that Redis’s atomic increment operation can implement. The idea is fairly simple: Send an INCR command to Redis every time an operation occurs.
1.2 Code Implementation
Use the Redis counter, INCR command to count the number of scans. Assume that there are 3 scans within 5 minutes. When there are more than 3 scans, we will give feedback to the user, please scan again in 5 minutes.
Core code:
// counter + 1
redisTemplate.opsForValue().increment(key);
// Set the expiration time when the first scan starts
redisTemplate.expire(key,60 * 5,TimeUnit.SECONDS);
Copy the code
Specific code
@PostMapping("/test1/{id}/{key}")
public JSONObject test1(@PathVariable String id, @PathVariable String key){
JSONObject jsonObject = new JSONObject();
// Count times
Long increment = 0L;
Object time = redisTemplate.opsForValue().get(key);
// Redis has no value on the first scan
if(time == null) {// counter +1
increment = redisTemplate.opsForValue().increment(key);
// Set the expiration time to 5 minutes
redisTemplate.expire(key,60 * 5,TimeUnit.SECONDS);
jsonObject.put("code".2000);
jsonObject.put("msg"."Scan successful");
return jsonObject;
}else {
// Not the first scan
Integer count = (Integer) time;
System.out.println("count->>>"+count);
// Check whether there are more than 3 times
if(count < 3) {// Less than 3 times, each counter +1 operation
increment = redisTemplate.opsForValue().increment(key);
jsonObject.put("code".2000);
jsonObject.put("msg"."Scan successful");
return jsonObject;
}else {
// Otherwise exceed 3 times within 5 minutes
jsonObject.put("code".5000);
jsonObject.put("msg"."Limit API calls to 3 times");
returnjsonObject; }}}Copy the code
Call interface analysis
Here, different users scan the same asset, but each asset can only be scanned 3 times in 5 minutes.
Here, asset device FEC-1 is scanned for the first time by user 1:
Now we continue to scan, using either user 2 or user 3, after we have called the request three times:
Can see we scan the number reaches the limit, once they have arrived at after 3 times, it is no longer the counter to Redis written into the inside, immediately to the user tips, to scan again after 5 minutes, here we are in view of the key assets of equipment to do, not for which people can only scan a few times, only for this asset device allows the scanned the largest number, Ok, this is the end of the requirement, it seems that there is no problem.
Requirement 2: Only one person can scan the same location at a time
1.1 Service Analysis
For the above limit number of requests, we have done, why do we have the following lock step?
My analysis: What if the same device is scanned by two different people at the same time? So introducing Redis locks at this time can help us solve this problem.
Redis locks mainly use the setnx command of Redis.
- Lock command: SETNX key value: if the key does not exist, set the key and return success; otherwise, return failure. The KEY is the unique identifier of a lock and is named based on services.
- Unlock command: DEL key, which releases the lock by removing key-value pairs so that other threads can acquire the lock by using the SETNX command.
- EXPIRE key timeout: sets the EXPIRE key timeout period to ensure that the lock is automatically released after a certain period of time even if the lock is not explicitly released. Otherwise, resources are locked forever.
1.2 Pseudo-code implementation
Let’s analyze the pseudocode first:
public void lock(a){
try {
// Set the automatic lock release time
Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock"."value", Duration.ofSeconds(10));
if (flag){
// Succeeded in obtaining the lock
// TODO executes business logic
}else {
// Failed to obtain the lock
// TODO fails quickly and responds to the client}}finally {
// Finally, be sure to release the lock to avoid deadlocks
redisTemplate.delete("key"); }}Copy the code
Here are a few things to think about:
Why lock timeout?
If setIfAbsent succeeds, the server hangs, restarts, or the network is faulty. If the timeout period is not set, the lock becomes a deadlock.Copy the code
Why release the lock?
Be sure to rememberfinallyBlock releases the lock to avoid a deadlock.Copy the code
Why set the value of the lock?
The same key is accessed by different people. The lock is the same object that cannot distinguish the current lock and needs to be identified.Copy the code
Lock misunderstanding except?
Let’s assume that user A and user B are trying to lock the lock. User A runs setIfAbsent to get the lock first (if the lock expires in 10 seconds). User B is waiting to try to get the lock. If the service logic is time-consuming and the execution time exceeds the redis lock expiration time, the lock of user A’s thread is automatically released (the key is deleted). User B detects that the lock key does not exist, and runs the setIfAbsent command to obtain the lock. However, thread A will release the lock (delete the key) after executing the business logic, which will cause user B’s lock to be released by thread A.
Here we only need to solve the last problem, lock misunderstanding division, encountered such a scenario how to do?
Set different values to ensure lock uniqueness.
1.3 Specific code implementation
The user ID and asset device number are used as the key of the Redis lock to ensure that the lock is unique.
// Lock to prevent simultaneous scanning
String LOCK_KEY = "lock";
// ID ->>> User ID Key ->>> Asset device NUMBER
String LOCK_VALUE = id + key;
// Assume 30 seconds to automatically release the lock
Boolean b = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, LOCK_VALUE, Duration.ofSeconds(30));
/ / releases the lock
if (LOCK_VALUE.equals(redisTemplate.opsForValue().get(LOCK_KEY))){
redisTemplate.delete(LOCK_KEY);
}
Copy the code
The complete code is as follows
@PostMapping("/test2/{id}/{key}")
public JSONObject incr(@PathVariable String id,@PathVariable String key) throws InterruptedException {
JSONObject jsonObject = new JSONObject();
// Lock to prevent simultaneous scanning
String LOCK_KEY = "lock";
// ID ->>> User ID Key ->>> Asset device NUMBER
String LOCK_VALUE = id + key;
try {
// Assume 30 seconds to automatically release the lock
Boolean b = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, LOCK_VALUE, Duration.ofSeconds(30));
if (b){
// Succeeded in obtaining the lock
// Execute business logic
// Simulate service timeout
Thread.sleep(20000);
// counter key
String idKey = key;
Long increment = 0L;
Object time = redisTemplate.opsForValue().get(idKey);
if(time == null){
increment = redisTemplate.opsForValue().increment(idKey);
redisTemplate.expire(idKey,60*5,TimeUnit.SECONDS);
jsonObject.put("code".2000);
jsonObject.put("msg"."Scan successful");
return jsonObject;
}else {
Integer count = (Integer) time;
System.out.println("count->>>"+count);
if(count < 3){
increment = redisTemplate.opsForValue().increment(idKey);
jsonObject.put("code".2000);
jsonObject.put("msg"."Scan successful");
return jsonObject;
}else {
jsonObject.put("code".5000);
jsonObject.put("msg"."Limit API calls to 3 times");
returnjsonObject; }}}else {
// Failed to obtain the lock
// Fast failure, response to the client
jsonObject.put("code".5001);
jsonObject.put("msg"."Current device scanning!");
returnjsonObject; }}finally {
/ / releases the lock
if(LOCK_VALUE.equals(redisTemplate.opsForValue().get(LOCK_KEY))){ redisTemplate.delete(LOCK_KEY); }}}Copy the code
Here we simulate a service timeout, assuming that the service timeout, other threads need to acquire the lock, need to wait:
// Simulate service timeout sleep for 20 seconds
Thread.sleep(20000);
Copy the code
1.4 call API
To simulate A user first scan DT1 equipment assets, and to another user B scan DT1, here you can write multi-threaded simulation, request the interface at the same time, in order to facilitate the test, we use the sleep time to simulate, as well as the effect, of course test concurrent is also A lot of tools, small make up the article have said before, you can try.
User 1 is scanning and sleeps for 20 seconds. At this time, user 2 scans the device to obtain the lock.
Limit requests after 3 scans:
Ok, end !!!!!!!!!!!!!!!!!!!!!!!!!
conclusion
For example, when a service times out, the service execution time exceeds the lock timeout period. Generally speaking, the lock expires and the service execution is not complete (for complex service logic and long service consumption). How to solve this problem? We’ll talk more about that later.