Web API的设计——Token令牌保证接口幂等性

236 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. 问题场景

现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有可能在服务器处理完毕后返回结果的时候挂掉,这个时候用户端发现很久没有反应,那么就会多次点击按钮,这样请求有多次,那么处理数据的结果是否要统一呢?那是肯定的!尤其在支付场景。

还有一些可能性:用于用户误操作,多次点击表单提交按钮;由于网速等原因造成页面卡顿,用户重复刷新提交页面;恶用户如利用postman等工具重复恶意提交表单

2. 幂等性?

当目标是消息驱动和异步通信时,可能会带来一些问题,例如,如果将重复的消息添加到系统中,它是否会破坏状态?假设我们有银行账户更新服务,我们发送一条消息向该账户添加1000美元,如果有重复的消息怎么办?如果收到重复的消息,系统将如何确保它不会两次添加钱?此外,该系统如何区分重复消息和新消息?

有各种技术可用于解决此问题,最常见的是为每条消息添加消息编号或ID,这样系统可以确保每个具有唯一ID的消息只处理一次,另一种方法是保留消息中的先前状态和新状态——比如旧余额为X且新余额为Y——并且系统负责应用验证确保消息中提到的状态(旧余额)匹配系统的状态。最重要的是,无论何时构建系统,我们都需要确保应用程序能够正确处理重复发送的消息并且不会破坏系统状态的情况。

简单来说幂等性就是保证接口唯一性

3. 重复提交的情况

/**
 * 测试防止重复提交
 * @return
 */
@RequestMapping(value = "/addNoRepeatCommit", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
public String noRepeatCommitForToken(@RequestBody User user) {
    // 添加到数据库
    int add = userService.addUser(user);
    // 添加完后查询所有数据
    log.info(String.valueOf(userService.findAll()));
    
    return add > 0 ? "添加成功" : "添加失败";
}

重复提交 ,数据库有重复数据

image.png

4. 解决方案

image.png

主要流程就是:

  1. 服务端提供了发送token的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取token,服务器会把token保存到redis中。(微服务肯定是分布式了,如果单机就适用jvm缓存)。
  2. 然后调用业务接口请求时,把token携带过去,一般放在请求头部。
  3. 服务器判断token是否存在redis中,存在表示第一次请求,这时把redis中的token删除,继续执行业务。
  4. 如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。