微服务实例之间的JPA同步

时间:2018-12-18 09:12:51

标签: java hibernate spring-boot jpa spring-data-jpa

我有一个基于计时器的工作

@Component
public class Worker {

    @Scheduled(fixedDelay = 100)
    public void processEnvironmentActions() {
        Job job = pickJob();
    }

    public Job pickJob() {
        Job job = jobRepository.findFirstByStatus(Status.NOT_PROCESSED);
        job.setStatus(Status.PROCESSING);
        jobRepository.save(job);
        return job;
    }
}

现在,在大多数情况下,这应该给我正确的结果。但是,如果有两个微服务实例同时执行这段代码会发生什么?

我如何确保即使有多个服务实例,存储库也应始终仅将一个作业分配给一个实例,而不应分配给其他实例。

编辑: 我认为人们对@Transactional感到困惑/集中,因此将其删除。问题仍然相同。

4 个答案:

答案 0 :(得分:1)

我对spring-batch不熟悉,但是显然spring-batch实现了乐观锁定,因此如果另一个线程已经选择了相同的作业,则保存操作将失败。

请参见spring batch horizontal scaling

答案 1 :(得分:1)

  

但是,如果有两个微服务实例同时执行这段代码会发生什么?

答案通常是:这取决于。

所有这些都假定您的代码在事务内运行

  1. 乐观锁定。

    如果您的Job实体没有版本属性,即带有@Version注释的属性。 启用了乐观锁定。 如果要访问同一作业的进程,则在尝试保留已更改的作业实体并失败并显示OptimisticLockingException时,将会注意到版本属性已更改。 您所需要做的就是处理该异常,这样您的处理就不会死亡,但是会再次尝试获取下一个Job

  2. 没有(JPA级别)锁定。

    如果Job实体没有版本属性,则默认情况下,JPA将不应用任何锁定。 访问Job的第二个进程将发出一个更新,本质上是一个NOOP,因为第一个进程已经对其进行了更新。 两者都不会注意到问题。 您可能要避免这种情况。

  3. 悲观锁定

    pessimistic_write锁将阻止任何人在完成读写实体之前读取实体(至少这是我对JPA规范的理解)。 因此,这应避免第二个过程能够在第一个过程完成写入之前选择该行。 这可能会阻塞整个第二个过程。 因此,请确保持有这种锁的交易短。

    为了获得这种锁,请用findFirstByStatus注释存储库方法@Lock(LockModeType.PESSIMISTIC_WRITE)

当然,可能有一些库或框架可以为您处理这类细节。

答案 2 :(得分:0)

我同意canvasvg的回答,但是在不了解刷新策略的情况下,应使用jobRepository.saveAndFlush(job)方法,以确保将sql语句下推到数据库。

另请参阅@Conffusion

答案 3 :(得分:0)

@Jens Schauder的回答向我指出了正确的方向。让我共享代码,以指导其他人。这就是我解决问题的方式,我如下更改了工作类别

@Entity 
public class Job {
   @Version
   private Long version = null; 
   // other fields omitted for bervity
}

现在,让我们跟踪以下代码

@Transactional
public Job pickJob() {
    Job job = jobRepository.findFirstByStatus(Status.NOT_PROCESSED);
    job.setStatus(Status.PROCESSING);
    Job saved jobRepository.save(job);
    return saved;
}
  

注意 :确保返回saved对象,而不是job对象。如果您返回作业对象,它将在第二save天内失败   操作,因为用于job的版本计数将落后于   saved的内容。

Service 1                                 Service 2 

1. Read Object (version = 1)              1. Read Object (version = 1)
2. Change the object and save 
      (changes the version)
3. Continues to process                   2. Change the object and save 
                                               (this operation fails as 
                                                the version that was read 
                                                was 1 but in the DB version is 2)
                                          3. Skip the job processing 

这样,仅需一个过程即可完成作业。