热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

微服务下的几个难点问题及常见的解决方案

原文链接:https:cloud.tencent.comdevelopernews1362051背景介绍1.1幂等性定义数学定义在数学里,幂等有


原文链接: https://cloud.tencent.com/developer/news/136205

1背景介绍

1.1 幂等性定义

数学定义

在数学里,幂等有两种主要的定义:

在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1,即s*s=s

某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的,即f(f(x))=f(x)

HTTP规范定义

在HTTP/1.1规范中幂等性的定义是:

A request method is considered "idempotent" if the intended effect onthe server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.

HTTP的幂等性指的是一次和多次请求某一个资源应该具有相同的副作用。如通过PUT接口将数据的Status置为1,无论是第一次执行还是多次执行,获取到的结果应该是相同的,即执行完成之后Status =1。

1.2 幂等概念

微服务架构中,幂等是一致性方面的一个重要概念。幂等(Idempotent)是一个数学领域与计算机学的概念,常见于抽象代数中。而在编程中,一个幂等操作的特点是指其任意多次执行所产生的影响均与一次执行的影响相同。

有人会简单的认为,直接禁止所有重试即可。然而,重试是降低微服务失败率的重要手段。因为网络波动、系统资源分配的不确定性、跨机房的请求等等原因,都会或多或少的导致一小部分请求的失败。而这部分失败的请求中,又有大部分请求其实只需要简单重试几次,即可成功。

1.3 重试机制

降低微服务失败率

提高至四个或五个9

提高微服务架构的容错性

提高微服务架构的高可靠性

2 幂等分析

2.1 幂等场景

可能会发生重复请求或消费的场景,在微服务架构中是随处可见的。以下是笔者梳理的几个常见场景:

网络波动:因网络波动,可能会引起重复请求

分布式消息消费:任务发布后,使用分布式消息服务来进行消费

用户重复操作:用户在使用产品时,可能会无意的触发多笔交易,甚至没有响应而有意触发多笔交易

未关闭的重试机制:因开发人员、测试人员或运维人员没有检查出来,而开启的重试机制(如Nginx重试、RPC通信重试或业务层重试等)

2.2 CRUD分析

新增类请求(C)

数据库自增主键,不具备幂等性

查询类动作(R)

重复查询不会产生或变更新的数据,因此查询是天然具备幂等性

更新类请求(U)

基于主键的计算式Update,不具备幂等性,即:

基于主键的非计算式Update,具备幂等性,即:

基于条件查询的Update,不一定具有幂等性(需要根据实际情况进行分析判断)

删除类请求(D)

基于主建的Delete具备幂等性

一般业务层面都是逻辑删除(即Update操作),而基于主键的逻辑删除操作也是具有幂等性的

2.3 幂等重要性

针对一个微服务架构,如果不支持幂等操作,那将会出现以下情况:

电商超卖现象

重复转账、扣款或付款

重复增加金币、积分或优惠券

超卖现象

比如某商品的库存为1,此时用户1和用户2并发购买该商品,用户1提交订单后该商品的库存被修改为0,而此时用户2并不知道的情况下提交订单,该商品的库存再次被修改为-1这就是超卖现象。

究其深层原因,是因为数据库底层的写操作和读操作可以同时进行,虽然写操作默认带有隐式锁(即对同一数据不能同时进行写操作)但是读操作默认是不带锁的,所以当用户1去修改库存的时候,用户2依然可以都到库存为1,所以出现了超卖现象。

解决方案A:可以对读操作加上显式锁(即在select …语句最后加上for update)这样一来用户1在进行读操作时用户2就需要排队等待了。但问题来了,如果该商品很热门并发量很高那么效率就会大大的下降,如何解决呢?(解决方案B)

解决方案B:我们可以有条件有选择的在读操作上加锁,比如可以对库存做一个判断,当库存小于一个量时开始加锁,让购买者排队,这样一来就解决了超卖现象。

3 何种接口提供幂等性

3.1 HTTP幂等性

在HTTP规范中定义GET、PUT和DELETE方法应该具有幂等性,具体如下:

GET方法

The GET method requests transfer of a current selected representatiofor the target resourceGET is the primary mechanism of information retrieval and the focus of almost all performance optimizations. Hence, when people speak of retrieving some identifiable information via HTTP, they are generally referring to making a GET request.

GET方法是向服务器查询,不会对系统产生副作用,具有幂等性(不代表每次请求都是相同的结果)。

PUT方法

The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload.

也就是说PUT方法首先判断系统中是否有相关的记录,如果有记录则更新该记录,如果没有则新增记录。

DELETE 方法

The DELETE method requests that the origin server remove the association between the target resource and its current functionality. In effect, this method is similar to the rm command in UNIX: it expresses a deletion operation on the URI mapping of the origin server rather than an expectation that the previously associated information be deleted.

DELETE方法是删除服务器上的相关记录。

3.2 实际业务案例

现在简化为这样一个系统,用户购买商品的订单系统与支付系统;订单系统负责记录用户的购买记录已经订单的流转状态(orderStatus),支付系统用于付款,提供:

订单系统与支付系统通过分布式网络交互描述如下:



订单幂等性

这种情况下,支付系统已经扣款,但是订单系统因为网络原因,没有获取到确切的结果,因此订单系统需要重试。由上图可见,支付系统并没有做到接口的幂等性,订单系统第一次调用和第二次调用,用户分别被扣了两次钱,不符合幂等性原则(同一个订单,无论是调用了多少次,用户都只会扣款一次)。如果需要支持幂等性,付款接口需要修改为以下接口:

通过orderId来标定订单的唯一性,付款系统只要检测到订单已经支付过,则第二次调用不会扣款而会直接返回结果:



订单支持幂等性

在不同的业务中不同接口需要有不同的幂等性,特别是在分布式系统中,因为网络原因而未能得到确定的结果,往往需要支持接口幂等性。

3.3 分布式应用幂等性

随着分布式应用及微服务的普及,因为网络原因而导致调用应用未能获取到确切的结果从而导致重试,这就需要被调用应用具有幂等性。例如上文所阐述的支付系统,针对同一个订单保证支付的幂等性,一旦订单的支付状态确定之后,以后的操作都会返回相同的结果,对用户的扣款也只会有一次。这种接口的幂等性,简化到数据层面的操作:

其中value是用户要减少的订单,paystatus代表支付状态,paid代表已经支付,unpay代表未支付,orderid是订单号。在上文中提到的订单系统,订单具有自己的状态(orderStatus),订单状态存在一定的流转。订单首先有提交(0)→付款中(1)→付款成功(2)/ 付款失败(3),简化之后其流转路径如图:



订单状态流转的幂等性

当orderStatus = 1 时,其前置状态只能是0,也就是说将orderStatus由0->1 是需要幂等性的:

当orderStatus 处于0,1两种状态时,对订单执行0->1 的状态流转操作应该是具有幂等性的。这时候需要在执行update操作之前检测orderStatus是否已经=1,如果已经=1则直接返回true即可。

但是如果此时orderStatus = 2,再进行订单状态0->1 时操作就无法成功,但是幂等性是针对同一个请求的,也就是针对同一个requestid保持幂等,这时候再执行:

接口会返回失败,系统没有产生修改,如果再发一次,requestid是相同的,对系统同样没有产生修改。

4 解决方案

4.1 全局唯一ID

如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、Redis等。如果存在则表示该方法已经执行。

使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。

4.2 去重表

这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

4.3 插入或更新

这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:

4.4 多版本控制

这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等:

在实现时可以如下:

4.5 状态机控制

这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100,付款失败为99。在做状态机更新时,我们就这可以这样控制:

以上就是保证接口幂等性的一些方法。

5 总结

幂等性设计不能脱离业务来讨论,一般情况下,去重表同时也是业务数据表,而针对分布式的去重ID,可以参考以下几种方式:

UUID

Snowflake

数据库自增ID

业务本身的唯一约束

业务字段+时间戳拼接


推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
author-avatar
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有