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所以删除它。问题保持不变。
但是如果有两个微服务实例同时执行这段代码会怎样呢?
答案通常是:视情况而定。
所有这些都假设您的代码在事务中运行
乐观锁。
如果您的Job实体具有版本属性,即带有注释的属性@Version。启用乐观锁定。如果要处理访问相同的作业,则在尝试持久保存更改的作业实体时会注意到版本属性发生了更改,并且失败并显示OptimisticLockingException. 您所要做的就是处理该异常,这样您的进程就不会终止,而是再次尝试获取下一个Job.
没有(JPA 级别)锁定。
如果Job实体没有版本属性,JPA 将默认不应用任何锁定。访问 a 的第二个进程Job会发出更新,这本质上是一个 NOOP,因为第一个进程已经更新了它。两者都不会注意到问题。你可能想避免这种情况。
悲观锁
pessimistic_write 锁将阻止任何人在您完成读写之前读取实体(至少这是我对 JPA 规范的理解)。因此,这应该避免第二个进程能够在第一个进程完成写入之前选择该行。这可能会阻止整个第二个过程。因此,请确保持有此类锁的事务很短。
为了获得这样的锁,findFirstByStatus用@Lock(LockModeType.PESSIMISTIC_WRITE).
当然,可能有一些库或框架可以为您处理这些细节。
@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。
。
Run Code Online (Sandbox Code Playgroud)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
这样一来,这项工作将仅由一个进程完成。
| 归档时间: |
|
| 查看次数: |
2195 次 |
| 最近记录: |