如何避免重复请求的竞争条件?

i2loujxw  于 2021-07-03  发布在  Java
关注(0)|答案(3)|浏览(603)

假设我收到两个具有相同负载的并发请求。但我必须(1)执行单个支付事务(使用第三方api)和(2)以某种方式返回对两个请求的相同响应。这是第二个要求,使事情复杂化。否则,我可能会返回一个对重复请求的错误响应。
我有两个实体: Session 以及 Payment (相关途径) @OneToOne 关系)。 Session 有两个字段用于跟踪总体状态: PaymentStatus ( NONE , OK , ERROR ), SessionStatus ( CHECKED_IN , CHECKED_OUT ). 初始条件是 NONE 以及 CHECKED_IN .
请求负载确实包含一个唯一的会话号,我使用它来获取相关的会话。现在,假设支付服务对于一个唯一的订单id是“幂等的”:它只对一个给定的订单id执行一个事务。订单id也包含在请求负载中(对于twin请求的值相同)。
我想到的流程是这样的:
获取会话
如果 session.getPaymentStatus() == OK ,找到付款并返回成功响应。
执行付款
将付款保存到db。 Session 具有从请求负载生成的具有唯一约束的字段。因此,如果其中一个线程试图插入一个副本 DataIntegrityViolationException 将被抛出。我捕获它,找到已经插入的付款,并返回基于它的响应。
如果4中没有抛出异常,则返回相应的响应。
在这个流程中,似乎至少有一种情况,在这种情况下,我可能必须返回两个请求的错误响应,尽管支付事务已成功完成!例如,假设“first”请求发生错误,付款未完成,返回错误响应。但是对于“第二个”请求(处理该请求的时间恰好稍长),支付已经完成,但是在插入到db之后,发现已经插入的支付记录,并在此基础上形成错误响应。
我想避免所有这些类似比赛的情况。我觉得我错过了一些很明显的东西。从本质上讲,问题是如何让一个请求等待另一个请求完成。有没有一种方法可以利用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.");
bjp0bcyl

bjp0bcyl1#

你说的是“相同的有效载荷”。因此,您必须使用实现“same”概念的hash/equal方法来创建类负载。
然后为所有已启动的有效负载创建一个同步哈希集。
当处理下一个请求时,如果不存在,则创建新的有效负载并启动它。如果这样的负载已经存在,那么只需返回其结果。即使现有的有效负载也不能完成,要舒适地等待其结果,并将有效负载声明为completablefuture。

fbcarpbf

fbcarpbf2#

我想避免所有这些类似比赛的情况。我觉得我错过了一些很明显的东西。从本质上讲,问题是如何让一个请求等待另一个请求完成。有没有一种方法可以利用db事务和锁来顺利地处理这个问题?
我倾向于认为,没有办法消除返回错误响应的所有可能性,尽管付款处理成功,因为有太多的地方可能发生破损,包括在您自己的代码之外。但是,您可以通过应用一些锁定来消除不一致响应的一些机会。
例如,
获取会话
获取会话的 PaymentStatus 把它锁起来。您还必须包含代码,以确保在请求处理完成之前释放该锁,即使是在错误情况下(我对此没有进一步说明)。
如果 session.getPaymentStatus() != NONE ,返回相应的响应。
执行付款
将付款保存到db,我想这包括更新 PaymentStatus 或者 OK 或者 ERROR . 由于锁定,预计不会尝试插入副本。如果发生这种情况,那么需要通知管理员,并返回不同的响应,可能是501。
返回相应的响应。
请注意,成功付款的幂等性在这方面对您没有帮助,但是如果幂等性扩展到付款失败的情况,那么您原来的工作流将不会受到问题中描述的不一致响应问题的影响。

xxslljrj

xxslljrj3#

我认为将id分配给实体(希望对于相同的请求总是相同的)和 UNIQUE (id)约束是避免db重复的一个充分条件。
如果您想(出于我不知道的原因)避免第一个条件,您可以始终检查请求的时间戳,或者将持久层设计为在更新之前“手动”检查重复项。
但是,一如既往,问题是你想做什么?这里(stackoverflow)更多的是讨论/纠正实现,而不是理论问题。
编辑
如果我理解正确的话,那就是在某处设置一个公共静态标志(或者一个标志列表,你就明白了)。在您的服务中,您首先检查标志,如果为真,则等待它为假;最后执行主操作。
至于重复的请求,我会将每个请求与上一个请求进行比较。如果所有参数都相同,并且时间戳足够接近,我会返回状态400或其他什么。
但我还是不明白你为什么想要同样的回答。当然,您可以在收到每个请求之后和实际执行它之前等待任意的时间量,但是为什么不总是允许一个“唯一的”请求继续呢?

相关问题