我有两台服务器连接到同一个数据库。两个人都有预定的工作,我真的不在乎哪一个工作预定的工作,只要只有一个工作。因此,我们的想法是在数据库中保留一个键值对,无论哪个读取值,首先要运行预定作业。
理想情况下,这样可以这样:
我有一个配置表,我在锁上保持状态。
config:
name: VARCHAR(55)
value: VARCHAR(55)
存储库:
@Repository
public interface ConfigRepository extends CrudRepository<Config, Long> {
@Lock(LockModeType.PESSIMISTIC_READ)
Config findOneByName(String name);
@Lock(LockModeType.PESSIMISTIC_WRITE)
<S extends Config> S save(S entity);
}
服务:
@Service
public class ConfigService {
@Transactional
public void unlock(ConfigEnum lockable) {
Config lock = configRepository.findOneByName(lockable.getSetting());
lock.setValue("0");
configRepository.save(lock);
}
@Transactional
public void lock(ConfigEnum lockable) {
Config lock = configRepository.findOneByName(lockable.getSetting());
lock.setValue("1");
configRepository.save(lock);
}
@Transactional
public boolean isLocked(ConfigEnum lockable) {
Config lock = configRepository.findOneByName(lockable.getSetting());
return lock.getValue().equals("1");
}
}
计划程序:
@Component
public class JobScheduler {
@Async
@Scheduled("0 0 1 * * *")
@Transactional
public void run() {
if (!configService.isLocked(ConfigEnum.CNF_JOB.getJobName())) {
configService.lock(ConfigEnum.CNF_JOB.getJobName());
jobService.run();
configService.unlock(ConfigEnum.CNF_JOB.getJobName());
}
}
}
但是我注意到两个应用程序上的预定作业仍在同一时间运行。有时会出现死锁,但是如果遇到死锁,Spring会重试该事务。在那个时候,似乎一个应用程序已经完成,所以这个应用程序再次开始相同的工作(不确定)。
任务不是那么短,可以建立锁,表更新,任务运行和锁释放。我想在不涉及Quartz或ShedLock等其他库的情况下保持这个非常简单。
答案 0 :(得分:1)
我认为你的交易太短了。您不会在run方法中启动事务,但每个ConfigService方法都是事务性的。很可能每个方法都会获得一个新事务并在完成后提交。提交将释放锁定,因此isLocked和lock之间存在竞争条件。
组合isLocked并锁定:
@Transactional
public boolean tryLock(ConfigEnum lockable) {
Config lock = configRepository.findOneByName(lockable.getSetting());
if("1".equals(lock.getValue()) {
return false;
}
lock.setValue("1");
configRepository.save(lock);
return true;
}
这在同一个交易中进行检查和写入,应该有效。
作为旁注,这是一种危险的方法。如果具有锁定的节点死亡会发生什么?有许多可能的解决方案。一种是锁定特定记录并在整个作业中保持锁定。另一个节点无法继续,如果第一个节点死亡,则锁定将被释放。另一种方法是使用时间戳而不是1,并要求所有者定期更新时间戳。或者你可以介绍像Zookeeper这样的东西。