业务场景
最近对公司项目做优化,之前的订阅项目是指定主副节点模式,目前为了迎合市场分布式微服务技术,需改为无主节点概念的模式。
通过查询资料决定用redisson做分布式锁,多节点根据持锁标识决定是否分发任务;
try {
log.info("======抢《apiErr任务》锁======" + ServerUtils.getHostIpAndPort());
boolean isGetLock = distributedLocker.tryLock(key, TimeUnit.SECONDS, 5L, 60 * 60L);
if (isGetLock) {
log.info("======获得《apiErr任务》锁======" + ServerUtils.getHostIpAndPort());
subscribeApiErrorService.handleApiErr(null, SubscribeConstants.TRIGGER_TYPE_0);
} else {
log.info("======未获得《apiErr任务》锁======" + ServerUtils.getHostIpAndPort());
}
} catch (Exception e) {
distributedLocker.unlock(key);
e.printStackTrace();
log.error("《apiErr任务》获取异常:" + e.getMessage(), e);
}
通过redisson是解决了多节分发问题,但是我们项目不是电商秒杀类的项目,是很定时任务分配,还需要解决多节点抢锁时间的校准。翻翻资料,参考XXL-JOB做了下符合我们业务场景的校准。
Quartz :
定时任务执行时使用数据库的表锁,保证一个任务只能被一个节点触发,而表锁随着事务的提交或者回滚自动释放。
xxl-job 触发校准:
xxl-job是基于 Quartz实现,拿到了距now 5秒内的任务列表数据:scheduleList,分三种情况处理:for循环遍历scheduleList集合
(1)对到达now时间后的任务:(超出now 5秒外):直接跳过不执行; 重置trigger_next_time;
(2)对到达now时间后的任务:(超出now 5秒内):线程执行触发逻辑; 若任务下一次触发时间是在5秒内, 则放到时间轮内(Map<Integer, List<Integer>> 秒数(1-60) => 任务id列表);
再 重置trigger_next_time;
(3)对未到达now时间的任务:直接放到时间轮内;重置trigger_next_time 。
经过参考xxl-job 的任务触发机制,调整订阅的任务分配流程,如下:
log.info("======《分配任务》开始======" + ServerUtils.getHostIpAndPort());
String key = "redisson_task_key";
try {
log.info("======抢《分配任务》锁======" + ServerUtils.getHostIpAndPort());
boolean isGetLock = distributedLocker
.tryLock(key, TimeUnit.SECONDS, 3*60L, 3*60L-1);
if (isGetLock) {
long time = System.currentTimeMillis();
log.info("======获得《分配任务》锁======" + ServerUtils.getHostIpAndPort());
if(null != redisUtil.get("_task_time")){
long taskTime = (Long)redisUtil.get("_task_time");
if((time<(taskTime-5*1000)) && (time>=(taskTime-3*60*1000))){
distributedLocker.unlock(key);
return;
}else if((time>(taskTime+5*1000)) &&(time<=(taskTime+3*60*1000)) ){
distributedLocker.unlock(key);
return;
}
}
redisUtil.set("_task_time",time+3*60*1000);
try {
//业务......
}catch (Exception e1) {
log.error("任务执行失败:" + e1.getMessage(), e1);
}
} else {
log.info("======未获得《分配任务》锁======" + ServerUtils.getHostIpAndPort());
}
} catch (Exception e) {
distributedLocker.unlock(key);
e.printStackTrace();
log.error("《分配任务》锁获取异常 ip:" + ServerUtils.getHostIpAndPort() + ",错误信息:" + e.getMessage(), e);
}
log.info("======《分配任务》结束======" + ServerUtils.getHostIpAndPort());
写的还有点简单,欢迎大佬指点!