高并发下接口幂等性解决方案
①. 一个幂等操作指任意多次执行所产生的影响跟一次执行的影响相同.
②. 幂等函数(幂等方法):
a. 可以使用相同参数重复执行,并能获得相同结果的函数.
b. 这些函数不会影响数据状态,也不用担心重复执行会对数据造成改变.
c. 如:"getUsername()"、"setTrue()"就是一个幂等函数.
③. 总结:
幂等就是一个操作不论执行多少次,产生的效果和返回的结果都是一样的.
①. 前端重复提交选中的数据,后台只能产生一个反应结果.
②. 发起一笔付款请求,只能扣用户账户一次钱,当遇到网络重发或系统bug重发,也只能扣一次钱.
③. 发送短信、极光推送等消息,也只能发一次.
④. 创建业务订单,一次业务请求只能创建一个.
(1). mysql:
①. 查询操作:
a. 在数据不变的情况下,查询一次和多次的结果是一样的.
b. select是天然的幂等操作.
②. 删除操作:
a. 删除一次和多次都是把数据删除,会体现在返回结果不一样.
b. 删除的数据不存在,返回0.
c. 删除的数据多条,返回多条值.
③. 唯一索引:
a. 防止新增脏数据.
b. 如:支付宝每个用户只能有一个资金账户,将资金账户表的用户ID加唯一索引,可以防止给用户创建资金账户多个.
c. 唯一索引或唯一组合索引是用来防止新增数据存在脏数据.
④. 悲观锁:
a. 加锁获取数据:
select * from table_xxx where id='xxx' for update;
b. 注:id字段一定是主键或唯一索引,否则会锁表.
c. 悲观锁使用时,一般伴随事务一起使用,数据锁定时间可能会很长(根据实际情况选用).
⑤. 乐观锁:
a. 只在更新数据那一刻锁表,其它时间不锁表.
b. 相对于悲观锁,效率更高.
c. 实现方式可以通过version或其他状态条件:
1. 通过版本号实现:
update table_name set name=xx, version=version+1 where id=#id# and version=xxx
2. 通过条件限制实现:
update table_name set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0
要求:quality-#subQuality# >= ,这个情景适合不用版本号,只更新是做数据安全校验,适合库存模型,扣份额和回滚份额,性能更高;
d. 注:乐观锁的更新操作,最好加主键或唯一索引来更新,这样是行锁,否则更新时会锁表.
(2). redis:
①. token机制:
a. 防止页面重复提交.
b. 当客户端请求页面时,服务器会生成一个随机数Token保存到redis或session中,再将Token发给客户端(hidden表单).
c. 下次客户端提交请求时,Token会随着表单一起提交到服务器端.
d. 服务器端第一次验证相同通过后,会将Token值更新,若是用户重复提交,第二次的验证判断将会失败.
(3). 分布式锁:
如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供);