java - Spring :PESSIMISTIC_READ/WRITE 不起作用

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

我有两台服务器连接到同一个数据库。两者都有预定的作业,我并不关心哪一个运行预定的作业,只要只有一个运行即可。因此,我们的想法是在数据库中保留一个键值对,并且无论哪个读取值为 0,第一个都可以运行计划的作业。

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

  1. 应用 A 和应用 B 同时运行计划作业。
  2. 应用A首先访问数据库,锁定表的读写。
  3. 应用 A 将值设置为 1 并释放锁定。
  4. 应用 A 开始执行预定作业。
  5. 应用 B 从其数据库请求中读取值 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);
}

服务:

@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 等其他库。

最佳答案

我认为您的交易时间太短。您不会在 run 方法中启动事务,但每个 ConfigService 方法都是事务性的。最有可能的是,每个方法都会获得一个新事务并在完成后提交。提交会释放锁,因此 isLocked 和锁之间存在竞争条件。

结合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 这样的东西。

关于java - Spring :PESSIMISTIC_READ/WRITE 不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50899370/

相关文章:

java - Hibernate从子实体获取父实体的id

java - 如何添加新的 SSL 连接 Spring Boot jks cacerts

java - 主备服务器的回复-响应模式

java.lang.NoClassDefFoundError : org/hibernate/cache/TimestampsRegion

java - H2 未使用我的 data.sql 文件创建架构时出错

java - 从两个 ArrayList 中提取特定连接的对象,组合这些相关对象并从子类中打印

java - Maven 没有运行 JUnit 测试

java - 命令行是以编程方式配置 Wildfly 10 的唯一方法吗?

java - 关于自定义比较器

java - Spring Data 中的自定义方法实现失败并出现属性未找到错误