Java Spring:悲观的_读/写不工作

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(

我有两台服务器连接到同一个数据库。两者都有计划的作业,我不在乎哪一个运行计划的作业,只要只有一个运行。因此,我们的想法是在DB中保留一个键值对,任何将该值读取为0的都将首先运行计划的作业

理想情况下,这样做是可行的:

  • 应用程序A和应用程序B同时运行计划作业
  • 应用程序A首先访问数据库,锁定表进行读写
  • 应用程序A将值设置为1并释放锁
  • 应用程序A开始处理计划作业
  • 应用程序B从其DB请求中读取值1,并且不运行计划作业
  • 我有一个配置表,用于保存锁的状态

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