幂等性问题与解决方案-程序员宅基地

技术标签: 项目管理和设计思想  java  网络  服务器  

幂等性问题与解决方案

摘要

幂等概念来自数学,表示N次变换和1次变换的结果是相同的。这里讨论在某些场景下,客户端在调用服务没有达到预期结果时,会进行多次调用,为避免多次重复的调用对服务资源产生副作用,服务提供者会承诺满足幂等。HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的副作用(网络超时等问题除外)。

也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

  1. 幂等多次请求对资源没有副作用(比如查询数据库操作,没有增删改,因此没有对数据库有任何影响)。
  2. 幂等还包括第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。
  3. 幂等关注的是以后的多次请求是否对资源产生的副作用,而不关注结果
  4. 网络超时等问题,不是幂等的讨论范围。

幂等性是系统服务对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部调用失败是常态,并且失败之后必然会有重试

一、幂等的适用场景

业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而造成重复提交情况。在交易系统,支付系统这种重复提交造成的问题有尤其明显,比如:

  1. 前端重复提交表单: 在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
  2. 用户恶意进行刷单: 例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
  3. 接口超时重复提交:很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
  4. 消息进行重复消费: 当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

很显然,声明幂等的服务认为,外部调用者会存在多次调用的情况,为了防止外部多次调用对系统数据状态的发生多次改变,将服务设计成幂等。

1.1 保证幂等性的场景

以SQL为例,有下面三种场景,只有第三种场景需要开发人员使用其他策略保证幂等性:

  1. SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。
  2. UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。
  3. UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。
1.2 幂等与防止重复的区别
  1. 重复提交的情况,和服务幂等的初衷是不同的。重复提交是在第一次请求已经成功的情况下,人为的进行多次操作,导致不满足幂等要求的服务多次改变状态。
  2. 幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化。
1.3 设计幂等性服务

幂等使得客户端逻辑处理很简单,但是服务端逻辑会很复杂。满足幂等性服务需要包含两点逻辑:

  1. 首先去查询上一次的执行状态,如果没有则认为是第一次请求。
  2. 在服务改变状态的业务逻辑前保证防重复提交的逻辑。

执行是分两步执行的,后面一步依赖上面一步的查询结果,这样就无法保证原子性;无法保证原子性在高并发的情况下会存在问题:第二次请求在第一次请求的下一步订单状态没有修改为"已支付状态"时进行。

这个问题解决方案:将查询和变更状态操作加锁,并将并行操作改为串行执行

二、幂等性的解决方案

2.1 数据库唯一主键实现幂等性

数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。

使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键,这样才能能保证在分布式环境下 ID 的全局唯一性。

适用操作

  • 插入操作

使用限制

  • 需要生成全局唯一主键 ID(Snowflake、Uidgenerator、Leaf等算法);

主要流程如下:

  • 客户端调用服务端获取唯一主键ID接口。
  • 客户端调用服务端插入接口,如果插入成功则表示没有重复调用接口。如果抛出主键重复异常,则表示数据库中已经存在该条记录,返回错误信息到客户端。
2.2 数据库乐观锁实现幂等性

数据库乐观锁方案一般只能适用于执行更新操作的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。

这样每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。

适用操作

  • 更新操作

使用限制

  • 需要数据库对应业务表中添加额外字段

为了每次执行更新时防止重复更新,确定更新的一定是要更新的内容,我们通常都会添加一个 version 字段记录当前的记录版本, 这样在更新时候将该值带上,那么只要执行更新操作就能确定一定更新的是某个对应版本下的信息。

这样每次执行更新时候,都要指定要更新的版本号,如下操作就能准确更新 version=5 的信息:

UPDATE my_table SET price=price+50,version=version+1 WHERE id=1 AND version=51.

上面 WHERE 后面跟着条件 id=1 AND version=5 被执行后,id=1 的 version 被更新为 6,所以如果重复执行该条 SQL 语句将不生效,因为 id=1 AND version=5 的数据已经不存在,这样就能保住更新的幂等,多次更新对结果不会产生影响。

2.3 防重Token令牌实现幂等性

针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。

简单的说就是

  1. 调用方在调用接口的时候先向后端请求一个全局 ID(Token),
  2. 再次请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中), 后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令, 然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作。

适用操作

  • 插入操作
  • 更新操作
  • 删除操作

使用限制

  • 需要生成全局唯一 Token 串
  • 需要使用第三方组件 Redis 进行数据效验
2.4 下游传递唯一序列号实现幂等性

所谓请求序列号,其实就是每次向服务端请求时候附带一个短时间内唯一不重复的序列号,该序列号可以是一个有序 ID,也可以是一个订单号,一般由下游生成,在调用上游服务端接口时附加该序列号和用于认证的ID。

当上游服务器收到请求信息后拿取该 序列号和下游认证ID进行组合,形成用于操作Redis的Key,然后到 Redis 中查询是否存在对应的 Key 的键值对,根据其结果:

  • 如果存在,就说明已经对该下游的该序列号的请求进行了业务处理,这时可以直接响应重复请求的错误信息。
  • 如果不存在,就以该 Key 作为 Redis 的键,以下游关键信息作为存储的值(例如向下游传递的一些业务逻辑信息),将该键值对存储到 Redis 中 ,然后再正常执行对应的业务逻辑即可。

适用操作

  • 插入操作
  • 更新操作
  • 删除操作

使用限制

  • 要求第三方传递唯一序列号;
  • 需要使用第三方组件 Redis 进行数据效验;

三、幂等性的方案实现

幂等性是开发当中很常见也很重要的一个需求,尤其是支付、订单等与金钱挂钩的服务,保证接口幂等性尤其重要。在实际开发中,我们需要针对不同的业务场景我们需要灵活的选择幂等性的实现方式:

  1. 对于下单等存在唯一主键的,可以使用“唯一主键方案”的方式实现。
  2. 对于更新订单状态等相关的更新场景操作,使用“乐观锁方案”实现更为简单。
  3. 对于上下游这种,下游请求上游,上游服务可以使用“下游传递唯一序列号方案”更为合理。

类似于前端重复提交、重复下单、没有唯一ID号的场景,可以通过Token与Redis配合的“防重Token方案”实现更为快捷。

方案名称 适用方法 实现复杂度 方案缺点
数据库唯一主键 插入和删除操作 简单 只能用于插入和删除以及存在唯一主键的场景
数据库乐观锁 更新操作 简单 只能用于更新操作、表需要额外添加字段
请求序列号 都可以 简单 需要保证下游生成唯一序列号、需要redis保证可靠存储已请求的序列号
放重token令牌 都可以 适中 需要redis存储生成等token串
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46836425/article/details/130228736

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签