幂等性概念与解决方案

2023-10-31

幂等性概念

幂等性原本是数学上的概念用在编程领域,则意为对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的

幂等性原本是数学上的概念,,即使公式:f(x)=f(f(x)) 能够成立的数学性质。编程领域,则意为对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的

用通俗的话讲:就是针对一个操作,不管做多少次,产生效果或返回的结果都是一样的。

如果没有实现接口幂等性会有很严重的后果: 支付接口,重复支付会导致多次扣钱 订单接口,同一个订单可能会多次创建。

重复消费例子

  1. 前端重复提交:提交订单,用户快速重复点击多次,造成后端生成多个内容重复的订单。
  2. 接口超时重试:对于给第三方调用的接口,为了防止网络抖动或其他原因造成请求丢失,这样的接口一般都会设计成超时重试多次。
  3. 消息重复消费:MQ消息中间件,消息重复消费。

不是所有的操作都需要幂等

这个很好理解,比如用户多次购买同一个产品,这种情况肯定是允许的,如果做了幂等,用户就只能购买一次,这明显是不合理的。

有些操作天生就是幂等的,不需要特殊处理

比如把数据清零,这种操作不论执行多少次,结果都是一样的。(与之相反的是累加或者累减,这种操作每次执行都会改变最终结果)

HTTP 协议与幂等性

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

这里只简单介绍详细的可以看:https://juejin.cn/post/6844903813799739399

查询操作 (Get)

Get 方法用于获取资源,不应当对系统资源进行改变,所以是幂等的。**注意这里的幂等提现在对系统资源的改变,而不是返回数据的结果,即使返回结果不相同但是该操作本身没有副作用,所以幂等。比如:GET http://www.bank.com/account/123456,不会改变资源的状态,不论调用一次还是N次都没有副作用。

删除操作 (Delete)

Delete 方法用于删除资源,虽然改变了系统资源,但是第一次和第N次删除操作对系统的作用是相同的,所以是幂等的。比如:DELETE [http://www.forum.com/article/1234,调用一次和N次对系统产生的副作用是相同的,即删掉id为1234的帖子;因此,调用者可以多次调用或刷新页面而不必担心引起错误。

修改操作(PUT 与 POST)

比较容易混淆的是HTTP POST和PUT。POST和PUT的区别容易被简单地误认为“POST表示创建资源,PUT表示更新资源”;而实际上,二者均可用于创建资源,更为本质的差别是在幂等性方面。在HTTP规范中对POST和PUT是这样定义的:

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 … If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.

POST所对应的URI并非创建的资源本身,而是资源的接收者。比如:POST http://www.forum.com/articles的语义是在http://www.forum.com/articles下创建一篇帖子,HTTP响应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;所以,POST方法不具备幂等性。而PUT所对应的URI是要创建或更新的资源本身。比如:PUT http://www.forum/articles/1234的语义是创建或更新ID为1234的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有幂等性。

如何解决幂等性问题?

我们在网上搜索幂等性问题的解决方案,会有各种各样的解法,但是如何判断哪种解决方案对于自己的业务场景是最优解,这种情况下,就需要我们抓问题本质。

经过以上分析,我们得到了解决幂等性问题就是要控制对资源的写操作

我们从问题各个环节流程来分析解决:

在这里插入图片描述

1.前端源头,控制重复请求

控制动作触发源头,即前端做幂等性控制实现

主要解决方案:

  • 控制操作次数,例如:提交按钮仅可操作一次(提交动作后按钮置灰)
  • 及时重定向,例如:下单/支付成功后跳转到成功提示页面,这样消除了浏览器前进或后退造成的重复提交问题。

相对不太可靠,没有从根本上解决问题,仅算作辅助解决方案。

2.过滤重复动作,逻辑层面。这一步其实可以了

控制过滤重复动作,是指在动作流转过程中控制有效请求数量。

1)分布式锁

分布式锁的主要实现有两种,但原理都是相同的,这里那用Redis 和 Zookeeper举个例子

Redis
  1. 订单发起支付请求,支付系统会去 Redis 缓存中查询是否存在该订单号的 Key,如果不存在,则向 Redis 增加 Key 为订单号
  2. 查询订单支付状态,如果未支付,则进行支付流程,支付完成后删除该订单号的 key
Zookeeper
  1. 订单发起支付请求,支付系统会去 Zookeeper 中创建一个 node,如果创建失败,则表示订单已经被支付
  2. 如果创建成功,则进行支付流程,支付完成后删除 node

2)Token令牌机制

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

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

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

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

应用流程如下:

1)服务端提供了发送token的接口。执行业务前先去获取token,同时服务端会把token保存到redis中;

2)然后业务端发起业务请求时,把token一起携带过去;

3)服务器判断token是否存在redis中,存在即第一次请求,可继续执行业务,执行业务完成后将token从redis中删除;

4)如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码不被重复执行。

在这里插入图片描述

3)缓冲队列

把所有请求都快速地接下来,对接入缓冲管道。后续使用异步任务处理管道中的数据,过滤掉重复的请求数据。

优点:同步转异步,实现高吞吐。

缺点:不能及时返回处理结果,需要后续监听处理结果的异步返回数据。

在这里插入图片描述

3.解决重复写,数据库层面

实现幂等性常见的方式有:悲观锁(for update)、乐观锁、唯一约束。

1)悲观锁(Pessimistic Lock)

简单理解就是:假设每一次拿数据,都有认为会被修改,所以给数据库的行或表上锁。

当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。

select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。(注意for update要用在索引上,不然会锁表)

START TRANSACTION; # 开启事务
SELETE * FROM users WHERE id=1 FOR UPDATE;
UPDATE users SET name= 'xiaoming' WHERE id = 1;
COMMIT; # 提交事务

2)乐观锁(Optimistic Lock)

MVCC:多版本并发控制,乐观锁的一种实现,在数据更新时需要去比较持有数据的版本号,版本号不一致的操作无法成功。

不过,乐观锁存在失效的情况,就是常说的ABA问题,不过如果version版本一直是自增的就不会出现ABA的情况。

UPDATE users SET name='xiaoxiao', version=(version+1) WHERE id=1 AND version=version;

缺点:就是在操作业务前,需要先查询出当前的version版本

3)状态机

例如:支付状态流转流程:待支付->支付中->已支付

具有一定要的前置要求的,严格来讲,也属于乐观锁的一种。

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

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

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

4)唯一约束

常见的就是利用数据库唯一索引或者全局业务唯一标识(如:source+序列号等)。

这个机制是利用了数据库的主键唯一约束的特性,解决了在insert场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键。

全局ID生成方案

  • UUID:结合机器的网卡、当地时间、一个随记数来生成UUID;
  • 数据库自增ID:使用数据库的id自增策略,如 MySQL 的 auto_increment。
  • Redis实现:通过提供像 INCR 和 INCRBY 这样的自增原子命令,保证生成的 ID 肯定是唯一有序的。
  • 雪花算法-Snowflake:由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。

小结:按照应用上的最优收益,推荐排序为:乐观锁 > 唯一约束 > 悲观锁。

参考

R 和 INCRBY 这样的自增原子命令,保证生成的 ID 肯定是唯一有序的。

  • 雪花算法-Snowflake:由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。

小结:按照应用上的最优收益,推荐排序为:乐观锁 > 唯一约束 > 悲观锁。

参考

https://xie.infoq.cn/article/107fd263605e9d184b78bf093

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

幂等性概念与解决方案 的相关文章

  • 广西公需科目 当代科学技术前沿知识 考试答案

    用百度的识字api 可以识别广西公需科目 当代科学技术前沿知识 的文档 当代科学技术前沿知识读本 电子书 全文 pdf 这个可以在平台下载完了就识别出来就好搜答案了 自动考试视频 guagnxi视频 mp4 考试代码 for i in co
  • MySQL误删数据找回神器之binlog2sql

    一 使用前提 1 binlog format为ROW 且binlog row image为full或noblog 默认为full 2 必须开启MySQL Server 理由有如下两点 它是基于BINLOG DUMP协议来获取binlog内容
  • 笔试题目收集(2)

    笔试题目搜集系列推荐 1 笔试题目搜集1 2 笔试题目收集2 3 笔试题目搜集3 4 笔试题目搜集4 5 笔试题目搜集5 1 下列程序运行的结果 面试宝典P108 include
  • Unity核心10——导航寻路系统

    Unity 中的导航寻路系统是能够让我们在游戏世界当中 让角色能够从一个起点准确的到达另一个终点 并且能够自动避开两个点之间的障碍物选择最近最合理的路径进行前往 Unity 中的导航寻路系统的本质 就是在 A 星寻路算法的基础上进行了拓展和

随机推荐