概念(百度百科)
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。
这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现
可能出现违反接口幂等性的场景
前端重复提交
- 页面重复刷新
- 使用浏览器后退按钮重复之前的操作,导致重复提交表单
- 用户无意触发多次下单交易,或者因为没有响应而有意触发多次交易应用
- 使用浏览器历史记录重复提交表单
- 用户双击提交按钮,导致浏览器重复的HTTP请求
后端接口异常
- 使用了失效或超时重试机制(Nginx重试、RPC重试或业务层重试等)
- 定时任务重复执行
- 网络波动, 可能会引起重复请求
中间件异常
在使用消息中间件来处理消息队列,且手动 ack 确认消息被正常消费
如果消费者突然断开连接,那么已经执行了一半的消息会重新放回队列,当消息被其他消费者重新消费时,导致消息重复消费
最终导致结果异常,如数据库重复数据,数据库数据冲突,资源重复等
解决方案
前端部分
- 按钮,点击一次后 disabled 置灰,避免二次点击
- 使用PRG模式,POST-REDIRECT-GET
简单来说就是当用户提交连表单后,跳转到一个重定向的信息页面
这样就避免用户按F5刷新导致的重复提交,而且也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退导致同样重复提交的问题
后端
在 session 中存放特殊标志
在服务端,生成一个唯一的标识符,将它存入session,同时前端获取这个标识符的值将它写入表单的隐藏中,用于用户输入信息后点击一起提交
在服务器端,获取表单中隐藏字段的值,与session中的唯一标识符比较,相等说明是首次提交,就处理本次请求
然后将session中的唯一标识符移除,不相等则表示是重复提交,不再做处理
缓存层
缓存中的去重键
本地内存
业务流水号 ccid,即全局唯一id
在 localcache 中缓存唯一序列号,后端通过序列号判断请求是否重复,在并发时只能处理一个请求
其他相同并发请求阻止业务处理或者挂起等待前面请求执行完成后再执
数据层
- 使用唯一索引防止新增脏数据
当数据重复时,插入数据库会抛出异常,保证不会出现脏数据
- 乐观锁
在字段中加一列表示版本号,每次更新的时候带上版本号,不同的线程更新时拿行写锁+版本号,保证幂等性
update table set version = version + 1 where id = #{id} and version = #{version}
分布式
分布式锁
redis或者zookeeper,分布式锁由第三方系统提供
防重表
比如使用订单号作为防重表的唯一索引
每一次请求都根据订单号向防重表中插入一条数据,插入成功可以处理后面的业务
当处理完业务逻辑之后删除防重表中的订单号数据,后续如果有重复请求,则会因为防重表唯一索引原因导致插入失败
直接返回操作失败,直到插入成功的请求返回结果,防重表就是加锁
缓冲队列
将请求都快速地接收下来后放入缓冲队列中,后续使用异步任务处理队列中的数据,过滤掉重复的请求
该解决方案优点是同步处理改成异步处理、高吞吐量,缺点则是不能及时地返回请求结果,需要轮询得到处理结果