【README】 这是一篇非常棒的, 讲解幂等性问题的post, 感谢原文作者;

转自: https://juejin.cn/post/6844903815552958477

幂等 (idempotence) 的概念

幂等的数学概念

幂等是源于一种数学概念。其主要有两个定义

如果在一元运算中,x 为某集合中的任意数,如果满足 f(x) = f(f(x)) ,那么该 f 运算具有幂等性,比如绝对值运算 abs(a) = abs(abs(a)) 就是幂等性函数。

如果在二元运算中,x 为某集合中的任意数,如果满足 f(x,x) = x,前提是 f 运算的两个参数均为 x,那么我们称 f 运算也有幂等性,比如求大值函数 max(x,x) = x 就是幂等性函数。

幂等性在开发中的概念

在数学中幂等的概念或许比较抽象,但是在开发中幂等性是极为重要的。简单来说,对于同一个系统,在同样条件下,一次请求和重复多次请求对资源的影响是一致的,就称该操作为幂等的。比如说如果有一个接口是幂等的,当传入相同条件时,其效果必须是相同的。

特别是对于现在分布式系统下的 RPC 或者 Restful 接口互相调用的情况下,很容易出现由于网络错误等等各种原因导致调用的时候出现异常而需要重试,这时候就必须保证接口的幂等性,否则重试的结果将与第一次调用的结果不同,如果有个接口的调用链 A->B->C->D->E,在 D->E 这一步发生异常重试后返回了错误的结果,A,B,C也会受到影响,这将会是灾难性的。

在生活中常见的一些要求幂等性的例子:

  1. 博客系统同一个用户对同一个文章点赞,即使这人单身30年手速疯狂按点赞,那么实际上也只能给这个文章 +1 赞
  2. 在微信支付的时候,一笔订单应当只能扣一次钱,那么无论是网络问题或者bug等而重新付款,都只应该扣一次钱

幂等性与并发安全

在查阅网络资料的时候,我看到许多文章把幂等性和并发安全的问题有些混淆了。幂等性是系统接口对外的一种承诺,而不是实现,承诺多次相同的操作的结果都会是一样的。而并发安全问题是当多个线程同时对同一个资源操作时,由于操作顺序等原因导致结果不正确。

这两个实际上是完全独立的两个问题,比如说同一笔订单即使你不停的提交支付,如果扣除了多次钱,就说明该操作不幂等。而有多笔订单同时进行支付,最后扣除金额不是这多笔金额的总和,那么说明该操作有并发安全问题。所以幂等性和并发安全是完全两个维度的问题,要分开讨论解决。

我在一些讨论幂等性的文章中看到中给出的解决方案为‘悲观锁’和‘乐观锁’,这两个方案可以很好的解决并发问题,但是却不应该是幂等性问题的解决方案,特别是悲观锁是用于防止多个线程同时修改一个资源的。倒是乐观锁的版本号机制可以勉强以 token 或者状态标识 作为版本号来实现幂等性(下文解释token状态标识),勉强说的过去。

所以说幂等性与并发安全是不同的,在本文就只讨论幂等性的问题,对于并发安全问题不做讨论

Http 协议与幂等性

如果把操作按照功能分类,那就是增删改查四种,在 http 协议中则表现为 Get、Post、Put、Delete 四种。

查询操作 (Get)

Get 方法用于获取资源,不应当对系统资源进行改变,所以是幂等的。注意这里的幂等提现在对系统资源的改变,而不是返回数据的结果,即使返回结果不相同但是该操作本身没有副作用,所以幂等。

删除操作 (Delete)

Delete 方法用于删除资源,虽然改变了系统资源,但是第一次和第N次删除操作对系统的作用是相同的,所以是幂等的。比如要删除一个 id 为 1234 的资源,可能第一次调用时会删除,而后面所有调用的时候由于系统中已经没有这个 id 的资源了,但是第一次操作和后面的操作对系统的作用是相同的,所以这也是幂等的,调用者可以多次调用这个接口不必担心错误。

修改操作 (Put)

修改操作有可能是幂等的也可能不幂等。如果修改的资源为固定的,比如说把账户中金额改为 1000 元,无论调用几次都是幂等的。假如资源不固定,比如账户中金额减少50元,调用一次和调用多次的结果肯定不一样,这时候就不幂等了。在修改操作中想要幂等在下文中讨论。

2019-08-13 修改

原文对Put协议定义有错误,Put操作必须为幂等的,即如果声明为Put协议时就相当于对外声明这个接口是幂等的。所以对于原文举例说账户中金额减少50元这种操作在Put协议中是不允许的,只能做类似于账户中金额改为 1000 元的操作

参考:restfulapi.net/idempotent-…

新增操作 (Post)

Post 新增操作天生就不是一个幂等操作,其在 http 协议的定义如下:

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.

(www.w3.org/Protocols/r…)

在其定义中表明了 Post 请求用于创建新的资源,这意味着每次调用都会在系统中产生新的资源,所以该操作注定不是幂等操作。这时候想要幂等就必须在业务中实现,方案在下文会讨论。

实现幂等性的方案

在上面提到的幂等性还是比较理论,下面结合一些常见的实际业务场景来讨论幂等性设计方案。

去重表

利用数据库的特性来实现幂等。通常是在表上构建一个唯一索引,那么只要某一个数据构建完毕,后面再次操作也无法成功写入。

常见的业务就是博客系统点赞功能,一个用户对一个博文点赞后,就把用户 id 与 博文 id 绑定,后续该用户点赞同一个博文就无法插入了。或是在金融系统中,给用户创建金融账户,一个用户肯定不能有多个账户,就在账户表中增加唯一索引来存储用户 id,这样即使重复操作用户也只能拥有一个账户。

状态标识

状态标识是很常见的幂等设计方式,主要思路就是通过状态标识的变更,保证业务中每个流程只会在对应的状态下执行,如果标识已经进入下一个状态,这时候来了上一个状态的操作就不允许变更状态,保证了业务的幂等性。

状态标识经常用在业务流程较长,修改数据较多的场景里。最经典的例子就是订单系统,假如一个订单要经历 创建订单 -> 订单支付\取消 -> 账户计算 -> 通知商户 这四个步骤。那么就有可能一笔订单支付完成后去账户里扣除对应的余额,消耗对应的优惠卷。但是由于网络等原因返回了错误信息,这时候就会重试再次去进行账户计算步骤造成数据错误。

所以为了保证整个订单流程的幂等性,可以在订单信息中增加一个状态标识,一旦完成了一个步骤就修改对应的状态标识。比如订单支付成功后,就把订单标识为修改为支付成功,现在再次调用订单支付或者取消接口,会先判断订单状态标识,如果是已经支付过或者取消订单,就不会再次支付了。

Token 机制

Token 机制应该是适用范围最广泛的一种幂等设计方案了,具体实现方式也很多样化。但是核心思想就是每次操作都生成一个唯一 Token 凭证,服务器通过这个唯一凭证保证同样的操作不会被执行两次。这个 Token 除了字面形式上的唯一字符串,也可以是多个标志的组合(比如上面提到的状态标志),甚至可以是时间段标识等等。

举个例子,在论坛中发布一个新帖子,这是一个典型的 Post 新增操作,要怎样防止用户多次点击提交导致产生多个同样的帖子呢。可以让用户提交的时候带一个唯一 Token,服务器只要判断该 Token 存在了就不允许提交,便能保证幂等性。

上面这个例子比较容易理解,但是业务比较简单。由于 Token 机制适用较广,所以其设计中要注意的要求也会根据业务不同而不同。

Token 在何时生成,怎么生成?这是该机制的核心,就拿上面论坛系统来说,如果你在用户提交帖子的时候才生成 Token,那用户每次点提交都会生成新的 Token 然后都能提交成功,就不是幂等的了。必须在用户提交内容之前,比如进入编辑页面的时候生成 Token,用户在提交的时候内容带着 Token 一起提交,对于同一个页面无论用户提交多少次,就至多能成功一次。所以 Token 生成的时机必须保证能够使该操作具多次执行都是相同的效果才行。使用 Token 机制就要求开发者对业务流程有较好的理解。

结语

幂等性是开发当中很常见也很重要的一个需求。尤其是金融、支付等行业对其要求更加严格,既要有好的性能也要有严格的幂等性。除了对其概念的掌握,理解自身业务需求更是实现幂等功能的要点,必须处理好每一个结点细节,一旦某个地方没有设计完善,最后的结果可能仍旧达不到要求。

作者:zzzzbw
链接:https://juejin.cn/post/6844903815552958477
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转:聊聊开发中幂等性问题(*)相关推荐

  1. 聊聊开发中经历的几种后端语言

    前言 谈这个话题,没有任何语言之争,只是个人开发的经历. 适合 开发语言的选择只是当时条件下适合每个项目或者每个团队乃至每个公司的最佳选择,并不是说XXX公司也用这个我们用这个肯定没问题. 当时还在某 ...

  2. 彼之蜜糖,吾之砒霜——聊聊软件开发中的最佳实践

    作者:sherrywasp https://www.cnblogs.com/sherrywasp/p/9436623.html "描述一个事物,唯有一个名词定义它的概念,唯有一个动词揭露它的 ...

  3. 汝之蜜糖,吾之砒霜— 聊聊软件开发中的最佳实践

    文章来源:https://www.cnblogs.com/sherrywasp/p/9436623.html 作者:sherrywasp "描述一个事物,唯有一个名词定义它的概念,唯有一个动 ...

  4. 开发中是如何保证接口幂等性的?

    文章目录 一.定义 二.业务场景 三.保证幂等性常用方法 方案1: insert前先select(基于mysql的分布式锁) 方案2:加悲观锁 select * from table where id ...

  5. 聊聊微信小程序开发中的appid、openid、unionid

    文章转自:https://blog.csdn.net/shenzhou_yh/article/details/104603653 文章目录 appid 定义 获取途径 用途 openid 定义 获取途 ...

  6. vue 中provide的用法_聊聊Vue中provide/inject的应用详解

    众所周知,在组件式开发中,最大的痛点就在于组件之间的通信.在 Vue 中,Vue 提供了各种各样的组件通信方式,从基础的 props/$emit 到用于兄弟组件通信的 EventBus,再到用于全局数 ...

  7. 如何转obj_Java 开发中如何正确的踩坑,看完这个你可以避免50%的错误

    为什么说一个好的员工能顶 100 个普通员工 我们的做法是,要用最好的人.我一直都认为研发本身是很有创造性的,如果人不放松,或不够聪明,都很难做得好.你要找到最好的人,一个好的工程师不是顶10个,是顶 ...

  8. axios 超时_聊聊 Vue 中 axios 的封装

    axios 是 Vue 官方推荐的一个 HTTP 库,用 axios 官方简介来介绍它,就是: Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中. 作为一 ...

  9. iPhone开发中的技巧整理(四)

    iphone开发笔记 退回输入键盘 - (BOOL) textFieldShouldReturn:(id)textField{ [textField  resignFirstResponder]; } ...

最新文章

  1. flash全屏输入模式
  2. 枚举类型的定义与使用
  3. 2015/6/1站立会议(补发)
  4. 使用三目运算嵌套方法 或 临时变量方法: 获取三个整数中最大值的数
  5. 设计和实时视图不一样_新宝骏“星际几何”设计理念,演绎不一样的跨界融合...
  6. Java数组概述和定义
  7. Linux中source是什么指令?
  8. git Gui从服务器拉去项目
  9. python面向对象(下)
  10. c语言输入y循环n结束,大佬们帮帮忙 帮我改改 怎样能在输入Y后 再次进行for循环...
  11. RandomAccess接口
  12. 右键tomcat 选择 add and remove,出现的对话框里面没有我们的项目解决方法
  13. 台湾19大IT业营收连衰 全球产业景气警报先兆
  14. excel宏破解方法
  15. 姓名投票c语言未给定候选人,C语言上机
  16. 传统数据与大数据安装服务器系统的对比
  17. 2021最后一个月了,你找到带回家的那个她了吗?送你999朵玫瑰。
  18. 基于双月数据集利用最小二乘法进行分类
  19. android 如何从 3.55mm耳机接口 读取数据,USB Type-C接口详细定义,自制Type-C转3.5mm耳机转接线...
  20. HADOOP与HDFS数据压缩格式

热门文章

  1. 湖南大学第十六届程序设计竞赛 B Yuki with emofunc and playf 同余最短路
  2. CodeCraft-20 (Div. 2) D. Nash Matrix 构造 + dfs
  3. 模板:min-max容斥离散随机变量的几何分布(洛谷P3175:[HAOI2015]按位或)
  4. YbtOJ-毒瘤染色【LCT】
  5. P3577-[POI2014]TUR-Tourism【状压dp】
  6. YbtOJ#893-带权的图【高斯消元,结论】
  7. POJ1006-Biorhythms【中国剩余定理】
  8. P1373-小a和uim之大逃离【dp】
  9. Ch3101-阶乘分解【数论,质因数分解】
  10. [XSY] 计数(DP,NTT,分治)