wordpress转移整站,数字化转型,工业品牌设计公司,苏州住建网文章目录 六大方案解决接口幂等问题什么是接口幂等#xff1f;天然幂等不做幂等会怎么样#xff1f; 解决方案1#xff09;insert前先select2#xff09;使用唯一索引3#xff09;去重表加悲观锁4#xff09;加乐观锁之版本号机制5#xff09;使用 Redisson 分布式锁6天然幂等不做幂等会怎么样 解决方案1insert前先select2使用唯一索引3去重表加悲观锁4加乐观锁之版本号机制5使用 Redisson 分布式锁6Token 机制 六大方案解决接口幂等问题
什么是接口幂等
幂等idempotency本身是一个数学概念常见与抽象代数中代表一个函数或操作的结果不受其输入或者执行次数的影响例如f(n) 1^n无论 n 为多少f(n)的值永远为 1 。
在软件开发领域幂等对请求执行结果的一个描述这个描述就是无论执行多少次相同的请求产生的效果和返回的结果和发出单个请求是一样的。
举个例子
有时我们在填写某些form表单时保存按钮不小心快速点了两次表中竟然产生了两条重复的数据只是id不一样。我们在项目中为了解决接口超时问题通常会引入了重试机制。第一次请求接口超时了请求方没能及时获取返回结果此时有可能已经成功了为了避免返回错误的结果这种情况不可能直接返回失败吧于是会对该请求重试几次这样也会产生重复的数据。
天然幂等
那有没有有些情况是天然支持幂等的呢当然有比如说我要更新一个某记录的状态 status 1 具体的 sql 为 update table set status 1 where id 1 这种情况无论我执行多少次这条 sql 他的效果是一样的这就是天然支持幂等的。
不做幂等会怎么样
比如说用户在付款的时候同时点击多次付款按钮后端处理了多次扣款请求结果导致用户的账户扣了多次钱。妥妥 p0 事故呀
到这你又会说前端做个置灰按钮不就行了吗第一次付款完毕后那用户或者恶意攻击你服务器的人直接用脚本搞你不走前端你是防止不了的。
那接下来我将介绍六大解决接口幂等的方案速速点赞上车
解决方案
1insert前先select
通常情况下在保存数据的接口中我们为了防止产生重复数据一般会在insert前先根据name或code字段select一下数据。如果该数据已存在则执行update操作如果不存在才执行 insert操作。 该方案可能是我们平时在防止产生重复数据时使用最多的方案。但是该方案不适用于并发场景在并发场景中要配合其他方案一起使用否则同样会产生重复数据。
2使用唯一索引
通过在表中加上唯一索引保证数据的唯一性。如果有重复的数据插入会抛出DuplicateKeyException异常程序可以捕获异常并处理。不过这种方法只适用于插入数据的场景。
create table t_order(id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT 主键,code varchar(200) not null COMMENT 流水号,user_id int unsigned COMMENT 用户id,amount decimal(10,2) unsigned not null COMMENT 总金额,UNIQUE unq_code(code)
) COMMENT订单表;不要依靠唯一索引来保证接口幂等但建议使用唯一索引作为兜底避免产生脏数据。
伪代码如下
public void idempotent(OrderDO orderDO){try {// 执行核心业务...orderMapper.insert(orderDO);}catch(DuplicateKeyException e) {// 有重复的数据插入}
}3去重表加悲观锁
去重表本质上也是一种唯一索引方案。去重表是一张专门用于记录请求信息的表其中某个字段需要建立唯一索引用于标识请求的唯一性当客户端发出请求时服务端会将这次请求的一些信息如订单号、交易流水号等插入到去重表中如果插入成功说明这是第一次请求可以执行后续的业务逻辑如果插入失败说明这是重复请求可以直接返回或者忽略。
CREATE TABLE deduplication_table (id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT 主键,processed_code varchar(200) not null COMMENT 已处理的订单流水号,-- 省略其他字段UNIQUE unq_processed_code(processed_code)
) COMMENT去重表;使用for update加锁每次查询到都是最新的数据
select * from deduplication_table where processed_code xxx for update伪代码如下
public boolean idempotent(OrderDO orderDO){// 执行核心业务之前DoMain domain deduplicationMapper.selectForUpdate(orderDO.getProcessedCode);if(doamin ! null) {// 订单已经支付}
}4加乐观锁之版本号机制
既然悲观锁有性能问题为了提升接口性能我们可以使用乐观锁。需要在表中增加一个timestamp或者version字段这里以version字段为例。
在更新数据之前先查询一下数据
select id,amount,version from user id123;
如果数据存在假设查到的version等于1再使用id和version字段作为查询条件更新数据
update user set amount amount 100, version version 1 where id 123 and version 1;更新数据的同时version1然后判断本次update操作的影响行数如果大于0则说明本次更新成功如果等于0则说明本次更新没有让数据变更。
由于第一次请求version等于1是可以成功的操作成功后version变成2了。这时如果并发的请求过来再执行相同的sql
update user set amount amount 100,version version 1 where id 123 and version 1;该update操作不会真正更新数据最终sql的执行结果影响行数是0因为version已经变成2了where中的version1肯定无法满足条件。但为了保证接口幂等性接口可以直接返回成功因为version值已经修改了那么前面必定已经成功过一次后面都是重复的请求。
具体流程如下
具体步骤
先根据id查询用户信息包含version字段根据id和version字段值作为where条件的参数更新用户信息同时version1判断操作影响行数如果影响1行则说明是一次请求可以做其他数据操作。如果影响0行说明是重复请求则直接返回成功。
5使用 Redisson 分布式锁
基于 MySQL 也可以实现分布式锁但一般我们不会采用这种方式。
通常情况下我们一般会选择基于 Redis 或者 ZooKeeper 实现分布式锁Redis 用的要更多一点。
// 唯一标识
String uniqueId orderId123;
// 1. 根据唯一标识生成分布式锁对象
RLock lock redisson.getLock(lock: uniqueId);try {// 2. 尝试获取锁(Watch Dog 自动续期机制) if (lock.tryLock()) {// 3. 如果成功获取到锁说明请求还没有被处理执行业务逻辑} else {// 请求已经被处理直接返回}
} finally {// 4. 释放锁lock.unlock();
}
6Token 机制
Token 机制的核心思想是为每一次操作生成一个唯一性的凭证 token。这个 token 需要由服务端生成的因为服务端可以对 token 进行签名和加密防止篡改和泄露。如果由客户端生成 token可能会存在安全隐患比如客户端伪造或重复 token导致服务端无法识别和校验。
这样的话就需要两次请求才能完成一次业务操作 请求获取服务器端 tokentoken 需要设置有效时间可以设置短一点服务端将该 token 保存起来。 执行真正的请求将上一步获取到的 token 放到 header 或者作为请求参数。服务端验证 token 的有效性如果有效一般是通过删除 token 的方式来验证删除成功则有效执行业务逻辑并删除 token防止重复提交如果无效拒绝请求返回提示信息。
// 获取token
public String getToken(Long busId, Long userId){String UUID UUID.randomUUID().toString();stringRedisTemplate.opsForValue().set(busIduserId, UUID, 20, TimeUnit.SECONDS)return UUID;
}
// 发起业务请求携带token
public void doSomeBusiness(Parameter parameter) {Long busId parameter.getBusId;Long userId UserContext.getUserId();// 判断token是否存在Boolean deleted stringRedisTemplate.delete(busIduserId);if(deleted) {// 删除成功,代表重复请求不进行操作直接返回return;}// doSomeBusiness...
}具体步骤
用户访问页面时浏览器自动发起获取 token 请求。服务端生成 token保存到 redis 中然后返回给浏览器。用户通过浏览器发起请求时携带该 token。从 redis 中尝试删除 token 如果删除失败说明是第一次请求做则后续的数据操作。如果删除成功说明是重复请求不做任何操作。