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");
}
}