简体   繁体   English

微服务实例之间的JPA同步

[英]JPA synchronization between micro-service instances

I have a timer based job 我有一个基于计时器的工作

@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;
    }
}

Now, in the most situations this should give me correct result. 现在,在大多数情况下,这应该给我正确的结果。 But what will happen if there are two instances of microservice executing this piece of code at the same time? 但是,如果有两个微服务实例同时执行这段代码会发生什么?

How do I make sure that even if there are multiple instances of service, the repository should always give one job to only one instance and not other instances. 我如何确保即使有多个服务实例,存储库也应始终仅将一个作业分配给一个实例,而不分配给其他实例。

EDIT: I think people are getting confused/concentrated over @Transactional so removed it. 编辑:我认为人们对@Transactional感到困惑/集中,因此将其删除。 The question remains the same. 问题仍然相同。

I'm not familiar to spring-batch but apparently spring-batch implements optimistic locking so the save operation will fail if another thread already picked the same job. 我对spring-batch不熟悉,但显然spring-batch实现了乐观锁定,因此如果另一个线程已经选择了相同的作业,则保存操作将失败。

See spring batch horizontal scaling 请参阅春季批水平缩放

But what will happen if there are two instances of microservice executing this piece of code at the same time? 但是,如果有两个微服务实例同时执行这段代码会发生什么?

As so often the answer is: It depends. 答案通常是:这取决于。

All this assumes your code runs inside a transaction 所有这些都假定您的代码在事务内运行

  1. Optimistic locking. 乐观锁。

    If your Job entity hast a version attribute, ie an attribute annotated with the @Version annotation. 如果您的Job实体没有版本属性,即使用@Version注释注释的属性。 Optimistic locking is enabled. 启用了乐观锁定。 If to processes access the same job one will notice the versions attribute changed when trying to persist the changed job entity and fail with an OptimisticLockingException . 如果要访问同一作业的进程,则在尝试保留更改后的作业实体时会注意到版本属性已更改,并因OptimisticLockingException而失败。 All you have to do is handle that exception so you process doesn't die but tries again to get the next Job . 您所要做的就是处理该异常,因此您的处理不会死亡,但是会再次尝试获取下一个Job

  2. No (JPA level) locking. 无(JPA级别)锁定。

    If the Job entity doesn't have a version attribute, JPA will be default not apply any locking. 如果Job实体没有版本属性,则JPA将默认不应用任何锁定。 The second process accessing a Job would issue an update, that essentially is a NOOP, since the first process already updated it. 访问Job的第二个进程将发出一个更新,本质上是一个NOOP,因为第一个进程已经对其进行了更新。 Neither will notice the problem. 两者都不会注意到问题。 You probably want to avoid this. 您可能想避免这种情况。

  3. Pessimistic locking 悲观锁定

    A pessimistic_write lock will prevent anyone from reading the entity before your done reading and writing it (at least that is my understanding of the JPA spec). pessimistic_write锁将阻止任何人在完成实体读写之前读取实体(至少这是我对JPA规范的理解)。 Therefore this should avoid the second process to be able to select the row before the first process is done writing it. 因此,这应避免第二个过程能够在第一个过程完成写入之前选择该行。 This probably blocks the whole second process. 这可能会阻塞整个第二个过程。 So make sure the transaction holding such a lock is short. 因此,请确保持有此锁的事务短。

    In order to obtain such a lock annotate the repository method findFirstByStatus with @Lock(LockModeType.PESSIMISTIC_WRITE) . 为了获得这种锁,请使用@Lock(LockModeType.PESSIMISTIC_WRITE)注释存储库方法findFirstByStatus

Of course, there might be libraries or frameworks out there that handle these kinds of details for you. 当然,可能有一些库或框架可以为您处理这些细节。

@Jens Schauder 's answer points me in the right direction. @Jens Schauder的回答向我指出了正确的方向。 Let me share the code so it guides other people. 让我共享代码,以指导其他人。 This is how I solved my problem, I changed my job class as below 这就是我解决问题的方式,我如下更改了工作类别

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

Now, let's trace the following code 现在,让我们跟踪以下代码

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

Note : Make sure you return the saved object and not the job object. 注意 :确保返回saved对象而不是job对象。 If you return the job object, it'll fail for second save operation as the version count that was for job will be behind than that for saved . 如果您返回作业对象,则该作业对象将无法进行第二次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 

This way the job will picked up by only one process. 这样,仅需一个过程即可完成工作。

I agree with @Conffusion answer, but without knowing about the flush policy you should use jobRepository.saveAndFlush(job) method so that you are sure sql statements are pushed down to the database. 我同意@Conffusion的回答,但是在不了解刷新策略的情况下,应该使用jobRepository.saveAndFlush(job)方法,以确保将sql语句下推到数据库。

see also Difference between save and saveAndFlush in Spring data jpa 另请参见Spring data jpa中save和saveAndFlush之间的区别

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM