微服务实例之间的JPA同步

Gan*_*ute 6 java hibernate jpa spring-data-jpa spring-boot

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

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我如何确保即使有多个服务实例,存储库也应该始终只为一个实例而不是其他实例提供一项工作。

编辑:我认为人们越来越困惑/专注于@Transactional所以删除它。问题保持不变。

Jen*_*der 5

但是如果有两个微服务实例同时执行这段代码会怎样呢?

答案通常是:视情况而定。

所有这些都假设您的代码在事务中运行

  1. 乐观锁。

    如果您的Job实体具有版本属性,即带有注释的属性@Version。启用乐观锁定。如果要处理访问相同的作业,则在尝试持久保存更改的作业实体时会注意到版本属性发生了更改,并且失败并显示OptimisticLockingException. 您所要做的就是处理该异常,这样您的进程就不会终止,而是再次尝试获取下一个Job.

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

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

  3. 悲观锁

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

    为了获得这样的锁,findFirstByStatus@Lock(LockModeType.PESSIMISTIC_WRITE).

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


Gan*_*ute 3

@Jens Schauder 的回答为我指明了正确的方向。让我分享代码,以便它指导其他人。这就是我解决问题的方法,我改变了我的工作类别,如下所示

@Entity 
public class Job {
   @Version
   private Long version = null; 
   // other fields omitted for bervity
}
Run Code Online (Sandbox Code Playgroud)

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

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

注意:确保返回的是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 
Run Code Online (Sandbox Code Playgroud)

这样一来,这项工作将仅由一个进程完成。