背景
- Spring Task并不是为分布式环境设计的,在分布式环境下,这种定时任务是不支持集群配置的,如果部署到多个节点上,各个节点之间并没有任何协调通讯机制,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行。
解决方案
- 我们选择了用数据库+乐观锁的方式来解决任务互斥访问的问题。大致的思路是这样的,声明一把全局的“锁”作为互斥量,哪个应用服务器拿到这把“锁”,就有执行任务的权利,未拿到“锁”的应用服务器不进行任何任务相关的操作。
java + mongodb数据库实现
package com.kbao.kbcassist.job.entity;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
@Data
@Document
public class JobLocksMo {
@Id
private String id;
private String name;
private Integer status;
private String description;
@Indexed(background = true)
private Long version;
private String createId;
private LocalDateTime createTime;
private String updateId;
private LocalDateTime updateTime;
}
package com.kbao.kbcassist.common.dao;
import com.kbao.kbcassist.job.entity.JobLocksMo;
public interface JobLocksMongoDao {
void save(JobLocksMo jobLocksMo);
JobLocksMo findById(String id);
JobLocksMo lock(String id, Long version);
JobLocksMo unlock(String id);
}
package com.kbao.kbcassist.common.dao.impl;
import com.kbao.kbcassist.common.dao.JobLocksMongoDao;
import com.kbao.kbcassist.job.entity.JobLocksMo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
@Repository
public class JobLocksMongoDaoImpl implements JobLocksMongoDao {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public void save(JobLocksMo jobLocksMo) {
mongoTemplate.save(jobLocksMo);
}
@Override
public JobLocksMo findById(String id) {
return mongoTemplate.findById(id, JobLocksMo.class);
}
@Override
public JobLocksMo lock(String id, Long version) {
Query query = new Query();
Criteria criteria = new Criteria();
criteria.and("id").is(id);
criteria.and("status").is(0);
criteria.and("version").is(version);
query.addCriteria(criteria);
Update update = new Update();
update.set("status", 1);
update.inc("version", 1);
update.set("updateId", "system_job_lock");
update.set("updateTime", LocalDateTime.now());
return mongoTemplate.findAndModify(query, update, JobLocksMo.class);
}
@Override
public JobLocksMo unlock(String id) {
Query query = new Query();
Criteria criteria = new Criteria();
criteria.and("id").is(id);
criteria.and("status").is(1);
query.addCriteria(criteria);
Update update = new Update();
update.set("status", 0);
update.set("updateId", "system_job_unlock");
update.set("updateTime", LocalDateTime.now());
return mongoTemplate.findAndModify(query, update, JobLocksMo.class);
}
}
package com.kbao.kbcassist.task;
import com.kbao.kbcassist.common.dao.JobLocksMongoDao;
import com.kbao.kbcassist.email.service.CaseEmailService;
import com.kbao.kbcassist.job.entity.JobLocksMo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
@Slf4j
@Configuration
public class ScheduleTask {
@Autowired
private CaseEmailService caseEmailService;
@Autowired
private JobLocksMongoDao jobLocksMongoDao;
@Async("taskExecutor")
@Scheduled(cron = "0 0/3 6/1 * * ?")
public void receiveEmailByTask() {
JobLocksMo jobLocksMo = null;
try {
jobLocksMo = jobLocksMongoDao.findById("distributed_job_lock_email");
if (null != jobLocksMo && jobLocksMo.getStatus() == 0) {
JobLocksMo lock = jobLocksMongoDao.lock(jobLocksMo.getId(), jobLocksMo.getVersion());
if (null != lock) {
log.debug("定时任务开启。。。");
caseEmailService.receiveEmailByTask();
log.debug("定时任务结束。。。");
Thread.sleep(60 * 1000);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != jobLocksMo) {
jobLocksMongoDao.unlock(jobLocksMo.getId());
}
}
}
}