Warm tip: This article is reproduced from serverfault.com, please click

java-如何避免重复条件的竞争条件?

(java - How to avoid race conditions for duplicate requests?)

发布于 2020-11-30 12:35:58

假设我收到两个具有相同有效负载的并发请求。但是我必须(1)执行一次付款交易(使用第三方API),并且(2)以某种方式对两个请求返回相同的响应。正是第二个要求使事情变得复杂。否则,我可能刚刚对重复的请求返回了错误响应。

我有两个实体:SessionPayment(通过@OneToOne关系关联)。Session有两个字段,以保持整体状态的轨迹:PaymentStatusNONEOKERROR), (SessionStatus)。CHECKED_IN CHECKED_OUT初始条件为NONECHECKED_IN

请求有效负载确实包含一个唯一的会话号,我用它来获取相关的会话。现在,假设付款服务对于唯一的订单ID来说是“幂等的”:对于给定的订单ID,它仅执行一次交易。订单ID也会出现在请求有效负载中(两个请求的值相同)。

我想到的流程大致如下:

  1. 取得会议
  2. 如果为session.getPaymentStatus() == OK,找到付款并返回成功响应。
  3. 执行付款
  4. 将付款保存到数据库。Session有一个具有从请求有效负载生成的唯一约束的字段。因此,如果其中一个线程尝试插入副本,DataIntegrityViolationException则会抛出a。我抓住了它,找到了已经插入的付款,并根据它返回了响应。
  5. 如果在4中未引发任何异常,则返回适当的响应。

在此流程中,似乎至少有一种情况,尽管付款交易已成功完成,我可能不得不对两个请求都返回错误响应例如,假设“第一个”请求发生了错误,付款未完成,并且返回了错误响应。但是对于“第二个”请求(恰好需要更长的处理时间),完成了付款,但是在插入到DB中后,发现了已插入的付款记录,并在此基础上形成了错误响应。

我想避免所有这些类似比赛条件的情况。而且我感觉我在这里遗漏了一些非常明显的东西。本质上,问题在于以某种方式发出一个请求,以等待另一个请求完成。有没有一种方法可以利用数据库事务和锁来平稳地处理此问题?

上面我假设给定订单ID的付款服务是幂等的。如果不是这样,我必须绝对避免向它发送重复的请求怎么办?

这是服务方法的相关部分:

Session session = sessionRepo.findById(sessionId)
        .orElseThrow(SessionNotFoundException::new);

Payment payment = paymentManager.pay(session, req.getReference(), req.getAmount());

Payment saved;
try {
    saved = paymentRepo.save(payment);
} catch (DataIntegrityViolationException ex) {
    saved = paymentRepo.findByOrderId(req.getReference())
            .orElseThrow(PaymentNotFoundException::new);
}

PaymentStatus status = saved.getSession().getPaymentStatus();
PaymentStage stage = saved.getSession().getPaymentStage();

if (stage == COMPLETION && status == OK)
    return CheckOutResponse.success(req.getTerminalId(), req.getReference(), 
            req.getPlateNumber(), saved.getAmount(), saved.getRrn());

return CheckOutResponse.error(req.getTerminalId(), req.getReference(),
            "Unable to complete transaction.");
Questioner
Ramin Ismayilov
Viewed
0
Alexei Kaigorodov 2020-12-01 04:53:04

你所说的是“相同的负载”。因此,你必须使用实现“相同”概念的哈希/等值方法来创建有效载荷类。

然后,为曾经启动的所有有效负载创建一个同步的哈希集。

处理下一个请求时,如果不存在,则创建新的有效负载,然后启动它。如果这样的有效载荷已经存在,则只需返回其结果。甚至现有的有效负载也无法完成,以轻松地等待其​​结果将有效负载声明为CompletableFuture。