什么是幂等
在计算机中编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
为什么需要实现幂等性
在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可以就会出现问题,如:
- 前端重复提交表单: 在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
- 用户恶意进行刷单: 例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
- 接口超时重复提交: 很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
- 消息进行重复消费: 当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。
使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题。
引入幂等性后对系统的影响
幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:
- 把并行执行的功能改为串行执行,降低了执行效率。
- 增加了额外控制幂等的业务逻辑,复杂化了业务功能;
所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。
数据库操作和幂等性关系
1. select查询天然幂等
2. delete删除也是幂等,删除同一个多次效果一样
3. update直接更新某个值的,幂等
4. update更新累加操作的,非幂等
5. insert非幂等操作,每次新增一条
产生原因
由于重复点击或者网络重发:
1. 点击提交按钮两次;
2. 点击刷新按钮;
3. 使用浏览器后退按钮重复之前的操作,导致重复提交表单;
4. 使用浏览器历史记录重复提交表单;
5. 浏览器重复的HTTP请;
6. nginx重发等情况;
7. 分布式RPC的try重发等;
解决方案
唯一序列号校验解决接口幂等
方案描述:
所谓请求序列号,其实就是每次向服务端请求时候附带一个短时间内唯一不重复的序列号,该序列号可以是一个有序 ID,也可以是一个订单号,一般由下游生成,在调用上游服务端接口时附加该序列号和用于认证的 ID。
当上游服务器收到请求信息后拿取该 序列号 和下游 认证ID 进行组合,形成用于操作 Redis 的 Key,然后到 Redis 中查询是否存在对应的 Key 的键值对,根据其结果:
- 如果存在,就说明已经对该下游的该序列号的请求进行了业务处理,这时可以直接响应重复请求的错误信息。
- 如果不存在,就以该 Key 作为 Redis 的键,以下游关键信息作为存储的值(例如下游商传递的一些业务逻辑信息),将该键值对存储到 Redis 中 ,然后再正常执行对应的业务逻辑即可。
适用操作:
- 插入操作
- 更新操作
- 删除操作
使用限制:
- 要求第三方传递唯一序列号;
- 需要使用第三方组件 Redis 进行数据效验;
主要流程:
主要步骤:
- ① 下游服务生成分布式 ID 作为序列号,然后执行请求调用上游接口,并附带“唯一序列号”与请求的“认证凭据ID”。
- ② 上游服务进行安全效验,检测下游传递的参数中是否存在“序列号”和“凭据ID”。
- ③ 上游服务到 Redis 中检测是否存在对应的“序列号”与“认证ID”组成的 Key,如果存在就抛出重复执行的异常信息,然后响应下游对应的错误信息。如果不存在就以该“序列号”和“认证ID”组合作为 Key,以下游关键信息作为 Value,进而存储到 Redis 中,然后正常执行接来来的业务逻辑。
上面步骤中插入数据到 Redis 一定要设置过期时间。
这样能保证在这个时间范围内,如果重复调用接口,则能够进行判断识别。
如果不设置过期时间,很可能导致数据无限量的存入 Redis,致使 Redis 不能正常工作。
token 机制
防止页面重复提交。业务要求:页面的数据只能被点击提交一次;发生原因:由于重复点击或者网络重发,或者 nginx 重发等情况会导致数据被重复提交;解决办法:集群环境采用 token 加 redis(redis 单线程的,处理需要排队);单 JVM 环境:采用 token 加 redis 或 token 加 jvm 内存。处理流程:1. 数据提交前要向服务的申请 token,token 放到 redis 或 jvm 内存,token 有效时间;2. 提交后后台校验 token,同时删除 token,生成新的 token 返回。token 特点:要申请,一次有效性,可以限流。注意:redis 要用删除操作来判断 token,删除成功代表 token 校验通过,如果用 select+delete 来校验 token,存在并发问题,不建议使用