gpt4 book ai didi

spring - REST 幂等实现 - 如何在已处理请求时回滚?

转载 作者:行者123 更新时间:2023-12-02 11:21:47 26 4
gpt4 key购买 nike

我正在努力实现的目标

我们有一个使用 Spring Boot、JPA 和 Hibernate 构建的 REST API。
使用 API 的客户端对网络的访问不可靠。为了避免最终用户出现太多错误,我们让客户端重试不成功的请求(例如,在超时发生后)。

由于在再次发送时我们无法确定请求尚未被服务器处理,因此我们需要使 POST 请求具有幂等性。也就是说,发送两次相同的 POST 请求不得两次创建相同的资源。

到目前为止我所做的

为了实现这一点,这是我所做的:

  • 客户端在自定义 HTTP header 中随请求一起发送 UUID。
  • 当客户端重新发送相同的请求时,会发送相同的 UUID。
  • 服务器第一次处理请求时,请求的响应与 UUID 一起存储在数据库中。
  • 第二次收到相同的请求时,会从数据库中检索结果并做出响应,而无需再次处理该请求。

  • 到现在为止还挺好。

    问题

    我有多个服务器实例在同一个数据库上工作,并且请求是负载平衡的。因此,任何实例都可以处理请求。

    在我当前的实现中,可能会发生以下情况:
  • 请求由实例1处理,耗时较长
  • 因为时间太长,客户端中止连接,重新发送相同的请求
  • 第二个请求由实例 2
  • 处理
  • 第一次请求处理完成,结果由实例1
  • 保存在数据库中
  • 第二个请求处理完成。当实例 2 尝试将结果存储在数据库中时,该结果已存在于数据库中。

  • 在这种情况下, 该请求已被处理两次,这是我想要避免的。

    我想到了两种可能的解决方案:
  • 当相同请求的结果已经存储时回滚请求 2,并将保存的响应发送给客户端。
  • 一旦实例 1 开始处理请求 2,就通过将请求 ID 保存在数据库中来防止处理请求 2。此解决方案不起作用,因为客户端和实例 1 之间的连接因超时而关闭,从而使客户端无法实际接收实例 1 处理的响应。

  • 尝试解决方案 1

    我正在使用 Filter检索和存储响应。我的过滤器大致如下所示:
    @Component
    public class IdempotentRequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    String requestId = getRequestId(request);


    if(requestId != null) {

    ResponseCache existingResponse = getExistingResponse(requestId);

    if(existingResponse != null) {
    serveExistingResponse(response, existingResponse);
    }
    else {

    filterChain.doFilter(request, response);

    try {
    saveResponse(requestId, response);
    serve(response);
    }
    catch (DataIntegrityViolationException e) {

    // Here perform rollback somehow

    existingResponse = getExistingResponse(requestId);
    serveExistingResponse(response, existingResponse);
    }
    }
    }
    else {
    filterChain.doFilter(request, response);
    }

    }

    ...

    然后我的请求像这样处理:
    @Controller 
    public class UserController {

    @Autowired
    UserManager userManager;

    @RequestMapping(value = "/user", method = RequestMethod.POST)
    @ResponseBody
    public User createUser(@RequestBody User newUser) {
    return userManager.create(newUser);
    }
    }

    @Component
    @Lazy
    public class UserManager {

    @Transactional("transactionManager")
    public User create(User user) {
    userRepository.save(user);
    return user;
    }

    }

    问题
  • 你能想到任何其他解决方案来避免这个问题吗?
  • 有没有其他解决方案可以使 POST 请求幂等(也许完全不同)?
  • 如何从 Filter 开始、提交或回滚事务如上所示?这是一个好习惯吗?
  • 在处理请求时,现有代码已经通过调用用 @Transactional("transactionManager") 注释的多个方法来创建事务。 .当我使用过滤器启动或回滚事务时会发生什么?

  • 注意:我对 spring、hibernate 和 JPA 比较陌生,对事务和过滤器背后的机制了解有限。

    最佳答案

    The request is processed by instance 1 and takes a long time



    考虑将过程分为 2 个步骤。

    第 1 步存储请求,第 2 步处理请求。在第一个请求中,您只需将所有请求数据存储在某处(可以是数据库或队列)。在这里你可以介绍一个状态,例如'新'、'进行中'、'准备好'。
    无论如何,您都可以使它们同步或异步。
    因此,在第二次尝试处理相同的请求时,您会检查它是否已经存储和状态。您可以在此处回复状态或等待状态变为“就绪”。
    因此,在过滤器中,您只需检查请求是否已存在(之前已存储),如果是,则获取要发送到响应的状态和结果(如果已准备好)。

    您可以向 RequestDTO 添加自定义验证注释 - @UniqueRequest 并添加 @Valid 以检查数据库(请参阅 the example )。无需在过滤器中执行此操作,而是将逻辑移动到 Controller (实际上这是验证的一部分)。
    在这种情况下如何响应取决于您 - 只需检查 BindingResult。

    关于spring - REST 幂等实现 - 如何在已处理请求时回滚?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45328135/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com