详解服务幂等性设计

标签: 服务 幂等 设计 | 发表时间:2022-12-04 14:41 | 作者:架构精进之路
出处:https://juejin.cn/tag/%E6%9E%B6%E6%9E%84

本文正在参加 「金石计划 . 瓜分6万现金大奖」

hello,大家好,我是张张,「架构精进之路」公号作者。

引子

在日常工作中的一些技术设计方案评审会上,经常会有人提到注意服务接口的幂等性问题,最近就有个组内同学就跑到跟前问我,幂等性到底是个啥?

在目前分布式/微服务化的今天,提供的服务能力丰富多样,基于 HTTP 协议的 Web API 是时下最为流行的一种分布式服务提供方式,对于服务的幂等性保障尤为重要。

我想了想,觉得有必要好好给他普及一下才行,否则以后做事还是一头雾水

今天计划就关于服务幂等性的一系列问题,在此将材料总结整理,顺便分享给大家~

1、何为幂等性?

幂等(idempotence),来源于数学中的一个概念,例如:幂等函数/幂等方法(指用相同的参数重复执行,并能获得相同结果的函数,这些函数不影响系统状态,也不用担心重复执行会对系统造成改变)。

简单理解即: 多次调用对系统的产生的影响是一样的,即对资源的作用是一样的

幂等性

幂等性强调的是外界通过接口对系统内部的影响, 只要一次或多次调用对某一个资源应该具有同样的副作用就行。

注意:这里指对资源造成的副作用必须是一样的,但是返回值允许不同!

2、幂等性主要场景有哪些?

根据上面对幂等性的定义我们得知: 产生重复数据或数据不一致,这个绝大部分是由于发生了重复请求

这里的重复请求是指同一个请求在一些情况下被多次发起。

导致这个情况会有哪些场景呢?

  • 微服务架构下,不同微服务间会有大量的基于 http,rpc 或者 mq 消息的网络通信,会有第三个情况【未知】,也就是超时。如果超时了,微服务框架会进行重试。

  • 用户交互的时候多次点击,无意地触发多笔交易。

  • MQ 消息中间件,消息重复消费

  • 第三方平台的接口(如:支付成功回调接口),因为异常也会导致多次异步回调

  • 其他中间件/应用服务根据自身的特性,也有可能进行重试。

3、幂等性的作用是什么?

幂等性主要保证 多次调用对资源的影响是一致的

在阐述作用之前,我们利用资源处理应用来说明一下:

HTTP 与数据库的 CRUD 操作对应:

  • PUT :CREATE
  • GET :READ
  • POST :UPDATE
  • DELETE :DELETE

(其实不光是数据库,任何数据如文件图表都是这样)

1) 查询

  SELECT * FROM users WHERE xxx;

不会对数据产生任何变化,天然具备幂等性。

2) 新增

  INSERT INTO users (user_id, name) VALUES (1, 'zhangsan');

case1:带有唯一索引(如:`user_id`),重复插入会导致后续执行失败,具有幂等性;

case2:不带有唯一索引,多次插入会导致数据重复,不具有幂等性。

3) 修改

case1:直接赋值,不管执行多少次 score 都一样,具备幂等性。

  UPDATE users SET score = 30 WHERE user_id = 1;

case2:计算赋值,每次操作 score 数据都不一样,不具备幂等性。

  UPDATE users SET score = score + 30 WHERE user_id = 1;

4) 删除

case1:绝对值删除,重复多次结果一样,具备幂等性。

  DELETE FROM users WHERE id = 1;

case2:相对值删除,重复多次结果不一致,不具备幂等性。

  DELETE top(3) FROM users;

总结: 通常只需要对写请求(新增 &更新)作幂等性保证

4、如何解决幂等性问题?

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

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

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

幂等性问题分析

4.1 控制重复请求

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

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

主要解决方案:

  • 控制操作次数,例如:提交按钮仅可操作一次(提交动作后按钮置灰)

  • 及时重定向,例如:下单/支付成功后跳转到成功提示页面,这样消除了浏览器前进或后退造成的重复提交问题。

4.2 过滤重复动作

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

1)分布式锁

利用 Redis 记录当前处理的业务标识,当检测到没有此任务在处理中,就进入处理,否则判为重复请求,可做过滤处理。

订单发起支付请求,支付系统会去 Redis 缓存中查询是否存在该订单号的 Key,如果不存在,则向 Redis 增加 Key 为订单号。查询订单支付已经支付,如果没有则进行支付,支付完成后删除该订单号的 Key。通过 Redis 做到了分布式锁,只有这次订单订单支付请求完成,下次请求才能进来。

分布式锁相比去重表,将放并发做到了缓存中,较为高效。思路相同,同一时间只能完成一次支付请求。

2)token 令牌

应用流程如下:

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

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

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

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

3)缓冲队列

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

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

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

4.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)

简单理解就是:就是很乐观,每次去拿数据的时候都认为别人不会修改。更新时如果 version 变化了,更新不会成功。

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

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

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

另外,还存在一种: 状态机控制

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

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

3)唯一约束

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

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

全局 ID 生成方案

  • UUID:结合机器的网卡、当地时间、一个随记数来生成 UUID;

  • 数据库自增 ID:使用数据库的 id 自增策略,如 MySQL 的 auto_increment。

  • Redis 实现:通过提供像 INCR 和 INCRBY 这样的自增原子命令,保证生成的 ID 肯定是唯一有序的。

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

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

5、总结

通常情况下,非幂等问题,主要是 由于重复且不确定的写操作造成的。

1、解决重复的主要思考点

从请求全流程,控制重复请求触发以及重复数据处理

  • 客户端 控制发起重复请求

  • 服务端 过滤重复无效请求

  • 底层数据处理 避免重复写操作

2、控制不确定性主要思考点

从服务设计思路上做改变,尽量避免不确定性:

  • 统计变量改为数据记录方式

  • 范围操作改为确定操作

后记

听了我以上大段的讲述后,他好像收获感满满的似的说:大概理解了...

但是出于自身责任感,我还得叮嘱他几句:

1)幂等性处理 虽然复杂了业务处理,也可能会降低接口的执行效率,但是为了保证系统数据的准确性,是非常有必要的;

2)遇到问题,善于发现并挖掘本质问题,这样解决起来才能高效且精准;

3)选择自身业务场景适合的解决方案,而不要去硬套一些现成的技术实现,无论是组合还是创新,要记住适合的才是最好的。

愿大家能够掌握问题分析以及解决的能力,都不要一上来就急于解决问题,可以多做些深入分析,了解本质问题之后再考虑解决办法进行解决。

·················· END ··················

希望今天的讲解对大家有所帮助,谢谢!

Thanks for reading!

作者:架构精进之路,十年研发风雨路,大厂架构师,CSDN 博客专家,专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。
**关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。
**

相关 [服务 幂等 设计] 推荐:

详解服务幂等性设计

- - 掘金 架构
本文正在参加 「金石计划. hello,大家好,我是张张,「架构精进之路」公号作者. 在日常工作中的一些技术设计方案评审会上,经常会有人提到注意服务接口的幂等性问题,最近就有个组内同学就跑到跟前问我,幂等性到底是个啥. 在目前分布式/微服务化的今天,提供的服务能力丰富多样,基于 HTTP 协议的 Web API 是时下最为流行的一种分布式服务提供方式,对于服务的幂等性保障尤为重要.

实战!聊聊幂等设计

- - 掘金 架构
大家好,我是捡田螺的小男孩. 公众号: 捡田螺的小男孩. 幂等是一个数学与计算机科学概念. 在数学中,幂等用函数表达式就是: f(x) = f(f(x)). 比如求绝对值的函数,就是幂等的, abs(x) = abs(abs(x)). 计算机科学中,幂等表示一次和多次请求某一个资源应该具有同样的副作用,或者说,多次请求所产生的影响与一次请求执行的影响效果相同.

再谈服务设计

- - 人月神话的BLOG
服务设计本身分两个方面的内容,一个是本身的服务契约和接口的设计,如soap webservice下的wsdl和xsd文件的设计. 一个更加重要的是关于服务的性能,安全,事务,同步/异步,大数据量,日志监控,SLA等方面的设计,前者是实现基本的服务功能,后者才是满足一个高可用的服务架构. 尽量多根据业务场景设计适合特定业务场景的粗粒度的业务服务,而减少数据服务的使用.

服务设计初探

- - 携程设计委员会
以往,交互设计师的日常主要是配合产品功能需求,快速地绘制线框图,做原型demo,设计网站、手机上人机界面的交互——可若仅限于此,不免有沦为“绘图师”之虞. 在屏幕上与用户的信息交互,只是用户行为的一环. 在设计的过程中我们大可以从固定的配合需求中跳出来,加入“服务设计”的理念,更广泛地思考用户与多服务触点的关系,以求给用户更好的体验.

云服务器安全设计

- - WooYun知识库
目前越来越多的初创企业把自己的业务系统架设在公有云上,包含:阿里云、Ucloud、青云、华为云和AWS. 在云上的安全怎么保证,是目前摆在我们面前的最大问题,因为,互联网公司业务系统在不断迭代,迭代周期最少的有3天,而且架构也不断在改变. 在这种频繁改变的过程中,云安全应该怎么保证. ,云主机安全服务平台(Cloud security as a service),为多租户提供云主机安全服务的产品,减少用户业务系统攻击面,防止恶意的定向攻击(APT).

[译] 微服务设计指南

- - IT瘾-dev
本文为翻译发表,转载需要注明来自公众号EAWorld. 作者:Thilina Ashen Gamage. 原题:Microservices Design Guide. 原文:http://t.cn/EAvCCMb. 全文5949字,阅读约需要10分钟. 2018年,每个人都听说过微服务. 微服务是当今软件工程师的一个热门话题.

电商课题:幂等性

- - 博客园_旁观者
幂等性的数学表达:f(f(x)) = f(x). 幂等性是系统接口对外的一种承诺. 幂等性指的是,使用相同参数对同一资源重复调用某个接口的结果与调用一次的结果相同. 幂等性的一个实现是,使你的接口必须返回 0(成功),即使这时资源或动作已经停止并且无工作要完成. 防范 POST 重复提交. HTTP POST 操作既不是安全的,也不是幂等的(至少在HTTP规范里没有保证).

同步类服务:还能怎么做产品设计创新?

- comain - 同步控
本文灵感源自上周在微博上发布的这么一条感想:. “花一个小时扫描了下本周 @爱范儿 上的有趣创新项目,基本归纳为:① 老土的领域垂直化(eProf/OrderWithMe);② 枯燥的事情趣味化(Pitochart/Perltrees);③ 蛋疼的玩法数字化(ibragu/LEGO Life);④ 无聊的数据社交化(NextGoals);⑤ 窥私的欲望公开化(teleportd).

以互联网产品为核心的服务设计

- - 百度商业用户体验部
当前互联网产品的发展日新月异,从业者在不断地深挖产品的方方面面,这些工作足以使产品的质量非常优秀,但为什么有时却得不到广大用户的认可呢. 从服务设计的角度来分析,我们也许可以得到答案. 进行产品设计时,一般要考虑四个因素:用户、情景、过程、对象(即产品本身). 在服务设计的思路里,产品可以是有形的,也可以是无形的——与用户发生交互的每个环节都是产品的一部分.

Memcached的服务设计与启动过程——C10K系列

- - 行业应用 - ITeye博客
C10k要解决的问题,是10K个连接. LINUX下,使用EPOLL可实现异步非阻塞(注:阻塞的一定是同步的,阻塞是调用方自己阻塞自己(等待事件)). 非阻塞:是指调用方不会阻塞自己,如被调用方有数据就返回,无数据就返回EAGAIN,调用方根据EAGAIN决定自己的策略. 因此非阻塞,和异步没有任何关联.