集群服务器中定时任务多次执行的解决方案

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过期
			}

 

你可能感兴趣的:(数据库,其他)