1. Overview
Here, Redis's setNx command, expire command and del command are used to implement distributed locks.
First of all, we need to know that our redis executes commands in a queue mode. There are no multiple commands running at the same time. All commands are accessed serially. This means that there is no concurrency problem when multiple clients connect to redis.
In fact, Redis can not only be used to implement distributed locks, but also other methods. The main purpose is to have a place as the lock state, and then use the lock state to realize the functions in the code. As long as our lock operation is serial, we can realize distributed lock.
In fact, there is a problem. Why don't we use synchronized in Java and build a distributed lock? In fact, they are all distributed environments, and Java's built-in synchronized is a lock for a single java process. In the distributed environment, there are n Java processes, and the distributed lock implements the lock between multiple Java processes.
So why should we use the setNx command instead of other commands? For example, the get command may be dirty data after we get the key, and our setNx means that we set a key. If the key already exists, it returns 0, if it does not exist, it returns 1, and the setting is successful. We can use this method to implement the so-called distributed lock.
Note that the most important thing about the implementation of distributed locks is that there is a step to achieve serialization without dirty data.
Without much nonsense, go straight to the ready-made method.
2. Code
/** * Redis Lock tool class * * @author dh * @date 20211028103258 **/ @Component public class RedisLockHelper { @Autowired public RedisTemplate redisTemplate; /** * Acquire lock * @param key Lock key * @param seconds Maximum lock time * @return true:Success, false: failure */ public boolean lock(String key,Long seconds){ return (Boolean) redisTemplate.execute((RedisCallback) connection -> { /** If it does not exist, then true, then execution is allowed, */ Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(key).getBytes()); /** To prevent deadlock, set its key to the expiration time */ connection.expire(key.getBytes(), seconds); if (acquire) { return true; } return false; }); } /** * Delete lock * @param key */ public void delete(String key) { redisTemplate.delete(key); } }
3. Case
If friends with strong understanding get this method, they can quickly realize the functions in the business. Here is an implementation case to prevent repeated submission.
Anti duplicate submission annotation repeatsubmiteintercept
/** * Repeatedly submit interception comments * @author dh */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatSubmitIntercept { /** * Maximum blocking time, default 5s */ long maxTime() default 5L; /** * msg is returned when submitting repeatedly */ String errorTitle() default "The current operation is repeated!"; /** * Interception method: * 1,If it is 0: it is intercepted according to the current user, and the current method can only be accessed once before the last request is completed * 2,If it is 1: then it will be intercepted according to the currently specified parameter name, and the user of the current method can only access the same parameter once before the last request is completed */ int type() default 0; /** * Interception method: * If the interception method is 0, the user is judged according to the request header */ String userHead() default CacheConstants.AUTHORIZATION_HEADER; /** * If the interception method is 1, the specified parameter name collection * @return */ String []parameters() default {}; /** * redis The key prefix in does not need to be modified */ String redis_lock_prefix() default "super_bridal_repeat_submit_lock_prefix_"; /** * When the method is in the intercepted state, the number of repeated attempts is 0, and no attempt is made * @return */ int rewaitCount() default 0; }
aop
/** * Anti duplicate submission notes * * @param point * @return * @throws Throwable */ @Around("@annotation(Package name.........RepeatSubmitIntercept)") public Object noRepeatSubmitAround(ProceedingJoinPoint point) throws Throwable { HttpServletRequest request = ServletUtils.getRequest(); String uriStringBase64 = Base64.getEncoder().encodeToString(request.getRequestURI().getBytes()); MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); RepeatSubmitIntercept repeatSubmitIntercept = method.getAnnotation(RepeatSubmitIntercept.class); if (repeatSubmitIntercept.maxTime() < 1L) { throw new RepeatSubmitInterceptException("Repeated submission interceptor error--Error setting maximum blocking time,At least greater than 1 s", 500); } if (StringUtils.isBlank(repeatSubmitIntercept.errorTitle())) { throw new RepeatSubmitInterceptException("Repeated submission interceptor error--Please do not set the error message reminder to blank/Empty string", 500); } if (StringUtils.isBlank(repeatSubmitIntercept.redis_lock_prefix())) { throw new RepeatSubmitInterceptException("Repeated submission interceptor error--prefix Key Cannot be empty/Empty string", 500); } String token = Convert.toStr(ServletUtils.getRequest().getHeader(repeatSubmitIntercept.userHead())); StringBuilder key = new StringBuilder() .append(repeatSubmitIntercept.redis_lock_prefix()) .append(token) .append("/") .append(uriStringBase64); if (StringUtils.isEmpty(token)) { throw new RepeatSubmitInterceptException("Repeated submission interceptor error--The current interception method is[User interception],But in its request header token Empty!", 500); } /** Method of user interception */ if (repeatSubmitIntercept.type() == 0) { /** The token in the request header should be used as the key here, so no other operations are performed here */ } else if (repeatSubmitIntercept.type() == 1) { /** Get key from request parameters */ // .................... Omitted } else { throw new RepeatSubmitInterceptException("Repeated submission interceptor error--The current interception method is not set!", 500); } if (redisLockHelper.lock(key.toString(), repeatSubmitIntercept.maxTime())) { return execute(key.toString(), point); } else { /** * 1,It is judged that repeated waiting is allowed * 2,Repeat wait operation * */ if (repeatSubmitIntercept.rewaitCount() > 0) { int i = 0; while (i < repeatSubmitIntercept.rewaitCount()) { /** Pause for 100ms and get it again */ Thread.sleep(100); i++; if (redisLockHelper.lock(key.toString(), repeatSubmitIntercept.maxTime())) { return execute(key.toString(), point); } } } } throw new RepeatSubmitInterceptException(repeatSubmitIntercept.errorTitle(), 500); }
Note that the repeatsubmiteinterceptexception here is a custom exception.
Place of use
@GetMapping("/test1") @RepeatSubmitIntercept() public AjaxResult test1(){ System.out.println("Request entered:" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return AjaxResult.success(); }
If there is any problem in the implementation, please leave a message.