其他分享
首页 > 其他分享> > spring – REST Idempotence实现 – 如何在处理请求时回滚?

spring – REST Idempotence实现 – 如何在处理请求时回滚?

作者:互联网

我想要实现的目标

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

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

到目前为止我做了什么

要做到这一点,我就是这样做的:

>客户端在自定义HTTP标头中发送UUID以及请求.
>当客户端重新发送相同的请求时,将发送相同的UUID.
>服务器第一次处理请求时,请求的响应与UUID一起存储在数据库中.
>第二次收到相同的请求时,将从数据库中检索结果,并在不再处理请求的情况下进行响应.

到现在为止还挺好.

问题

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

使用我当前的实现,可能会发生以下情况:

>请求由实例1处理,需要很长时间
>因为它需要太长时间,客户端会中止连接并重新发送相同的请求
>第二个请求由实例2处理
>第一个请求处理完成,结果由实例1保存在数据库中
>第2次请求处理结束.当实例2尝试将结果存储在数据库中时,结果已存在于数据库中.

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

我想到了两种可能的解决方案:

>当已经存储了相同请求的结果时,回滚请求2,并将保存的响应发送到客户端.
>只要实例1开始处理请求ID,就通过在数据库中保存请求ID来阻止处理请求2.此解决方案不起作用,因为客户端和实例1之间的连接被超时关闭,使得客户端无法实际接收实例1处理的响应.

尝试解决方案1

我正在使用过滤器来检索和存储响应.我的过滤器看起来大致如下:

@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请求具有幂等性(或许完全不同)?
>如何从上面显示的过滤器启动事务,提交或回滚它?这是一个好习惯吗?
>处理请求时,现有代码已通过调用使用@Transactional(“transactionManager”)注释的多个方法创建事务.使用过滤器启动或回滚事务时会发生什么?

注意:我对spring,hibernate和JPA很新,而且我对事务和过滤器背后的机制了解有限.

解决方法:

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

考虑按两个步骤拆分流程.

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

您可以向RequestDTO添加自定义验证注释 – @UniqueRequest,并添加@Valid以检查DB(请参阅the example).不需要在Filter中执行此操作,而是将逻辑移动到Controller(实际上它是验证的一部分).
这取决于你如何在这种情况下作出回应 – 只需检查BindingResult即可.

标签:rest,spring,hibernate,transactions,idempotent
来源: https://codeday.me/bug/20190710/1427375.html