接口幂等性
1. 什么是接口幂等性
幂等源于数学,表达的是N次变换与1次变换的结果相同。如果一个方法调用一次和调用多次产生的效果是相同的。幂等函数或者幂等方法,是指可以使用相同参数执行,并能获得相同结果的函数,这些函数不会影响系统的状态,也不用担心重复执行会对系统造成改变。
2. HTTP维度
GET方法
HTTP GET 方法用于获取资源,不应有副作用,所以是幂等的。Select可以说是天生的幂等。
这里指的是N次和1次具有相同的副作用,而不是返回相同的数据。
DELETE方法
HTTP DELETE 方法用于删除资源,有副作用,但是它应该满足幂等性,调用一次和调用N次的副作用是相同的,删除一个文章不会造成其他数据的误删。
POST方法
HTTP POST 所对应的URI尾资源的接收者。发表一篇文章,两次相同的POST请求会在服务器端创建两份资源,所以POST方法不具备幂等性。
PUT方法
HTTP PUT 所对应的URI是创建或者更新资源。创建或者更新一个ID唯一的文章,对同一个URI进行多次PUT和一次PUT是相同的,因此PUT方法具有幂等性。
幂等性讨论:
- RESTful风格:它把HTTP当成应用程协议,遵守HTTP协议的各种规定。
- 另一种是在HTTP协议上封装RPC,没有完全把HTTP当成应用层协议,而是把HTTP协议作为传输层协议,然后在HTTP之上建立自己的应用层协议。
3. 应用维度
第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用,这里的副作用指不会对结果产生破坏或者产生不可预料的结果。
产生幂等性场景
- 网络波动,可能引起重复请求
- 用户重复操作,用户在使用产品是可能会五一的出发多次下单多次交易,甚至没有响应而有意触发多笔交易。
- 应用使用了失败或者超时机制(Nginx重试、RPC重试、业务层重试)。
- 第三方平台的接口(支付成功问题接口)。
- 中间件、应用服务根据自身特性,也可能进行重试。
- 双击提交按钮
- 页面重复刷新
- 多核浏览器重复操作,导致重复提交表单
- 浏览器重复的HTTP请求
- 定时任务多次执行
幂等性在哪一层实现
分布式微服务在哪一层解决幂等性问题?
- Nignx、网关不适合做幂等性,因为不能在这写业务逻辑代码。
- Service层,不需要做幂等性,最终对数据更改的是DAO层,所以Service一般不做幂等性。
- DAO层需要做幂等性分析,因为直接对接数据,可能会产生不可预测的副作用。
数据访问层的幂等性
写请求、读请求?
读请求不会产生副作用。不改变数据的操作不需要做幂等。
写请求因为修改数据,需要做幂等。
insert操作
对于Insert操作,分为两种情况:
自增主键
多次Insert后会产生数据相同,但是主键不相同的数据。
业务主键
第二次Insert时,会因为主键冲突而报错。
delete操作
对于Delete操作,分为两种情况:
相对值删除
比如删除 top 数据,多次操作会导致幂等问题。
绝对值删除
通过Id删除,只会删除一个数据。
update操作
相对值更新
update product_info set prince = price + 99 where id = 1234 ;
无法保证幂等性
绝对值更新
update product_info set prince = 99 where id = 1234 ;
可以保证幂等性
所以对数据写的时候考虑幂等性。幂等性还分为广义的幂等性和狭义的幂等性,单库属于狭义的幂等性,而如今分布式系统中,需要分布式事务保证广义的幂等性。
- insert要求唯一业务主键
- delete中相对删除不具有幂等性
4. 保证幂等性的方法
4.1. 前端幂等性实现(不可靠)
4.1.1. 按钮置灰
按钮只可操作一次,一般是提交完成后按钮置灰或者loading状态,这里使用一些js组件实现,消除用户因为重复点击而产生的副作用,比如添加操作,由于点击两次而产生的两条记录。
缺点:用户可能会绕过js验证,所以前端实现的幂等性只能防止小白。
4.1.2. Token机制
产品上允许重复提交,但要保证重复提交不产生副作用,进入页面时申请一个Token,后面的所有请求都带上这个Token,根据Token来避免重复请求。
4.1.3. Post / Redirect / Get 模式
在提交后执行页面重定向,就是所谓的Post-Redirect-Get模式。当用户提交表单时,重定向到一个成功界面,防止用户刷新导致的重定向提交,同时也不会出现浏览器表单重复提交的警告,也能消除浏览器前进和后退导致的同样出伏提交问题。
4.1.4. Session中存放特殊标志
在服务器端,生成一个唯一标识符,存入Session,同时将它写入表单的隐藏中,然后提交表单,服务端会判断session中的标识符和表单中的标识符是否一致,如果一致,则表示首次提交,并将标识符移除,否则是重复提交。
4.2. 后端幂等性实现
使用唯一索引防止幂等性问题
像上文中说过的insert业务主键,可以有效的保证幂等性。
4.2.1. Token + Redis 的幂等方案
申请Token+业务操作两个阶段,整个流程如下图所示,但是业务操作需要加分布式锁来处理。
4.2.2. 状态机幂等
对于更新操作,设定更新状态操作,结合CAS思想,假设订单状态有待支付、支付中、支付成功、支付失败、订单超时等,在SQL语句where中加上status = 期望的值然后进行修改,这样也只会修改一次。
4.2.3. 乐观锁实现
更新已有数据,进行加锁更新,设计表结构时使用乐观锁,通过version来做乐观锁,即可保证效率,又能保证幂等性。乐观锁的version版本在更新业务时是递增的。
4.2.4. 防重表实现幂等性
防重表类似于锁的机制,保证当前处理订单只有一个线程。但是对于处理完成后,此订单的第二次操作同样会带来副作用,这里就需要配合其他机制来保证了,比如状态机。
4.2.5. Select + Insert
此方案是解决单机的幂等性问题的,先查询在判断是否插入,但是并发的时候需要加锁处理。
4.2.6. 分布式锁
进入方法时,先去获取锁,如果获取锁了,进行业务操作,如果没获取,等待锁的释放。同时分布式锁还有很多的细节,这里先不说了。
常用解决分布式锁的工具有Redis、zookeeper。思路类似于防重表。
4.2.7. 缓冲队列
按照消息的顺序来保证幂等性,同步改为异步,高吞吐量,但是不能同步返回结果。