首页 > 解决方案 > 基于用户配置的数据库并发问题的自定义序列

问题描述

在我们的项目中,我们创建了一个表(即 serial_conf),其中包含一些用户可以设置的配置。当我们需要为特定实体生成序列号(即 28_12_2019_0001)时,我们调用此序列号的服务类,该服务类上有@Transactional

该服务正在执行以下操作:

  1. 从数据库中获取当前的串行配置
  2. 使用返回的对象生成序列号
  3. 更新序列号的计数器,以便下次我们可以生成正确的序列号并增加计数器(即 0002)
  4. 将结果字符串返回给服务的调用者

现在的问题是,当我使用 Jmeter 对 API 进行压力测试时(同时 10 个线程)。这个 API 做了很多操作,其中之一就是生成一个序列号。在 10 个线程中,只有 2 或 4 个通过,其他线程在串行服务的第 1 步抛出 OptimisticLockingFailureException。

我读过Spring @Transactional with synchronized 关键字在我尝试使用同步时不起作用,但它不起作用。我什至创建了一个单例类,它使用同步签名调用我的服务,所有使用串行服务的 API 现在都调用这个单例,这样它们就可以一个接一个地排列,但它也不起作用。

现在我的问题是:处理这个问题的正确方法是什么?我应该让数据库在表上做一个锁吗?还是应该使用不支持自定义样式的数据库序列(即 28_12_2019_0001)?

(我使用的是 spring-boot 1.5、hibernate 和 postgresql)

编辑1: 假设服务是这样的:

@Service("SerialService")
@Transactional(readOnly = false)
public class SerialService{

    @Autowired
    private SerialRepo repo;

    public String generate(Long userId){
            Serial serial = repo.findByUserId(userId).get();
            serial.setCounter(serial.getCounter() + 1);
            serial = repo.save(serial);
            return "custom_serial_"+serial.getCounter().toString();
    }
}

标签: javaspringspring-bootjmeteroptimistic-locking

解决方案


我想这就是正在发生的事情:

  1. 线程A:进入generate方法,调用repo.findByUserId并阻塞,等待结果。
  2. 线程B:进入generate方法,调用repo.findByUserId并阻塞,等待结果。
  3. 线程 A:repo.findByUserId调用完成,返回Seriala version= 1。
  4. 线程 B:repo.findByUserId调用完成,返回Seriala version= 1。
  5. 线程 A:更新计数器并保存实体。版本更新为 2。
  6. 线程 B:更新计数器并尝试保存实体。由于版本不匹配而引发 OptimisticLockingFailureException。

同步该generate方法没有帮助,因为在进入该方法之前(在 Spring 生成的代理中)打开了事务,因此两个线程看到相同的初始version.

一般来说,假设冲突很少发生(因此名称:“乐观锁定”),通过重试操作来处理 OptimisticLockingFailureException 是很正常的。在您的情况下,它们非常频繁。

问题是您的测试是否反映了真实的应用程序使用情况。我假设他们使用相同的userId. 我进一步猜测,Serial每个用户都有单独的实例,因此它们是单独版本的。这意味着,仅当一个用户并行发送两个请求时才会抛出 OptimisticLockingFailureException。它可能会发生,但我猜它不应该那么频繁,所以重试是可以接受的。

我建议您使用不同的用户 ID 重复您的测试。


推荐阅读