SpringBoot提供了 Schedule模块完美支持定时任务的执行
在实际开发中由于项目部署在分布式或集群服务器上 会导致定时任务多次触发
因此,使用redis分布锁机制可以有效避免多次执行定时任务
核心方法是org.springframework.data.redis.core包下的
setIfAbsent() 方法 返回值为布尔类型
方法类似redis的SETNX命令 即”SET if Not Exists”
服务器在执行邮件定时发送任务之前会向redis缓存中写入lock_key即任务锁 表明此服务器正在执行定时任务
另一台服务器在写入锁时 由于锁已经存在就不做任何操作
执行定时任务的服务器在执行完成后需释放任务锁
具体代码实现如下:
定义注解:
/*** redis锁注解* @author zhouzhou*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {String lockPrefix() default "";String lockKey() default "";long timeOut() default 5;TimeUnit timeUnit() default TimeUnit.SECONDS;}
定义切面@Aspect, pointCut就是 RedisLock注解
/*** Description: redis锁拦截器实现* User: zhouzhou* Date: 2018-09-05* Time: 15:30*/
@Aspect
@Component
public class RedisLockAspect {private static final Integer MAX_RETRY_COUNT = 3;private static final String LOCK_PRE_FIX = "lockPreFix";private static final String LOCK_KEY = "lockKey";private static final String TIME_OUT = "timeOut";private static final int PROTECT_TIME = 2 << 11;//4096private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);@Autowiredprivate CommonRedisHelper commonRedisHelper;@Pointcut("@annotation(com.shuige.components.cache.annotation.RedisLock)")public void redisLockAspect() {}@Around("redisLockAspect()")public void lockAroundAction(ProceedingJoinPoint proceeding) throws Exception {//获取redis锁Boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());if (flag) {try {proceeding.proceed();Thread.sleep(PROTECT_TIME);} catch (Throwable throwable) {throw new RuntimeException("分布式锁执行发生异常" + throwable.getMessage(), throwable);} finally {// 删除锁this.delLock(proceeding);}} else {log.info("其他系统正在执行此项任务");}}/*** 获取锁** @param proceeding* @return*/private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {//获取注解中的参数Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);String key = (String) annotationArgs.get(LOCK_KEY);long expire = (long) annotationArgs.get(TIME_OUT);//String key = this.getFirstArg(proceeding);if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {// 此条执行不到throw new RuntimeException("RedisLock,锁前缀,锁名未设置");}if (commonRedisHelper.setNx(lockPrefix, key, expire)) {return true;} else {// 如果当前时间与锁的时间差, 大于保护时间,则强制删除锁(防止锁死)long createTime = commonRedisHelper.getLockValue(lockPrefix, key);if ((currentTime - createTime) > (expire * 1000 + PROTECT_TIME)) {count ++;if (count > MAX_RETRY_COUNT){return false;}commonRedisHelper.delete(lockPrefix, key);getLock(proceeding,count,currentTime);}return false;}}/*** 删除锁** @param proceeding*/private void delLock(ProceedingJoinPoint proceeding) {Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);String key = (String) annotationArgs.get(LOCK_KEY);commonRedisHelper.delete(lockPrefix, key);}/*** 获取锁参数** @param proceeding* @return*/private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {Class target = proceeding.getTarget().getClass();Method[] methods = target.getMethods();String methodName = proceeding.getSignature().getName();for (Method method : methods) {if (method.getName().equals(methodName)) {Map<String, Object> result = new HashMap<String, Object>();RedisLock redisLock = method.getAnnotation(RedisLock.class);result.put(LOCK_PRE_FIX, redisLock.lockPrefix());result.put(LOCK_KEY, redisLock.lockKey());result.put(TIME_OUT, redisLock.timeUnit().toSeconds(redisLock.timeOut()));return result;}}return null;}/*** 获取第一个String类型的参数为锁的业务参数** @param proceeding* @return*/@Deprecatedpublic String getFirstArg(ProceedingJoinPoint proceeding) {Object[] args = proceeding.getArgs();if (args != null && args.length > 0) {for (Object object : args) {String type = object.getClass().getName();if ("java.lang.String".equals(type)) {return (String) object;}}}return null;}}
CommonRedisHelper
/*** Description:* User: zhouzhou* Date: 2018-09-05* Time: 15:39*/
@Component
public class CommonRedisHelper {@AutowiredRedisTemplate redisTemplate;/*** 加分布式锁** @param track* @param sector* @param timeout* @return*/public boolean setNx(String track, String sector, long timeout) {ValueOperations valueOperations = redisTemplate.opsForValue();Boolean flag = valueOperations.setIfAbsent(track + sector, System.currentTimeMillis());// 如果成功设置超时时间, 防止超时if (flag) {valueOperations.set(track + sector, getLockValue(track, sector), timeout, TimeUnit.SECONDS);}return flag;}/*** 删除锁** @param track* @param sector*/public void delete(String track, String sector) {redisTemplate.delete(track + sector);}/*** 查询锁* @return 写锁时间*/public long getLockValue(String track, String sector) {ValueOperations valueOperations = redisTemplate.opsForValue();long createTime = (long) valueOperations.get(track + sector);return createTime;}}
使用场景:
@Scheduled(cron = "0,30 * * * * ? ")@RedisLock(lockPrefix = "hello",lockKey = "world")public void hello(){System.out.println("每隔30秒定时任务测试,当前时间为:" + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date()));}