Java 代码开发过程中,常常需要跑一些定时任务,而部署的时候为了提供高可用服务,往往部署在启多台实例。这就会引发一个问题,每台实例上的scheduled job都会同时运行,这种情况下可以加实例锁,保证同一时刻只会有一台实例会跑scheduled job.当然,这个问题也可继续做延伸: 做分布式部署的时候,如何保证线程安全?感兴趣的可以在评论区,把日常用的方案写出来,欢迎大家多多交流。
本文方案是使用锁机制,由于定时任务是多台实例在同一时间开始执行,可以采用锁机制:每台实例在跑之前,先去获取锁,成功获取锁的,开始执行定时任务,获取不到的,放弃执行。lockid 存储在mongodb中,当然也可以存储在redis中。
第一步,创建一个model,用于保存lockid.
@Data
@Document(collection = "scheduled_lock")
public class ScheduledLock{
@Id
private String id;
@CreatedDate
@Field("created_date")
@JsonIgnore
@Indexed(expireAfterSeconds = 3600) // = 3600 seconds
private ZonedDateTime createdDate = ZonedDateTime.now();
}
expireAfterSeconds 这个属性用来设置TTL.
第二步,实现lock, unlock方法。
public T lock(T lock) {
log.debug("Request to define lock : [{}]", lock);
return mongoLockRepository.insert(lock);
}
public void unlock(T lock) {
log.debug("Request to release lock : [{}]", lock);
mongoLockRepository.delete(lock.getId());
}
第三步,scheduled job跑之前先获取锁,获取不到的就不运行。
@Scheduled
public void scheduledJob() {
log.info("begin to run sheduled job ....");
ScheduledLock scheduledLock = new ScheduledLock();
scheduledLock.setId("scheduled_job");
try {
// lock the current instance
scheduledLockService.lock(scheduledLock);
try {
log.info("I get the lock and run schuduled job ...");
handleData();
} catch (Exception e) {
log.error("handle data error", e);
} finally {
log.info("Finished to process.");
// release lock
scheduledLockService.unlock(scheduledLock);
}
} catch (DuplicateKeyException exception) {
// don't get the lock
log.warn("run scheduled job can't get the lock id");
}
}