幂等 Idempotent
目录
幂等是一个数学与计算机科学概念。
- 在数学里,幂等有两种主要的定义。
- 在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1。
- 某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的。幂等用函数表达式就是:f(x) = f(f(x))。
- 一元运算的定义是二元运算定义的特例
- 计算机科学中,幂等表示一次和多次请求某一个资源应该具有同样的副作用,或者说,多次请求所产生的影响与一次请求执行的影响效果相同。
HTTP Idempotent Methods
一个HTTP方法是幂等的,指的是同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。在正确实现的条件下, GET , HEAD , PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的 safe 方法也都是幂等的。
幂等性只与后端服务器的实际状态有关,而每一次请求接收到的状态码不一定相同。例如,第一次调用 DELETE 方法有可能返回 200 ,但是后续的请求可能会返回 404 。 DELETE 的言外之意是,开发者不应该使用 DELETE 法实现具有删除最后条目功能的 RESTful API。
需要注意的是,服务器不一定会确保请求方法的幂等性,有些应用可能会错误地打破幂等性约束。
- 常见的幂等方法: GET , HEAD , PUT , DELETE , OPTIONS
- 常见的非幂等方法: POST
- 安全方法: GET, HEAD, OPTIONS
- 非安全方法: PUT, DELETE, POST
为什么需要幂等?
当前互联网的系统几乎都是解耦隔离后,会存在各个不同系统的相互远程调用。调用远程服务会有三个状态:成功,失败,或者超时。前两者都是明确的状态,而超时则是未知状态。我们转账超时的时候,如果下游转账系统做好幂等控制,我们发起重试,那即可以保证转账正常进行,又可以保证不会多转一笔。 其实除了转账这个例子,日常开发中,还有很多很多例子需要考虑幂等。比如:
- MQ(消息中间件)消费者读取消息时,有可能会读取到重复消息。(重复消费)
- 比如提交form表单时,如果快速点击提交按钮,可能产生了两条一样的数据(前端重复提交)
如何设计幂等
幂等性是指任意多次请求的执行结果和一次请求的执行结果所产生的影响相同。说的直白一点就是查询操作无论查询多少次都不会影响数据本身,因此查询操作本身就是幂等的。但是新增操作,每执行一次数据库就会发生变化,所以它是非幂等的。
全局的唯一性ID
不管是你哪个方案去设计幂等,都需要一个全局唯一的ID
一个Snowflake ID有64位。
- 第1位:Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
- 接下来前41位是时间戳,表示了自选定的时期以来的毫秒数。
- 接下来的10位代表计算机ID,防止冲突。
- 其余12位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID。
当然,全局唯一性的ID,还可以使用百度的Uidgenerator,或者美团的Leaf。
实现幂等
- 查询与删除操作是天然幂等
- 唯一索引,防止新增脏数据
- token 机制,存入 redis,同时设置过期时长,调用接口的时候带入token, 防止页面重复提交
- 悲观锁 select … for update
- 乐观锁(通过版本号/时间戳实现, 通过条件限制where avai_amount-#subAmount# >= 0)
- 分布式锁
- 状态机幂等(如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。)
- select + insert(并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行)
- redis 存储订单号,如果已存在的话说明已经处理过