1、Oracle
思路:Insert表内容时,主键重复,无法插入
范围:适用于执行频率低的定时任务
方法:新建一张任务执行表,每次执行定时任务之前先insert该表。
比如:每天执行一次批处理操作。主键可以设置为批处理name+日期(年月日)。集群服务器可能会同时去insert该表,而只有一台服务器能插入成功,则只让这一台服务器执行该批处理任务。
2、Redis
范围:适用于执行频率高的定时任务
有如下两种思路方法:
思路1:采用的是redis中list的push和pop操作。因为Redis是单线程,所有命令依次执行,所以不会出现多台服务器同时访问的情况。
方法:系统初始化时,向Redis中lpush一个list(key,1),作为标识Flag。每台服务器执行批处理之前,先去rpop该list,只有获取到该标识的服务器才能执行批处理任务,执行完毕后再lpush回一个list(key,1)。
比如:执行任务前先执行rpopRedisFlag,任务完成后执行lpushRedisFlag.
public class AutoJobActDis extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
if (rpopRedisFlag("AUTO_JOB_ACTDIS")) {
this.executeJob(this);
lpushRedisFlag("AUTO_JOB_ACTDIS");
}
}
/**
* 从缓存中取得Flag,获取定时任务执行权限
*
* @param redisKey
* @return
*/
public boolean rpopRedisFlag(String redisKey) {
String redisValue = redisService.rpopRedisList(redisKey);
if (StringUtil.isEmpty(redisValue)) {
return false;
} else {
return true;
}
}
/**
* 缓存中增加Flag
*
* @param redisKey
*/
public void lpushRedisFlag(String redisKey) {
redisService.lpushRedisList(redisKey, "1");
}
}
public class RedisService {
/**
* 存储Redis队列,顺序存储
* @param key redis键名
* @param value 键值
*/
public void lpushRedisList(String key, String value) {
Jedis jedis = null;
try {
jedis = jedisClient.getClient();
jedis.lpush(key, value);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* 移除并获取列表最后一个元素
* @param key
* @return
*/
public String rpopRedisList(String key) {
Jedis jedis = null;
try {
jedis = jedisClient.getClient();
return jedis.rpop(key);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return null;
}
}
思路2:采用的是redis中String的incr和expire操作。因为Redis是单线程,所有命令依次执行,所以不会出现多台服务器同时访问的情况。
语法:INCR key 将 key 中储存的数字值增一。
Redis Incr 命令将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
EXPIRE key seconds 为给定 key 设置过期时间。
Redis Expire 命令用于设置 key 的过期时间。key 过期后将不再可用。
方法:每台服务器执行批处理之前,先去incr一个固定key,只有返回值等于1的服务器才能执行批处理任务,然后将该key值设置过期时间。
比如:
jedis = jedisTool.getJedis();
if(jedis.incr(PRE_AGAIN_REDIS+batchId)!=1) {
logger.info("其他机器已经重跑");
return ;
} else {
jedis.expire(PRE_AGAIN_REDIS+batchId, 60);//设置60s过期
}