其他(幂等性)

幂等性

幂等性:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。

什么情况下需要幂等

SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。
UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。
UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。
insert into user(userid,name) values(1,'a') 如userid为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性。
如userid不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性。
delete from user where userid=1,多次操作,结果一样,具备幂等性

方案

1、token 机制实现

通过token 机制实现接口的幂等性,这是一种比较通用性的实现方法。

示意图如下:

具体流程步骤:

  1. 客户端会先发送一个请求去获取 token,服务端会生成一个全局唯一的 ID 作为 token 保存在 redis 中,同时把这个 ID 返回给客户端

  2. 客户端第二次调用业务请求的时候必须携带这个 token

  3. 服务端会校验这个 token,如果校验成功,并删除 redis 中的 token,则执行业务。

  4. 如果校验失败,说明 redis 中已经没有对应的 token,则表示重复操作,直接返回指定的结果给客户端

注意:

  1. 对 redis 中是否存在 token 以及删除的代码逻辑建议用 Lua 脚本实现,保证原子性

  2. 全局唯一 ID 可以用百度的 uid-generator、美团的 Leaf 去生成

关键点 先删除token,还是后删除token。

后删除token:如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除。这个问题其实是数据库和缓存redis数据不一致问题,后续会写文章进行讲解。

先删除token:如果系统出现问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。而且校验token和删除token必须合成一个原子操作。

先删除token可以保证不会因为重复请求,业务数据出现问题。出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了。
token机制缺点
业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。

2、防重表(基于 mysql 实现)

这种实现方式是利用 mysql 唯一索引的特性。

示意图如下:

具体流程步骤:

  1. 建立一张去重表,其中某个字段需要建立唯一索引

  2. 客户端去请求服务端,服务端会将这次请求的一些信息插入这张去重表中

  3. 因为表中某个字段带有唯一索引,如果插入成功,证明表中没有这次请求的信息,则执行后续的业务逻辑

  4. 如果插入失败,则代表已经执行过当前请求,直接返回

3、基于 redis 实现

这种实现方式是基于 SETNX 命令实现的

SETNX key value:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。

该命令在设置成功时返回 1,设置失败时返回 0。

示意图如下:

具体流程步骤:

  • 客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段

  • 将该字段以 SETNX 的方式存入 redis 中,并根据业务设置相应的超时时间

  • 如果设置成功,证明这是第一次请求,则执行后续的业务逻辑

  • 如果设置失败,则代表已经执行过当前请求,直接返回

4,乐观锁机制

这种方法适合在更新的场景中,update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
根据version版本,也就是在操作库存前先获取当前商品的version版本号,然后操作的时候带上此version号。我们梳理下,我们第一次操作库存时,得到version为1,调用库存服务version变成了2;但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务传如的version还是1,再执行上面的sql语句时,就不会执行;因为version已经变为2了,where条件就不成立。这样就保证了不管调用几次,只会真正的处理一次。
乐观锁主要使用于处理读多写少的问题。

# 总结

这几种实现幂等的方式其实都是大同小异的,类似的还有使用状态机、悲观锁、乐观锁的方式来实现,都是比较简单的。

### 幂等性概述 幂等性是指某个操作可以多次执行而不会改变其最初的效果。具体来说,在编程和API设计中,如果一个请求被重复发送,无论发送多少次,最终的结果都应该是相同的,并且资源的状态也不会因为重复调用而发生额外变化[^2]。 ### API 设计中的幂等性重要性 对于网络服务而言,由于互联网环境存在诸多不确定性因素,客户端可能会遇到超时、断网等问题从而导致请求失败或不确定状态。此时为了保证业务逻辑的一致性和数据的安全可靠,就需要引入幂等机制来处理这类情况。当用户再次发起相同的操作时,服务器端能够识别这是同一个事务的不同尝试而不是新的独立事件,进而做出恰当响应而不影响实际效果[^3]。 ### 实现幂等性的几种方式 #### 使用防重Token令牌方案 一种常见的做法是在每次提交表单或者发出特定类型的HTTP请求之前生成唯一标识符作为token存放在缓存里;之后随同其他参数一起传给后台接口用于验证合法性以及判断是否已经成功处理过此条记录。一旦确认无误,则立即删除对应的key-value对防止后续可能存在的冲突风险[^4]。 ```java // Maven 引入相关依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> // 防重Token 令牌实现伪代码 public boolean checkAndSet(String key){ try{ // 尝试设置新值并返回旧值 String oldValue = redisTemplate.opsForValue().getAndSet(key,"1"); if (oldValue != null && !"0".equals(oldValue)){ log.info("Duplicate request detected."); return false; } // 设置失效时间以确保长时间未完成的任务自动清理 redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); return true; }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } ``` #### 利用数据库特性 另一种思路是利用关系型数据库提供的原子性更新语句(如MySQL里的`INSERT ... ON DUPLICATE KEY UPDATE` 或者 PostgreSQL 的 `UPSERT`) 来达成目的。这种方式适用于那些天然具有唯一约束条件的数据模型场景下,比如订单号、交易流水号等等[^5]。 ```sql -- MySQL 插入或更新示例 INSERT INTO orders(order_id,status) VALUES('unique_order_007','PENDING') ON DUPLICATE KEY UPDATE status=VALUES(status); -- PostgreSQL UPSERT 示例 INSERT INTO orders(order_id,status) VALUES('unique_order_008','COMPLETED') ON CONFLICT (order_id) DO UPDATE SET status=EXCLUDED.status; ``` #### 基于框架特性的解决方案 某些流行的Web开发框架也内置了支持幂等功能的工具集,例如Spring AOP可以通过面向切面编程的方式轻松地为指定的服务层方法添加幂等控制逻辑。只需简单配置几个注解就能让整个流程变得既简洁又高效。 ```python from spring.aop import idempotent @idempotent def withdraw(account_id: int, amount: float) -> bool: """模拟提款功能""" pass ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值