集群项目问题之一:定时任务的执行。即页面配置/项目启动等产生的定时任务如何在集群下维护?
本文仅供自己日后参考。
总结过程如下:
1.触发第一次启动线程时刻,计算下次执行时间放入redis;
2.执行中加锁并更新下次执行时间,到时间自动释放锁;
3.项目重新启动先取消已有线程,获取下次执行时间进行计算等待时间开启新的线程。
4.运行过程中修改定时任务相关配置,使用redis发布订阅重新开启定时任务。
代码示例:
- 配置/修改周期
- redis订阅
- 系统启动/重启加载
- redis工具类、订阅线程
/**
* 订阅消息
*
* @param subscriber 主题事件处理类
* @param channel 发布的主题
*/
public void subscribe(JedisPubSub subscriber, String... channel) {
execute(new JedisActionNoResult() {
@Override
public void action(Jedis jedis) {
jedis.subscribe(subscriber, channel);
}
});
}
/**
* 发布消息
*
* @param channel 主题
* @param message 消息
* @return Long
*/
public Long publish(String channel, String message) {
return execute(new JedisAction<Long>() {
@Override
public Long action(Jedis jedis) {
return jedis.publish(channel, message);
}
});
}
/**
* 删除keys,如果指定key不存在,则直接忽略
*
* @param keys
* @return 实际删除的数量
*/
public Long del(final String... keys) {
return execute(new JedisAction<Long>() {
@Override
public Long action(Jedis jedis) {
return jedis.del(keys);
}
});
}
/**
* 设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。
*
* @param key
* @param value
*/
public Boolean set(final String key, final String value) {
return execute(new JedisAction<Boolean>() {
@Override
public Boolean action(Jedis jedis) {
String result = jedis.set(key, value);
return LOCK_SUCCESS.equals(result);
}
});
}
/**
* 原子性完成两个操作,一是设置该Key的值为指定字符串,同时设置该Key在Redis服务器中的存活时间(秒数)
* 该命令主要应用于Redis被当做Cache服务器使用时
*
* @param key
* @param seconds 设置的值的存活时间
* @param value
*/
public void setex(final String key, final int seconds, final String value) {
execute(new JedisActionNoResult() {
@Override
public void action(Jedis jedis) {
jedis.setex(key, seconds, value);
}
});
}
/**
* 从redis中获取指定key对应的内容,如果key不存在, 返回null
* 如果与该Key关联的Value不是string类型,Redis将返回错误信息,因为GET命令只能用于获取string Value
*
* @param key
* @return
*/
public String get(final String key) {
return execute(new JedisAction<String>() {
@Override
public String action(Jedis jedis) {
return jedis.get(key);
}
});
}
/**
* 系统通知消息监听线程
*/
@Component
public class SubscriberThread extends Thread {
private static final Logger logger = LoggerFactory.getLogger(SubscriberThread.class);
@Autowired
private RedisClient redisClient;
@Autowired
private Subscriber subscriber;
public SubscriberThread() {
super("SubscriberThread");
}
@Override
public void run() {
try {
logger.info("开始订阅 ...");
redisClient.subscribe(subscriber, RaRedisKey.CHANNEL_SYSTEM_NOTICE, RaRedisKey.CHANNEL_BACKUP_ACTIFICIAL);
logger.info("订阅成功!");
} catch (Exception e) {
logger.error("获取订阅消息异常", e);
}
}
}
- 创建线程池
/**
* @ClassName: DynamicThreadConstants class
* @Description: 用于动态线程定时任务
**/
@Configuration
public class DynamicThreadConstants {
public static final Map<String, Future> DYNAMIC_THREAD_COLLECTION = new HashMap<>();
public static ScheduledThreadPoolExecutor DYNAMIC_THREAD_POOL_EXECUTOR;
@Value("${dynamic.thread.poolSize}")
public void init(int poolSize){
//初始化动态线程池
synchronized (this) {
if (null == DYNAMIC_THREAD_POOL_EXECUTOR) {
DYNAMIC_THREAD_POOL_EXECUTOR = new ScheduledThreadPoolExecutor(poolSize);
}
}
}
//周期审计日志
public static final String PERIOD_AUDIT_LOG_THREAD_NAME = "period_audit_log_thread";
}