如何保证接口的幂等性?常见的实现方案有哪些?

什么是幂等性

幂等用于表示任意多次请求均与一次请求执行的结果相同,也就是说对于一个接口而言,无论调用了多少次,最终得到的结果都是一样的。

如何保证接口的幂等性

1、前端拦截

2、使用数据库实现幂等性

3、使用 JVM 锁实现幂等性

4、使用分布式锁实现幂等性

保证接口的幂等性的实现过程

1、前端拦截

前端拦截是指通过 Web 站点的页面进行请求拦截,比如在用户点击完“提交”按钮后,我们可以把按钮设置为不可用或者隐藏状态,避免用户重复点击。

但前端拦截有一个致命的问题,如果是懂行的程序员或者黑客可以直接绕过页面的 JS 执行,直接模拟请求后端的接口,这样的话,我们前端的这些拦截就不能生效了。因此除了前端拦截一部分正常的误操作之外,后端的验证必不可少。

2、数据库实现

a、通过悲观锁来实现幂等性

b、通过唯一索引来实现幂等性

c、通过乐观锁来实现幂等性

3. JVM 锁实现

JVM 锁实现是指通过 JVM 提供的内置锁如 Lock 或者是 synchronized 来实现幂等性。使用 JVM 锁来实现幂等性的一般流程为:首先通过 Lock 对代码段进行加锁操作,然后再判断此订单是否已经被处理过,如果未处理则开启事务执行订单处理,处理完成之后提交事务并释放锁,如图所示

JVM 锁存在的最大问题在于,它只能应用于单机环境,因为 Lock 本身为单机锁,所以它就不适应于分布式多机环境。

4. 分布式锁实现

分布式锁实现幂等性的逻辑是,在每次执行方法之前先判断是否可以获取到分布式锁,如果可以,则表示为第一次执行方法,否则直接舍弃请求即可

幂等性需要注意什么问题

幂等性的实现与判断需要消耗一定的资源,因此不应该给每个接口都增加幂等性判断,要根据实际的业务情况和操作类型来进行区分。例如,我们在进行查询操作和删除操作时就无须进行幂等性判断。查询操作查一次和查多次的结果都是一致的,因此我们无须进行幂等性判断。删除操作也是一样,删除一次和删除多次都是把相关的数据进行删除(这里的删除指的是条件删除而不是删除所有数据),因此也无须进行幂等性判断。

实现幂等性的关键步骤有哪些

1、每个请求操作必须有唯一的 ID,而这个 ID 就是用来表示此业务是否被执行过的关键凭证,例如,订单支付业务的请求,就要使用订单的 ID 作为幂等性验证的 Key;

2、每次执行业务之前必须要先判断此业务是否已经被处理过;

3、第一次业务处理完成之后,要把此业务处理的状态进行保存,比如存储到 Redis 中或者是数据库中,这样才能防止业务被重复处理。

说一说数据库实现幂等性的执行细节

1、通过悲观锁来实现幂等性

2、通过唯一索引来实现幂等性

3、通过乐观锁来实现幂等性

① 悲观锁

使用悲观锁实现幂等性,一般是配合事务一起来实现。

在实现的过程中需要注意以下两个问题:

a、如果使用的是 MySQL 数据库,必须选用 innodb 存储引擎,因为 innodb 支持事务;

b、id 字段一定要是主键或者是唯一索引,不然会锁表,影响其他业务执行。

比如:

begin;  # 1.开始事务

select * from table_name where id='xxx' for update; # 2.查询状态

insert into table_name (id) values ('xxx'); # 3.添加操作

update table_name set status='xxx'; # 4.更改操作

commit; # 5.提交事务

② 唯一索引

我们可以创建一个唯一索引的表来实现幂等性,在每次执行业务之前,先执行插入操作,因为唯一字段就是业务的 ID,因此如果重复插入的话会触发唯一约束而导致插入失败。在这种情况下(插入失败)我们就可以判定它为重复提交的请求。比如:

CREATE TABLE `table_name` (

  `id` int NOT NULL AUTO_INCREMENT,

  `orderid` varchar(32) NOT NULL DEFAULT '' COMMENT '唯一id',

  PRIMARY KEY (`id`),

  UNIQUE KEY `uq_orderid` (`orderid`) COMMENT '唯一约束'

) ENGINE=InnoDB;

③ 乐观锁

乐观锁是指在执行数据操作时(更改或添加)进行加锁操作,其他时间不加锁,因此相比于整个执行过程都加锁的悲观锁来说,它的执行效率要高很多。

乐观锁可以通过版本号来实现,例如以下 SQL:

update table_name set version=version+1 where version=0;

 

<think>我们正在讨论接口幂等性实现方案。根据引用,常见方案包括: 1. 利用数据库唯一约束(如唯一索引)防止重复记录[^2][^3] 2. 使用Token机制(包括前端生成或服务端生成一次性Token)[^2] 3. 基于状态机机制(确保状态流转的幂等性,如订单状态从“未支付”到“已支付”只能发生一次)[^3] 4. 采用乐观锁(通过版本号或条件更新)[^2] 5. 分布式锁(如Redis或ZooKeeper实现)[^4] 6. 幂等表(单独建立一张表来记录已处理的请求,通常结合唯一业务标识)[^2] 7. 通过AOP方式实现(自定义注解+拦截器/过滤器,结合Redis等存储)[^1][^4] 下面详细展开说明: </think>### 常见接口幂等性实现方案 根据引用资料,以下是分布式系统中广泛应用的接口幂等性解决方案及其原理: #### 1. **Token机制(前端防重)** - **原理**:服务端生成唯一Token(如UUID)并缓存至Redis,前端请求时携带该Token。服务端校验Token存在即执行操作并删除Token,重复请求因Token失效被拦截[^1][^4]。 - **适用场景**:用户交互请求(如表单提交、订单创建)。 - **示例代码**(SpringBoot + Redis): ```java // 生成Token接口 @GetMapping("/token") public String generateToken() { String token = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(token, "1", 10, TimeUnit.MINUTES); // 10分钟有效期 return token; } // 业务接口(带幂等注解) @Idempotent(fields = {"orderId"}) @PostMapping("/createOrder") public String createOrder(@RequestParam String token, String orderId) { // 业务逻辑 return "success"; } ``` #### 2. **数据库唯一约束** - **原理**:利用数据库唯一索引/主键约束(如订单号、支付流水号),重复插入会导致异常[^2][^3]。 - **适用场景**:新增类操作(如创建订单、支付记录)。 - **实现方式**: ```sql ALTER TABLE orders ADD UNIQUE INDEX idx_order_id (order_id); ``` 业务层捕获 `DuplicateKeyException` 异常并返回幂等响应。 #### 3. **幂等表方案** - **原理**:单独创建幂等表,以请求唯一标识(如 `request_id`)为主键,处理前先插入记录,重复请求因主键冲突失败[^2]。 - **优势**:与业务解耦,通用性强。 - **表结构示例**: | 字段 | 类型 | 说明 | |---------------|-------------|-------------------| | id | BIGINT | 自增主键 | | request_id | VARCHAR(64) | 唯一业务标识(UK) | | create_time | DATETIME | 记录创建时间 | #### 4. **状态机控制** - **原理**:基于业务状态流转(如订单状态:未支付→已支付),仅当当前状态符合预期才执行更新[^3]。 - **适用场景**:订单支付、状态变更。 - **SQL示例**: ```sql UPDATE orders SET status = 'paid' WHERE order_id = '123' AND status = 'unpaid'; -- 仅当状态为未支付时更新 ``` #### 5. **乐观锁机制** - **原理**:通过版本号(`version`)或时间戳控制更新,每次更新校验版本一致性[^2]。 - **实现示例**: ```java // 更新前先查询版本号 Order order = orderDao.selectById(orderId); // 更新时携带版本条件 int count = orderDao.update("UPDATE orders SET amount=100, version=version+1 WHERE id=#{id} AND version=#{currentVersion}"); if (count == 0) throw new IdempotentException("重复请求"); ``` #### 6. **分布式锁方案** - **原理**:使用Redis或Zookeeper实现互斥锁,确保同一请求同时只有一个线程处理[^4]。 - **Redis实现**: ```java String lockKey = "LOCK_ORDER_" + orderId; if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS)) { try { // 执行业务逻辑 } finally { redisTemplate.delete(lockKey); } } ``` #### 方案对比 | 方案 | 适用场景 | 复杂度 | 可靠性 | |--------------------|--------------------------|--------|--------| | Token机制 | 用户交互请求 | 中 | 高 | | 数据库唯一约束 | 数据新增操作 | 低 | 极高 | | 幂等表 | 所有写操作 | 中 | 高 | | 状态机/乐观锁 | 数据更新操作 | 中 | 高 | | 分布式锁 | 高并发场景 | 高 | 中 | > **最佳实践**:通常组合使用多种方案(如Token+数据库唯一约束),兼顾安全性与性能[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值