Java Spring:悲观的_读/写不工作
我有两台服务器连接到同一个数据库。两者都有计划的作业,我不在乎哪一个运行计划的作业,只要只有一个运行。因此,我们的想法是在DB中保留一个键值对,任何将该值读取为0的都将首先运行计划的作业 理想情况下,这样做是可行的:Java Spring:悲观的_读/写不工作,java,spring,spring-data-jpa,spring-data,spring-jdbc,Java,Spring,Spring Data Jpa,Spring Data,Spring Jdbc,我有两台服务器连接到同一个数据库。两者都有计划的作业,我不在乎哪一个运行计划的作业,只要只有一个运行。因此,我们的想法是在DB中保留一个键值对,任何将该值读取为0的都将首先运行计划的作业 理想情况下,这样做是可行的: 应用程序A和应用程序B同时运行计划作业 应用程序A首先访问数据库,锁定表进行读写 应用程序A将值设置为1并释放锁 应用程序A开始处理计划作业 应用程序B从其DB请求中读取值1,并且不运行计划作业 我有一个配置表,用于保存锁的状态 config: name: VARCHAR(
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);
}
调度程序:
@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。我认为您的事务太短了。您不会在run方法中启动事务,但每个ConfigService方法都是事务性的。很可能每个方法都会获得一个新事务,并在完成时提交。提交将释放锁,因此isLocked和lock之间存在竞争条件 联合收割机已锁定并锁定:
@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的东西。Ups。我忘了在
run()
方法上也有@Transactional
和@Async
注释。我将尝试您的建议,并添加一个时间戳来控制节点失败的情况。请注意,如果您使整个run方法成为事务性的,那么如果作业很长,您可能会遇到超时问题。祝你好运谢谢你,它很有魅力。我也从run()
中删除了@Transactional
。
@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;
}