幂等

82 阅读8分钟

背景

2第一次听说幂等这个词是有次和服务端讨论技术方案,之后也没太在意,以为幂等只是存在服务端的一

3个概念?但是近期频繁在客户端技术文章中看到幂等这个词,我才意识到没那么简单。于是便开启了探

4索之旅,在网上不断的搜索和理解,最后汇总形成了此文。通过本文你将了解到到:

51.幂等是什么;

62.什么场景需要幂等;

73.怎么实现幂等;

84.幂等的缺点。

9文中内容如有不妥之处,欢迎指正,谢谢人。

10定义

11幂等不是某领域内的专属名词,各领域都可以使用,就像安全这个词:

121.因为有ssl所以https内容传输是安全的;

132.因为有加密所以飞书密聊是安全的;

143.因为用了锁所以数据在多线程环境访问是安全的;

154....下面我们就来看看数学和编程领域中对幂等的解释,其他领域的大家可以自行搜索。

16数学

17f(f(x))f(x)

18数学是一门超级严谨的科学,在数学里,只对幂等进行了如下两种定义。

19幂等元素

20在某运算下,幂等元素是指被自己重复运算的结果等于自身的元素,比如

211.在乘法下,1和0是幂等元素,11=1,00=0;

222.在加法下,0是幂等元素,0+0=0;

233....

24幂等运算

25如果某运算是幂等的,那其作用在任一元素N次后会和其作用一次的结果相同,比如

261.绝对值运算是幂等的,|-1|=||-1|=|...|-1-1[|..]=1;

272.高斯运算是幂等的,[2.3]=[[2.3]]=[...[2.3]...]=2;

283.并集运算是幂等的,[1,2]U[3,4]=[1,2]U[3,4]U...[3,4]=[1,2,3,4];

294....有此可见,数学领域中的幂等只用在了元素和运算上。

30编程

31在编程领域对幂等的定义很抽象:任意多次执行对资源本身所产生的影响与一次执行的影响相同

=======

编程

2在编程领域对幂等的定义很抽象:任意多次执行对资源本身所产生的影响均与一次执行的影响相同:

3服务端

4资源1

5request1

6幂等性处理

7客户端

8request2

9资源2

10影响相同

11request...

12资源...

13举些例子更好说明:

141.幂等的删除指定订单操作,执行一次和执行多次对数据库的影响是一样的,都只是把指定订单删除了;

152.满足HTTP规范的Get操作,对指定资源发起一次和多次Get请求应该返回相同的数据,如果在Get请求里做了影响返回数据的操作,那就是不符合HTTP规范的;

163.收藏视频操作,点击一次和点击多次只会在收藏列表新增一条视频数据3

174....有此可见,编程领域中的幂等可以用在方方面面,并没有太多的拘泥。

18何时需要

19前面说了很多幂等的定义,那么什么场景应该去做幂等呢?这里我就简单的举几个例子:

201.发消息,你要给对方发消息,点击发送按钮,结果因为网络问题触发了客户端重发机制,实际发出去了4条,你是不是会吓一跳?

212.发红包,心情高兴给大家伙发一个红包,因为网络问题支付失败,让我重试一次,结果实际扣款了两次,你会不会气疯?

223.迫于服务端数据处理的压力,用户在页面中瞬间提交多次数据,服务端应该只处理一次数据;

234....有此可见,在一些关键业务场景都必须得实现幂等,这些场景大家不用去记,根据我们的经验,在做需求时也能知道哪些场景必须实现幂等,办法就是想一想多次执行会不会有什么问题。

24实现手段

25对外承诺了幂等当然就得有手段去实现,下面我们就针对上面列出的几个地景进行分析。

======= 发消息-唯一索引

2数据库

3服务端

4客户端

5消息Aid=xcid=y

6成功插入

7消息A cid=y

8幂等处理

9跳过

10消息Acid=y

11cid去重

12跳过一

13消息Acid=y...

14消息最终是存放在服务端的,客户端一般都会有重试机制,难免会有相同的数据发发送到服务端,所以服务端是肯定要做处理的,可以给消息映射一个唯一的id,可以用数据库的自增字段消息配合id一起插入数据库,后续客户端再发送相同的数据时,就可以跳过插入了。这里还有一个问题:服务端怎么判断数据是相同的数据?所以客户端也是要做处理的,给消息映射一个本地的cid:did+time,这样服务端可以根据cid进行去重。

15发红包-幂等状态机

16点击支付

17跳转支付

18开始发送

19输入金额

20发送完成

21完成付款

22输入密码

23发红包有很多阶段,比如:

24因为网络问题,以及客户端的重试机制,肯定会有相同的阶段被告知服务端很多次,比如在跳转支付阶段,用户输入了支付密码完成付款,但是没有收到服务端的完成响应,这时候用户再次跳转支付,服务端应该直接告知发送完成。

25客户端

26服务端

27生成id

28状态机

29带id请求

30跳转支付

31支付成功

32带id请求

33完成付款

34付款成功

35id:

36幕等处理

37输入密码

38断网了...

39查询/更新id状态

40付款成功

41完成付款

42带id请求

43跳转支付

44无需支付,返回付款成功

45这个场景我们维持一个幂等状态机(状态机本身也得是幂等的,保证状态只会往后移,多次插入相同状态影响一致)来做是很合适的。用户进入发红包界面时生成一个id,然后向服务端告知阶段时带上这个id,服务端存储该id以及到了哪个阶段,阶段的变化只会往后走,这样就可以做到幂等发红包了,因为用户完成了付款,服务端收到请求后数据库中记录该id已经到了完成付款阶段,后续用户再跳转支付时,服务端判断跳转支付比完成付款阶段完,则直接告知完成付款。

=====

提交数据-token机制

2服务端

3Redis

42.生成token

51.请求token

6幂等处理

7客户端

83.返回token

9无效请求

105.业务处理一

11判断token是否存在

124.携带token请求

13处理业务,删除token

14服务端提供获取token接口,在页面点击提交时客户端想服务端获取token,后续请求把token带上,服务端第一次处理请求时发现token在表中则进行处理,然后把token删除,后续请求发现token已被删除则直接返回,这样就保证了服务端的业务代码只执行了一次。

15上诉场景对应的幂等手段只是业界普遍认可和使用的手段而已,你也可以用其他方式实现幂等,如果要看更多幂等实现手段,可以自行搜索。

16缺点

17幂等增加了服务提供者的逻辑和成本,是否有必要,需要根据具体场景具体分析,因此除了业务上的特殊要求外,尽量不提供幂等的接口。当然如果是为了保证业务的正确性,我们不得不实现幂等,比如发红包场景。

18总结

19相信大家理解幂等后,就可以用这个词更加方便的和大家讨论技术方案了,同时自己实现一些功能时也可以用幂等的概念去和小伙伴们解释清楚,比如我自己维护的LarkFeatureGating里就有幂等接口

20class LarkFeatureGating {

21///获取不变的fg值,幂等:传相同key保证获取结果相同func getXxx(for key:String)->Bool{...}

22}