虾皮面试原题:有效保证接口的幂等性,避免重复操作导致的问题

分享一位群友在面试虾皮时遇到的关于接口幂等性的问题。

理解接口的幂等性

1. 接口幂等性定义

接口的幂等性意味着无论用户对同一操作发起一次还是多次请求,结果始终保持一致,不会因多次操作而产生副作用。举个简单的例子,假设用户在购买商品时完成了支付,然而在返回结果时发生网络异常,导致用户误以为未支付而再次点击支付按钮。这种情况下,用户的账户可能被多次扣款,从而引发接口幂等性的问题。

2. 实现幂等性的必要性

在接口调用的过程中,虽然大多数情况能正常返回信息并避免重复提交,但在以下情况下却容易出现问题:

  • 前端重复提交表单:用户在填写表单时,可能由于网络延迟未收到成功提交的确认,误以为提交失败而重复点击提交按钮,造成多次提交。
  • 恶意刷单:例如在投票系统中,用户可能会对同一选项进行多次提交,导致结果失真。
  • 接口超时重试:许多HTTP客户端工具默认开启超时重试机制,尤其是在调用第三方接口时,这可能导致重复请求的提交。
  • 重复消费消息:在使用消息队列中间件时,若未及时提交消费信息,可能会导致重复消费的情况。

通过实现幂等性,可以有效避免因重试操作引发的系统异常。

3. 需要保证幂等性的操作

在增删改查四个操作中,主要需要关注的是增加与修改操作。

  • 新增操作:在重复提交的情况下,新增操作可能引发问题,如支付场景。
  • 删除操作:删除数据的操作无论执行一次还是多次,其效果都是数据被删除,因此从这一点看,删除操作也是具有幂等性的。
  • 更新操作:更新操作在大部分情况下可保持一致,但如果是增量更新,则需要确保幂等性。例如,将某记录的A字段设置为1是幂等的,但将A字段增加1就不是。
  • 查询操作:查询操作自然是幂等的,数据不变的情况下,无论查询多少次返回结果一致。

4. 引入幂等性对系统的影响

虽然幂等性能够简化客户端逻辑并防止重复提交,但也增加了服务端的复杂度,其主要影响如下:

  • 将并行执行的功能改为串行执行,降低了执行效率。
  • 增加了控制幂等性的额外业务逻辑,导致业务功能的复杂化。

因此,在决定是否引入幂等性时,需要根据实际业务场景进行具体分析,通常情况下,并不需要在每个接口上实现幂等性。

常见的实现幂等性方案

方案一:使用数据库唯一主键

方案描述

数据库唯一主键的实现依托于数据库的唯一约束,通常适用于插入操作。当插入记录时,只有唯一主键能够保证一张表中只存在一条相同主键的记录。需注意使用分布式ID作为主键,以确保全局唯一性。

适用操作:

  • 插入操作
  • 删除操作

限制:

  • 需生成全局唯一主键ID。

方案二:数据库乐观锁

方案描述

乐观锁适用于“更新操作”,可在数据表中增加版本标识字段。每次更新时,需将当前记录的版本号作为条件,以确保数据的唯一性。

适用操作:

  • 更新操作

限制:

  • 需在业务表中增加版本号字段。

方案三:防重Token令牌

方案描述

在客户端进行连续点击或超时重试时,可以使用Token机制来防止重复提交。调用方请求一个全局ID(Token),并将其作为请求中的Header传递给后端。后端需对Token进行校验,并在存在的情况下执行业务逻辑。

适用操作:

  • 插入、更新、删除操作

限制:

  • 需生成全局唯一Token,使用Redis进行数据校验。

方案四:下游传递唯一序列号

方案描述

下游服务在请求时附带唯一序列号,以此作为请求标识。上游服务可根据序列号与下游提供的认证ID进行校验,以确定是否已处理该请求。

适用操作:

  • 插入、更新、删除操作

限制:

  • 需下游传递唯一序列号,使用Redis进行数据校验。

示例代码

以下是使用Token令牌方案的简单实现示例,确保在不同请求下的幂等性。

1. 引入依赖

本示例使用Maven进行依赖管理,需在pom.xml中引入SpringBoot、Redis、Lombok等相关依赖。

2. 配置Redis

在配置文件中添加Redis连接参数。

3. 创建与验证Token的工具类

编写用于生成与验证Token的Service类。

4. 创建测试Controller类

创建Controller类用于获取Token和测试接口的幂等性。

5. 启动应用程序

创建SpringBoot的主类启动整个应用。

6. 编写测试类

编写测试类,验证接口的幂等性,确保只有第一次调用成功。

总结

在开发中,接口的幂等性是一个常见而关键的需求,尤其是在涉及支付或订单的系统中。实现幂等性需要灵活选择具体的方案,确保满足业务需求,并关注每一个细节,以保障系统的正常运行。