功能介绍:
我系统中需要跑三个定时任务,由于是多节点部署,为了防止多个节点的定时任务重复执行。所以在定时任务执行时加个锁,抢到锁的节点才能执行定时任务,没有抢到锁的节点就不执行。从而避免了定时任务重复执行的情况
没有使用lambda表达式时的代码是这样的:
@Scheduled(cron = "${task.syncIncrement}") private void syncIncrementComment() { //获取redis锁,并把当前时间放入redis,锁定lockSeconds秒【插入之前判断是否已经有lockName了,如果存在则获取锁失败】 boolean lockKeyResult = redisTemplateHandler.redisSetNX(lockName, String.valueOf(Calendar.getInstance().getTimeInMillis()), lockSeconds); if (lockKeyResult) { //如果获取锁成功,执行业务代码 LOGGER.info("catch Redis-Task lock"); long startTime = System.currentTimeMillis(); LOGGER.info("开始同步原始数据..."); handler.getInterfaceCommentByCond(0); LOGGER.info("原始数据同步完成!用时:" + (System.currentTimeMillis() - startTime)); //业务代码执行完成,释放锁 boolean delResult = redisTemplateHandler.redisDelNX(lockName); LOGGER.info("free Redis-Task lock: {}", delResult); } else { //获取锁失败 LOGGER.info("do not catch Redis-Task lock"); if (!redisTemplateHandler.redisCheckNX(lockName, lockSeconds)) { //根据时间判断redis锁是否是失效锁, 执行锁失效,造成死锁 LOGGER.info("Redis-Task lock"); boolean redel = redisTemplateHandler.redisDelNX(lockName); // 释放执行锁 LOGGER.info("free Redis-Task lock: {}", redel); } } }
灰色部分就是对定时任务加的redis锁,可以看出,如果我要写10个定时任务那就要写十遍这些代码。这显然是不优雅的。所以我就想能不能把模板代码提取出来呢?然后把我们的要执行的业务代码当做参数传进来,这样的话我们就不用重复编写这些模板代码。而只需要关注我们的业务代码就好。
解决方案就是函数式接口->lambda表达式
改造:
1.编写函数式接口
@FunctionalInterfacepublic interface RedisLockFunction { public void excuteMonitor();}
2.提取模板代码
public void excuteInRedisLock(String lockName,RedisLockFunction lock) { boolean lockKeyResult = redisTemplateHandler.redisSetNX(lockName, String.valueOf(Calendar.getInstance().getTimeInMillis()), lockSeconds); if (lockKeyResult) { lock.excuteMonitor(); //业务代码,就这一行 boolean delResult = redisTemplateHandler.redisDelNX(lockName); LOGGER.info("free Redis-Task lock: {}", delResult); } else { LOGGER.info("do not catch Redis-Task lock"); if (!redisTemplateHandler.redisCheckNX(lockName, lockSeconds)) { // 执行锁失效,造成死锁 LOGGER.info("Redis-Task lock"); boolean redel = redisTemplateHandler.redisDelNX(lockName); // 释放执行锁 LOGGER.info("free Redis-Task lock: {}", redel); } } }
3.调用,可以与上面的做对比
@Scheduled(cron = "${task.syncIncrement}") private void syncIncrementComment() { excuteInRedisLock(WebConstants.TASK_LOCK_SYNCINCREMENTCOMMENT_HANDLE_MESSAGE,()->{ LOGGER.info("catch Redis-Task lock"); long startTime = System.currentTimeMillis(); LOGGER.info("开始同步原始数据..."); handler.getInterfaceCommentByCond(0); LOGGER.info("原始数据同步完成!用时:" + (System.currentTimeMillis() - startTime)); }); }
这样就实现了把代码当做参数传递到一个方法中取执行的功能。从而实现了代码的复用。
附redis锁的工具类代码
package com.ch.evaluation.handler;import java.util.Calendar;import java.util.concurrent.TimeUnit;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Component;@Componentpublic class RedisTemplateHandler { @Autowired private StringRedisTemplate stringRedisTemplate; /** * 插入分布式Job Redis锁 */ public boolean redisSetNX(String key, String val, long expire) { boolean result = stringRedisTemplate.execute((RedisCallback) connection -> { return connection.setNX(key.getBytes(), val.getBytes()); }); if (result) { stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS); } return result; } /** * 删除分布式Job Redis锁 */ public boolean redisDelNX(String key) { boolean result = stringRedisTemplate.delete(key); return result; } /** * 检查分布式Job Redis锁 */ public boolean redisCheckNX(String key, int lockSeconds) { long expireTime = stringRedisTemplate.getExpire(key); String nxValue = stringRedisTemplate.opsForValue().get(key); long time = 0; if (StringUtils.isNotBlank(nxValue)) { time = Calendar.getInstance().getTimeInMillis() - Long.valueOf(nxValue).longValue(); } if (expireTime <= 0 || time > lockSeconds * 1000L) { redisDelNX(key); return false; } return true; }}