在多个docker实例(Spring JPA@Lock、Java synchronized、scheduled jobs等)上运行应用程序时,如何处理代码同步?
上下文
我们有一个Spring引导应用程序(angular前端使用的API)
它在docker容器上运行。 它使用PostgreSQL数据库的单个实例
我们的应用程序有一些负载问题,所以我们要求扩展它。 我们告诉我们在几个docker容器上运行API
在处理执行代码的多个docker实例上的代码同步时,我们有几个问题
问题1
我们有一些@Scheduled
作业与我们的API代码集成和部署
我们不希望所有容器实例都执行这些调度作业,而只希望一个容器实例执行这些调度作业
我认为我们可以通过使用"-"
值的环境变量禁用其他容器上的作业来简单地处理这个问题,以禁用Spring调度的cron
听起来对吗
问题2
另一个问题是,我们在一些存储库方法上使用了Spring的@Lock
注释
public interface IncrementRepository extends JpaRepository<IncrementEntity, UUID> {
@Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
Optional<IncrementEntity> findByAnnee(String pAnneeAA);
@Lock(LockModeType.PESSIMISTIC_WRITE)
IncrementEntity save(IncrementEntity pIncrementEntity);
}
这对于我们在获取/计算用作某些数据的唯一标识符的增量时锁定它是至关重要的
如果我正确理解了这种锁定机制:
如果进程执行此代码,Spring JPA
@Transaction
将获得IncrementEntity
上的锁(锁定数据库表)当另一个进程在第一个事务释放第一个锁之前尝试执行相同的操作时,它应该有一个
PessimisticLockException
,第二个事务将回滚这是由Spring在应用程序级别管理的,而不是直接在数据库级别(右??)强>
那么,如果我们在几个容器上运行代码,会发生什么强>
- 容器1中运行的应用程序设置锁
- 在容器2中运行的应用程序执行相同的代码,并在第一个锁尚未释放时尝试设置相同的锁
- 在不同容器中运行的每个Spring应用程序可能会毫无问题地获得锁,因为它们不共享相同的信息李>
请告诉我我是否正确理解了它的工作原理,以及在几个docker容器上运行这样的代码是否会有问题
我猜解决方案是直接在数据库表上设置锁,因为我们只有一个实例
有没有一种方法可以使用SpringJPA代码轻松地在数据库级别设置/释放锁
或者我误解了使用Spring的@Lock
注释设置锁会设置一个真正的数据库锁强>
在这种情况下,也许我们根本没有任何问题,因为锁是在数据库本身上正确设置的,由所有容器实例共享
问题3
为了避免出现太多异常并同时拒绝一些试图获取锁的请求,我们还围绕上述代码添加了一个同步块
String numIncrement;
synchronized (this.mutex) {
try {
numIncrement = this.incrementService.getIncrement(var);
} catch (Exception e) {
// rethrow custom technical exception
}
}
这样,并发请求应该被延迟并排队,这对我们的用户体验更好
我想我们在这里也会遇到问题,因为docker实例不共享同一个JVM,所以同步只能在容器本身的范围内工作。。。对吧?
结论
对于所有这些问题,请告诉我您是否有一些解决方案来解决/调整我们的代码,以便与应用程序扩展兼容
# 1 楼答案
经过一系列测试,我可以确认关于我的原始问题的这些观点
问题1
我们可以使用
-
值禁用Spring CRON问题2
Spring的JPa
@Lock
注释在数据库本身上设置了一个锁。它不是由Spring软件管理的因此,在复制容器时,如果第一个容器中的Spring应用程序设置了锁,则数据库将被锁定,而当另一个容器中的第二个应用程序尝试获取数据时,它具有
PessimisticLockException
问题3
使用
synchronized
JAVA关键字的同步代码显然是由JVM管理的,因此容器之间没有代码互斥