接口幂等性
概念:
提交一次和提交一百次结果都是一样的,如下订单提交,用户点了1次或者100次,数据库只有一份订单。类似
什么情况下需要用到幂等性:
页面上表单提交,可能用户点击多次;用户页面回退再次提交,比如订单提交成功后的确认页回退之后再次提交订单;A系统和B系统相互调用,调用失败后重试多次,如A调B减B库存返回失败,可能出现B已经库存扣减成功了,但是在返回成功的时候网络问题没有给A返回成功,此时A再重试减B库存,在没有幂等的情况下,B的库存会被多次扣减。也可能A调B就失败,即B减库存失败,则此时需要重试调B。即无论点击多少次或者重试多少次都要保证接口幂等性
数据库的哪些操作天然幂等或不幂等:
如SELECT查询操作,想查某个数据,假如这个数据就在那固定不变,不管select查多少次结果都一样,UPDATE更新某条数据的某个字段的值为aaa,因为只是更新该数据某个字段的值为aaa则不管操作更新多少次,这条数据的最后更新结果都一样,DELETE根据ID删除某条数据,因为是根据ID删除所以不管操作删除多少次,最后的结果都是把该id数据删了,它也不会删除别的数据,INSERT根据ID主键插入数据,因为主键唯一,所以不管操作多少次,最多只能插入一条该ID数据,因为其他次操作数据会因为主键冲突插入失败。以上都为天然幂等性。
UPDATE更新某条数据的某个字段的值+1即UPDATE的叠加操作,每update一次该字段的值都不断加1,insert不带主键的插入,则相同数据每多插入一次则数据会增多一条,比如插入订单数据,假如订单号不唯一,则相同订单号数据每插入一次数据都会多一条,以上都为不幂等性操作。(建议在设计数据库时先把订单号设为唯一性约束,则插入时就不会有幂等性问题了)
幂等解决方案:
token机制-验证码:
如12306买座位,在选定座位点提交时会让输入一个验证码,只有本次操作带的验证码是对的,这一次提交才会完成,如果验证码不对或者拿着第一次操作的验证码去让第2次操作使用则提交失败。用户到了提交页面,在提交页面上前端先调用后端让后端返回一个带时效的token令牌(尽量多设置几分钟,要给用户提交留出充足的时间),后端会把令牌放到redis里(因为一般架构是集群微服务架构),然后在用户点击提交的时候前端在参数里把该令牌带上,后端在分布式锁里接收到参数之后会先校验令牌(为啥用分布式锁,是因为用户连续点击多次,假如不用分布式锁,则从redis里取token,校验redis中的token和前端页面传来的token是否一致,删除redis中的token,三个操作可能不具有原子性,则会导致幂等失效,所以一般用redis分布式锁或者luna脚本来保证原子性),一旦校验完毕后端会立马把令牌删除(注意是先删除令牌,因为后删可能后端执行太慢,导致订单又多创建了一个),然后去走创建订单流程。假如用户停在改提交页面多次点击提交,第一次提交之后后端已经把令牌删了,所以用户之后多次点提交的时候令牌会验证不通过。假如用户退出该提交页面再回到提交页面去点提交,那就是新的一笔订单了,在用户回到提交页面的那一刻前端会调用后端再去生成新令牌。。。。。。,令牌机制可以是验证码,让用户在提交时输入后端给用户返回的验证码,在提交订单时带上验证码让后端去做校验
数据库各种锁机制:
数据库层面的悲观锁如select where id = xxx for update,在主键或唯一性索引查询时加锁,锁定该条数据,查询时不让其他操作更改这条数据,注意不要锁表
数据库乐观锁即用版本号version,在更新或插入时携带版本号
业务层分布式锁:
就是假如说库存模块加了一个定时任务对失败数据进行重试,因为实际线上库存服务都是集群模式,假如库存服务部署了ABC三台机器,在扣减库存时失败了,在定时任务重试扣减库存时三台机器会同时对数据库的一条数据进行重试,这个时候就需要分布式锁,因为都重试成功则库存又被多扣了2次,此时也要求幂等性吧。。。。
各种唯一约束:
数据库的唯一约束就是创建某个字段唯一性索引
redis set防重:
比如上传一个文件,上传过了就给这个文件生成一个md5值放到redis的set类型里,则下次再上传该文件时因为相同文件生成的md5值是惟一的,所以再上传该文件时先判断redis中的set是否能set成功,不成功证明该文件已经被上传过了,则就可以避免多次上传文件
防重表:比如订单解锁库存,假如订单解锁库存过了就在订单解锁库存防重表里插入一条记录(应该是先插入记录再解锁库存),下次该订单还要解锁库存时先去防重表里插入一条该订单号解锁库存的记录,插入成功才能解锁库存,因为先前已经插入过了则插入防重表失败,则订单解锁库存失败
全局唯一请求id:
给每次请求建立一个全局唯一id,比如feign在调用接口时生成一个唯一id,在调用时把该id保存到redis的set集合中,假如feign拿老请求再去重试,则接收到feign请求参数后会去redis首先查这个唯一id是否存在,存在证明该请求先前已经成功,则直接返回成功不再处理本次请求(应该是feign请求在携带唯一id进行跨服务调用时,另外一个微服务在接收到请求后会先把该唯一id存到redis集合里,存的时候应该也需要luna脚本把,如果调用成功就正好存上了该唯一id,如果调用失败则最后应该会删除redis中的这个唯一id来让feign重试)
nginx设置全局唯一id:
nginx给每个请求设置唯一id来给请求做链路追踪,比如该请求可能经过了A系统、B系统、C系统看日志的时候做个链路追踪【nginx不能解决页面上的接口幂等性】
..........................