接口幂等性问题
接口幂等性
订单防止重复提交 网络不好 用户点击多次
什么是幂等性
用户对于同一操作发起的一次请求或者多次请求的结果是一致的 不会因为多次点击产生副作用 比如说支付场景 用户购买了商品支付扣款成功 但是返回结果的时候网络异常 这时候钱 已经扣掉了 再次点击会进行二次扣款…这就没有保证接口的幂等性
那些情况需要防止
用户多次点击按钮
用户页面回退再次提交
微服务相互调用
由于网络问题导致请求失败 feign触发重试机制
其它业务情况
什么情况下需要幂等
以sql为例子 查询 删除 操作大部分是天然的幂等性 因为在第一次的时候已经删除成功了
叠加状态 每次执行结果都不一样则不是幂等性
幂等性解决方案
索引 订单号 唯一
举例:五种方案
令牌机制
ytmall商城项目采用这个方案
存在问题: 如果业务执行后才删令牌 令牌仍然有可能多次进入
如果先删令牌
保证获取令牌 对比 删除 必须是 原子性的
可以在redis 使用lua脚本完成这个原子操作操作
1 | if redis.call('get',KEY[1]) == ARGV[1] then return redis.call('del',KEYS[1] else return 0 end) |
商城支付保证幂等性
1 |
|
各种锁机制
- 数据库悲观锁
select * from xxxx where id =1 for update;
悲观锁使用时候一般伴随事务一起使用,数据锁定时间可能会很长,需要根据实际情况选用.另外要注意的是,id字段一定是主键或者唯一索引,不然可能造成锁表的结果,处理起来会非常麻烦.
- 数据库乐观锁
这种方法适合在更新的场景中
update t_goods set count = count -1 ,version = version +1 where good_id=2 and version = 1
根据version版本,也就是在操作数据库前先获取当前商品的version版本号,然后操作的时候带上版本号.
例如:第一次操作库存的时候,得到版本为1,调用库存服务版本变成2;但是返回给订单服务出现问题,订单服务又一次发起调用库存服务,当订单服务传入的version还是1,在执行上面的sql语句的时候,就不会执行,因为version已经改变为2,where 条件不成立.这样可以保证无论调用几次,只会真正处理一次.乐观锁主要使用与处理读多写少的问题;
- 业务层分布式锁
如果多个机器可能统一时间同时处理相同的数据,比如多台机器定时任务都拿到了相同数据处理,我们就可以加分布式锁,锁定此数据.获取到锁的必须先判断这个数据是否被处理过
各种唯一约束
- 数据库唯一约束
插入数据,应该按照唯一索引进行插入,比如订单号,相同的订单就不可能有两条记录插入.
我们在数据库层面防止重复,利用了数据库主键唯一约束的特性,解决了在insert场景时幂等性问题,但是主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键.
如果是在分库分表的场景下,路由规则要保证相同请求下,落地在同一个数据库和同一个表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关.
- redis set防重
很多数据需要处理,只能被处理一次,比如我们可以计算数据的md5放入redis的set,每次处理数据,先看这个md5是否已经存在,存在就不处理
防重表
使用订单号orderNo作为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中.这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等性.这里要注意的是,去重表和业务表应该在同一个库中,这样就保证了在同一个事务中,即使业务操作失败了,也会把去重表的数据回滚,这个很好的保证了数据一致性.
全局请求唯一ID
调用接口的时候,生成一个唯一id,redis将数据保存到集合中(去重),存在即处理过.可以使用nginx设置每一个请求的唯一id;
1 | proxy_set_header X-Request-Id $request_id |