Spring:PESSIMISTIC_READ/WRITE 不工作

Spring: PESSIMISTIC_READ/WRITE not working

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

理想情况下,它会这样工作:

  1. 应用程序 A 和应用程序 B 运行 同时安排作业。
  2. 应用A先访问数据库,锁定table读写。
  3. 应用程序A将值设置为1并释放锁。
  4. 应用程序 A 开始处理计划的作业。
  5. 应用程序 B 从它的数据库请求中读取值 1,而不是 运行 计划的作业。

我有一个配置 table,我可以在其中保持锁状态。

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 似乎会在遇到死锁时重试事务。那时似乎一个应用程序已经完成,所以这个应用程序再次开始相同的工作(不确定)。

任务并不短,可以建立锁、table 更新、任务 运行 和释放锁。我想保持这个非常简单,而不涉及其他库,如 Quartz 或 ShedLock。

我认为您的交易时间太短了。您不会在 运行 方法中启动事务,但每个 ConfigService 方法都是事务性的。很可能每种方法都会获得一个新事务并在完成后提交。 commit 会释放锁,所以 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 这样的东西。