Problem description
It is no problem to deploy the project with scheduled tasks on a single test environment. In production, there are two cluster servers. When the project is deployed, it is found that the module of timing task is executed on both machines at the same time, which will lead to other accidents.
Solution - redis distributed lock
The redis distributed lock is used to lock the unique key specified for the scheduled task and set the lock timeout. When a timed task is triggered, the task of a service enters the aspect, and locks the unique key through the setNX(key,value) method. If the current key does not exist, it will be put into the cache and return true. The lock timeout is set through expire(key,second). After that, the timed task method is executed. When the second service task enters, when setting the lock, it is found that the lock already exists in the cache, and returns false. It does not jump to the execute scheduled task method.
Core code
1. Distributed lock facet
@Aspect @Slf4j @Component public class CacheLockAspect { private static final String LOCK_VALUE = "locked"; @Autowired private RedisConnection connection; @Around("execution(* *.*(..)) && @annotation(com.common.annotation.CacheLock)") public void cacheLockPoint(ProceedingJoinPoint pjp) { Method cacheMethod = null; for (Method method : pjp.getTarget().getClass().getMethods()) { if (null!=method.getAnnotation(CacheLock.class)){ cacheMethod = method; break; } } try { String lockKey = cacheMethod.getAnnotation(CacheLock.class).lockedPrefix(); long timeOut = cacheMethod.getAnnotation(CacheLock.class).expireTime(); if(null == lockKey){ throw new ManagerException(ErrorMsgEnum.LOCK_NAME_EMPTY); } if (connection.setNX(lockKey.getBytes(),LOCK_VALUE.getBytes())) { connection.expire(lockKey.getBytes(),timeOut); log.info("method:{}Get lock:{},Start running!",cacheMethod,lockKey); pjp.proceed(); return; } log.info("method:{}Not acquired lock:{},Failed to run!",cacheMethod,lockKey); } catch (Throwable e) { log.error("method:{},Running error!",cacheMethod,e); throw new ManagerException(ErrorMsgEnum.LOCK_JOB_ERROR,e); } } }
2. Handwritten method level notes
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheLock { String lockedPrefix() default ""; //Prefix of redis lock key long expireTime() default 10; //Time of key in redis, 1000S }
3. Scheduled task service
@Slf4j @Service public class TimeTaskService { /** * Perform scheduled tasks **/ @Scheduled(cron = "0 0 1 * * ?") @CacheLock(lockedPrefix = "TimeTaskService",expireTime=30) public void executeTask() { System.out.println("hello world!"); } }