01什么是接口幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生副作用;比如支付场景。
02哪些情况需要防止
- 用户多次点击按钮
- 用户页面回退再次提交
- 微服务相互调用,请求失败,feign触发重试机制
03什么情况下需要幂等
以sql为例,有些操作是天然幂等的。
- select * from table where id = ?,无论执行多少次都不会改变状态,是天然幂等;
- update table set col=1 where id = 2,无论执行成功多少次状态都是一致的,也是幂等操作;
- delete from user where id = 1,执行多次,结果一致,具备幂等性;
- insert into user(id, name) values(1, 'zhangsan'),如果id为主键,即使重复执行,也只会插入一条数据,具备幂等性;
- update table set col=col+1 where col=2,每次执行的结果都会变化,不是幂等;
- insert into user(id, name) values(1, 'zhangsan'),如果id不是主键,重复执行就会产生多条数据,不具备幂等性;
04幂等性解决方案
1、token机制
1、服务端提供了发送token的接口。在存在幂等问题的业务中,在业务执行前先去获取token,服务端会把token保存到redis中。
2、然后调用业务请求时,把token携带过去,一般放在请求头中。
3、服务端判断token是否存在redis,存在表示第一次请求,然后删除token,继续执行业务。
4、如果判断redis中不存在token,就表示是重复操作,直接返回给client,这样就保证了业务代码不被重复执行
危险性:
1、先删除token还是后删除token
(1)、先删除可能导致业务没有执行,重试还带上之前token,由于防重设置导致
(2)、后删除可能导致,业务处理成功,但是服务挂了,没有删除token,后面又被执行
2、token的获取、比较和删除必须是原子性的
redis中可以使用lua脚本保证原子性
if redis.call('get', KEYS[1]) == ARGV[1]
then return redis.call('del', KEYS[1])
else return 0
end
2、锁机制
1、数据库悲观锁
select * from table where id = 1 for update;
悲观锁使用时一般伴随着事物一起使用,数据锁定时间可能会很长,需要根据实际情况选用。
要注意的是,id字段一定是主键或者唯一索引,不然可能造成锁表的结果。
2、数据库乐观锁
这种方法适用于更新场景中。
update table set count = count+1,version = version + 1 where id = 1 and version = 1
根据version版本更新。乐观锁主要适用于处理读多写少的问题
3、业务层分布式锁
在获取到锁的时候先判断数据是否被处理过。
3、唯一约束
1、数据库唯一约束
插入数据,按照唯一索引进行插入,比如订单号,相同订单不会有两条记录插入。
这个机制利用了数据库的主键唯一约束的特性,解决了在insert场景下幂等问题,但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键。
如果是分库分表场景下,路由规则要保证相同请求下,落到同一个数据库同一张表中,要不然主键约束就不起效果了。
2、redis set防重
很多数据需要处理,只能被处理一次,可以计算数据的MD5将其放入redis的set,每次处理数据,先看这个MD5是否已经存在,存在就不处理。
4、防重表
使用订单号做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中,这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。要注意的是,去重表和业务表应该在同一个库中,这样就可以保证同一个事务。
5、全局请求唯一id
调用接口时,生成一个唯一id,redis将数据保存到集合中,存在即处理过。可以使用nginx设置每一个请求的唯一id:proxy_set_header X-Request-Id $request_id;