背景
写了个定时任务,是单机的,但是生产环境往往为了保证程序的稳定性会进行多台机的集群部署,这样定时任务就会出现问题的风险,所以要保证在集群部署下只有一台机的定时任务执行任务,其他机检测到已经有机子在执行任务的时候就不会执行,当执行任务的那台机挂了,其他机可以无缝衔接出一台机接着执行任务,分布式锁就很适合这种场景的应用。
本来想用公司提供的elastic-job框架配合zookeeper来做定时任务,毕竟这个是专门做集群定时的,甚至可以做到把程序中不同定时任务,放在不同机子上同时执行,可以缓解单台服务器的定时压力。但是..我那个项目用到分库分表组件,版本比较新,而公司的elastic-job框架是经过修改过的版本,用的jar包比较老旧,总之搞了半天jar包冲突没能解决,要么我的分库分表jar包降级(极不情愿,之前的代码不是白写了),要么公司的elastic-job升级(不太可能),所以无奈放弃了这个方案。
所以自己用redis的setnx方法实现了一个简单的分布式锁,只要用代码简单控制住永远只有一台机的定时任务在跑就行了。
示例
controller:
用的spring自带的定时任务注解,每十五秒执行一次
package com.ly.mp.iov.controller;
import java.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import com.ly.mp.iov.service.AppCommentsTaskService;
/**
* 启辰app评论服务定时任务
* @author ly-zhaohy
*
*/
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class AppCommentsTask {
private static Logger logger = LoggerFactory.getLogger(AppCommentsTask.class);
@Autowired
AppCommentsTaskService appCommentsTaskService;
//3.添加定时任务
@Scheduled(cron = "0/15 * * * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void likeCancelLikeTask() {
//System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
appCommentsTaskService.likeCancelLikeTask();
}
}
serviceImpl:
多余的业务代码省略没放上来
package com.ly.mp.iov.service.impl;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ly.mp.iov.common.Constant;
import com.ly.mp.iov.mapper.AppCommentsTaskMapper;
import com.ly.mp.iov.service.AppCommentsTaskService;
import com.ly.mp.jedis.multi.MpJedis;
import jodd.util.StringUtil;
@Service("AppCommentsTaskService")
public class AppCommentsTaskServiceImpl implements AppCommentsTaskService {
private static Logger logger = LoggerFactory.getLogger(AppCommentsTaskService.class);
@Autowired
private MpJedis mpJedis;
@Autowired
private AppCommentsTaskMapper appCommentsTaskMapper;
@Transactional
public void likeCancelLikeTask() {
int num = 0;
String prefix = Constant.REDIS_PREFIX + "likeOrCancelLike";
String jobIdKey = Constant.REDIS_PREFIX + "jobid";
String jobId = null == mpJedis.get(jobIdKey) ? "" : mpJedis.get(jobIdKey).toString();
boolean flag = false;
if(StringUtils.isNotBlank(jobId) && Constant.getJobId().equals(jobId)) {
flag = true;
} else if(StringUtils.isBlank(jobId)) {
long a = mpJedis.setnx(jobIdKey, Constant.getJobId(), 5*60);
if(a > 0) flag = true;
}
logger.info("flag====={}",flag);
if(flag) {
logger.info("appCommentsTaskBegin..." + LocalDateTime.now());
try {
this.getLikeOrCancelLikeFromRedis(prefix, num);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
logger.info("appCommentsTaskEnd..." + LocalDateTime.now());
}
}
}
自定义Constant类:
package com.ly.mp.iov.common;
import java.util.UUID;
/**
* 存放一些公共的变量
* @author ly-zhaohy
*
*/
public class Constant {
public static final String REDIS_PREFIX = "iov:comments:venucia:";
private static String JOB_ID = "";
static {
JOB_ID = UUID.randomUUID().toString().replace("-", "");
}
public static String getJobId() {
return JOB_ID;
}
}
Constant类中在程序启动的时候生成一个独一无二不会重复的jobId(uuid)作为程序标识,定时任务启动时把这个jobId放到redis里,当各机子上的定时任务在执行时都要先判断redis里固定键的值有没有值,如果没有值就setnx放入自己的jobId并执行业务代码,如果有值并且值等于自己的jobId也会执行业务代码,如果有值且不等于自己的jobId说明是其他定时程序已经占用了,所以就放弃执行业务代码;这个值在redis里每次放入只有5分钟的有效时间,5分钟之后其他机子就有放进来的机会,如果执行定时任务的那台机子挂了,最多5分钟之后其他机子就可以继续执行定时任务了。如此,就实现了一个简单的分布式锁。
网友评论