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