接口幂等性

1. 什么是幂等性?

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。比如说支付场景,用户购买了商品支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条,这就是违反了接口的幂等性。

2. 需要幂等性的场景?

  • 表单重复提交
  • 表单页面“回退”再次提交
  • 微服务互相调用,由于网络问题,导致请求失败(feign 触发重试机制)
  • 队列服务的重复消费的问题(库存扣减)

3. 幂等和非幂等的操作场景

3.1 幂等性操作
SELECT * FROM table WHERE id = ?
UPDATE tbl SET col1 = 2 WHERE id = 1;
DELETE FROM tbl WHERE id = 1;
# 幂等性操作前提: user_id, name 是联合唯一索引
INSERT INTO user(user_id, name) VALUEs(1, 'a');
3.2 非幂等性操作
UPDATE tbl SET col = col + 1 WHERE id = 1;
# 幂等性操作前提: user_id, name **不是**联合唯一索引
INSERT INTO user(user_id, name) VALUEs(1, 'a');

4. 如何保证幂等性?

4.1 token 机制

给每一个表单分配一个 TOKEN(Redis分发),需要注意的是:
getToekn & getToken.equlas(formToken) & delToken() 必须是原子性操作。

4.2 数据库表的锁机制
  • 悲观锁
SELECT * FROM tbl WHERE id = 1 FOR UPDATE;

悲观锁使用时一般伴随事务一起使用,数据锁定的时间可能会很长,需要根据实际情况选用。另外需要注意的是,id 字段一定是主键或者唯一索引,不然可能造成锁表的结果,处理起来会非常麻烦。

  • 乐观锁

这种场景适合在更新的场景中:

UPDATE t_goods SET `count` = `count` - 1, version = version + 1 where goods_id = 2 AND version = 1

根据 version 版本号,也就是操作库存之前先获取当前商品的 version 版本号,然后操作的时候带上此 version 号。我们梳理下,我们第一次操作库存时,得到 version 为1,调用库存服务 version 变成2;但返回订单服务出现了问题,订单服务又一次发起调用库存服务释放库存的方法,当订单服务传入的 version 还是1时,再执行上面的 SQL 语句时,就不会执行;因为 version 已经变成2了, where 条件就不成立了。这样就保证了不管调用几次,只会真正处理一次。

乐观锁主要用户读多写少的问题。

4.3 分布式锁

如果多个机器可能在同一时间同时处理相同的数据,比如多台机器定时任务都拿到了相同数据处理,我们就可以加分布式锁,锁定此数据,处理完后释放锁。获取到锁之前必须要查看这个数据是否被处理过(给记录加一个状态标识 status, status 有一个状态时处理中,也会有一个短暂的问题)。

4.4 唯一约束
  • 数据库唯一约束

插入数据,应该按照唯一索引插入,比如订单号,相同的订单就不可能有两条相同订单号的插入记录。我们在数据库层面防止重复。
这个机制是利用了数据库的唯一索引约束的特性,解决了在 insert 场景时的幂等问题,但主键的要求不是自增的主键,这样就需要业务生成一个全局唯一的主键。
如果是分库分表的情况下,路由规则要保证相同请求下,落地在同一个数据库和同一个表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不想关。

  • redis set 防重

很多数据需要处理,只能被处理一次,比如我们可以计算数据的 MD5 将其放入到 redis 的 set 中,每次处理数据,先校验这个 MD5 是否已经存在,存在就不处理了。

  • 防重表

使用订单号 orderNo 做为去重表的唯一索引,把唯一索引插入到去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。这里需要注意的是,去重表和业务表应该放到同一个数据库中,这样就保证了在同一个事务中,即使业务操作失败了,也会把去重表的数据回滚。这样就很好的保证了数据一致性。

4.5 全局请求唯一ID

调用接口时,生成一个唯一ID, redis 将数据保存到集合中(去重),存在即处理过。可以使用 nginx 设置每一个请求的唯一ID。

proxy_set_header X-Request-Id $request_id

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值