基于用户配置的数据库并发问题的自定义序列

时间:2019-12-28 08:36:23

标签: java spring spring-boot jmeter optimistic-locking

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

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

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

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

当我尝试使用 synchronized 时,我已经读过Spring @Transactional with synchronized keyword doesn't work,但没有用。我什至创建了一个单例类,该类使用同步签名调用我的服务,并且所有使用串行服务的API现在都调用此单例,以便它们可以彼此排成一行,但是它也不起作用。 / p>

现在我的问题是:处理此问题的正确方法是什么?我应该使数据库在表上执行锁定吗?还是应该使用不支持自定义样式(即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();
    }
}

1 个答案:

答案 0 :(得分:0)

我想这是正在发生的事情

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

同步generate方法无济于事,因为在进入该方法之前(在Spring生成的代理中)打开了事务,所以两个线程都看到相同的初始version

通常,假定冲突很少发生(因此名称为“乐观锁定”),通过重试该操作来处理OptimisticLockingFailureException是很正常的。就您而言,它们很常见。

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

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