Spring:PESSIMISTIC_READ / WRITE无效

时间:2018-06-17 18:26:27

标签: java spring spring-data-jpa spring-data spring-jdbc

我有两台服务器连接到同一个数据库。两个人都有预定的工作,我真的不在乎哪一个工作预定的工作,只要只有一个工作。因此,我们的想法是在数据库中保留一个键值对,无论哪个读取值,首先要运行预定作业。

理想情况下,这样可以这样:

  1. App A和App B同时运行预定作业。
  2. App A首先访问数据库,锁定表格以便阅读&写入。
  3. App A将值设置为1并释放锁定。
  4. App A开始处理预定的工作。
  5. App B从其DB请求中读取值1,并且不运行预定作业。
  6. 我有一个配置表,我在锁上保持状态。

    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等其他库的情况下保持这个非常简单。

1 个答案:

答案 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这样的东西。