Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何避免重复请求的竞争条件?_Java_Spring_Spring Mvc_Concurrency_Architecture - Fatal编程技术网

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

Java 如何避免重复请求的竞争条件?,java,spring,spring-mvc,concurrency,architecture,Java,Spring,Spring Mvc,Concurrency,Architecture,假设我收到两个具有相同负载的并发请求。但是我必须(1)执行单个支付事务(使用第三方API)和(2)以某种方式返回两个请求的相同响应。这第二个要求使事情复杂化。否则,我可能会对重复请求返回一个错误响应 我有两个实体:会话和支付(通过@OneToOne关系关联)会话有两个字段来跟踪整体状态:付款状态(无,确定,错误),会话状态(签入,签出)。初始条件为NONE和已检入 请求负载确实包含一个唯一的会话号,我使用它来获取相关会话。现在,假设支付服务对于唯一的订单id是某种“幂等的”:它对于给定的订单id

假设我收到两个具有相同负载的并发请求。但是我必须(1)执行单个支付事务(使用第三方API)和(2)以某种方式返回两个请求的相同响应。这第二个要求使事情复杂化。否则,我可能会对重复请求返回一个错误响应

我有两个实体:
会话
支付
(通过
@OneToOne
关系关联)<代码>会话有两个字段来跟踪整体状态:
付款状态
确定
错误
),
会话状态
签入
签出
)。初始条件为
NONE
已检入

请求负载确实包含一个唯一的会话号,我使用它来获取相关会话。现在,假设支付服务对于唯一的订单id是某种“幂等的”:它对于给定的订单id只执行一个事务。订单id也包含在请求负载中(对于两个请求的值相同)

我心目中的流程是这样的:

  • 获取会话
  • 如果
    session.getPaymentStatus()==OK
    ,则查找付款并返回成功响应
  • 付款
  • 将付款保存到DB<代码>会话有一个字段,该字段具有从请求有效负载生成的唯一约束。因此,如果其中一个线程尝试插入一个副本,将抛出一个
    DataIntegrityViolationException
    。我捕获它,找到已经插入的付款,并基于它返回响应
  • 如果在4中没有抛出异常,则返回相应的响应
  • 在这个流程中,似乎至少有一个场景,尽管支付交易已成功完成,但我可能必须返回两个请求的错误响应!例如,假设“第一个”请求发生错误,付款未完成,返回错误响应。但对于“第二个”请求(处理时间恰好稍长),支付已完成,但在插入DB后,会发现已插入的支付记录,并在此基础上形成错误响应

    我想避免所有这些类似比赛条件的情况。我有一种感觉,我错过了一些非常明显的东西。本质上,问题在于以某种方式提出一个请求,等待另一个请求完成。有没有一种方法可以利用DB事务和锁来顺利地处理这个问题

    上面我假设支付服务对于给定的订单id是幂等的。如果不是,我必须绝对避免向它发送重复的请求,那该怎么办

    以下是服务方法的相关部分:

    Session Session=sessionRepo.findById(sessionId)
    .orelsetrow(SessionNotFoundException::new);
    Payment Payment=paymentManager.pay(session,req.getReference(),req.getAmount());
    节省的款项;
    试一试{
    保存=付款回购保存(付款);
    }捕获(DataIntegrityViolationException ex){
    saved=paymentRepo.findByOrderId(req.getReference())
    .orelsetrow(PaymentNotFoundException::new);
    }
    PaymentStatus=saved.getSession().getPaymentStatus();
    PaymentStage=saved.getSession().getPaymentStage();
    如果(阶段==完成和状态==正常)
    返回CheckOutResponse.success(req.getTerminalId(),req.getReference(),
    req.getPlateNumber(),saved.getAmount(),saved.getRrn());
    返回CheckOutResponse.error(req.getTerminalId(),req.getReference(),
    “无法完成交易。”);
    
    我认为,将id分配给实体(对于相同的请求,希望总是相同的)和
    唯一的
    (id)约束的组合构成了避免db重复的充分条件

    如果您想(出于我不知道的原因)避免第一种情况,您可以始终检查请求的时间戳,或者在更新之前将持久层设计为“手动”检查重复项

    但是,像往常一样,问题是你想实现什么?这里(StackOverflow)更多的是讨论/纠正实现,而不是理论问题

    编辑

    如果我理解正确的话,那就是在某处设置一个公共静态标志(或者一个标志列表,你明白了)。在您的服务中,您首先检查标志,如果为true,则等待它为false;然后最后执行主操作

    至于重复请求,我会将每个请求与最后一个请求进行比较。如果所有参数都相同,并且时间戳足够接近,我会返回状态400或其他

    但我还是不明白你为什么想要同样的回答。当然,您可以在收到每个请求之后和实际执行之前等待任意时间,但为什么不总是允许“唯一”请求继续

    我想避免所有这些类似比赛条件的情况。我有一个 感觉我错过了一些很明显的东西。从本质上讲 问题是以某种方式提出一个请求,等待另一个请求 完成有没有一种方法可以利用DB事务和锁 要顺利处理这件事

    我倾向于认为,尽管付款已成功处理,但仍无法消除返回错误响应的所有可能性,因为有太多地方可能发生破坏,包括您自己的代码之外。但是,您可以通过应用一些锁定来消除不一致响应的一些机会

    比如说,

  • 获取会话
  • 获取会话的
    PaymentStatus
    对其进行悲观锁定。您还必须包含代码,以确保在请求处理完成之前释放该锁,即使在错误情况下也是如此(我对此没有进一步说明)
  • 如果
    ses