我正在尝试实现的内容
我们有一个使用Spring Boot,JPA和Hibernate构建的REST API。 使用API的客户端对网络有不可靠的访问权限。为了避免最终用户出现太多错误,我们让客户端重试了不成功的请求(例如发生超时之后)。REST Idempotence实现 - 如何在请求已被处理时回滚?
由于我们无法确定服务器在再次发送请求时是否已经处理了请求,因此我们需要使POST请求具有幂等性。也就是说,发送两次相同的POST请求不得两次创建相同的资源。
我做了什么至今
要做到这一点,这里是我做过什么:
- 客户端与请求一起发送UUID,在自定义HTTP标头。
- 当客户端重新发送相同的请求时,会发送相同的UUID。
- 服务器首次处理请求时,请求的响应与UUID一起存储在数据库中。
- 第二次接收到相同的请求时,将从数据库中检索结果,并在不处理请求的情况下做出响应。
到目前为止好。
问题
我有服务器在同一数据库上工作的多个实例,并请求进行负载平衡。因此,任何实例都可以处理这些请求。
从我目前的执行情况,可能会出现以下情况:
- 请求由实例1处理,需要很长的时间
- 因为时间太长,客户端终止连接并重新发送相同的请求
- 该第二请求是由实例2
- 的第一请求处理结束处理,并且结果是通过实例1
- 的第二REQ保存在数据库加工完成。当实例2尝试将结果存储在数据库中时,结果已存在于数据库中。
在这种情况下,该请求已处理两次,这是我想要避免的。
我想到了两个可能的解决方案:
- 回滚时,对同一请求的结果已经存储的请求2,发送到客户端的响应保存。
- 只要实例1开始处理它,就通过将请求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相当陌生,而且我对交易和过滤器背后的机制的理解有限。
您对存储与地位的要求的解决方案有一个缺点:在每个请求的尝试,你会得到一个新的进程“等待”另一到结束。队列将变得越来越长,使得整个系统变慢,从而降低了系统为请求提供服务的能力。 [点击这里阅读](https://stackoverflow.com/questions/31621970/how-to-keep-an-api-idempotent-while-receiving-multiple-requests-with-the-same-id) 关于验证注解,它看起来像我必须为每个请求编写特定的回滚代码。代码基数很大,我宁愿避免它。我会尽量探索它。 – Laure