接口的幂等性

article/2025/10/3 17:39:48

1、接口调用存在的问题

现如今我们的系统大多拆分为分布式 SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用 RPC 通信或者 restful。既然是通信,那么就有可能在服务器处理完毕后返回结果的时候挂掉,这个时候用户端发现很久没有反应,那么就会多次点击按钮,这样请求有多次,那么处理数据的结果是否要统一呢?那是肯定的!尤其在支付场景。

2. 什么是接口幂等性

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...这就没有保证接口的幂等性。

3、什么情况下需要保证接口的幂等性

在增删改查 4 个操作中,尤为注意就是增加或者修改。

(1)查询操作

查询对于结果是不会有改变的,查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select 是天然的幂等操作。

(2)删除操作

删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回 0,删除的数据多条,返回结果多个,在不考虑返回结果的情况下,删除操作也是具有幂等性的)

(3)更新操作

修改在大多场景下结果一样,但是如果是增量修改是需要保证幂等性的,如下例子:

把表中 id 为 XXX 的记录的 A 字段值设置为 1,这种操作不管执行多少次都是幂等的。

把表中 id 为 XXX 的记录的 A 字段值增加 1,这种操作就不是幂等的。

(4)新增操作

增加在重复提交的场景下会出现幂等性问题,如以上的支付问题。

4、那么如何设计接口才能做到幂等呢?

常见的两种实现方案: 

1、通过代码逻辑判断实现。   

2、使用 token 机制实现。

下面以支付系统为例,分别对接口的幂等性进行说明与实现。

(1)通过代码逻辑判断实现接口幂等性,只能针对一些满足判断的逻辑实现,具有一定局限性。

用户购买商品的订单系统与支付系统;订单系统负责记录用户的购买记录已经订单的流转状态(orderStatus),支付系统用于付款,提供如下接口,订单系统与支付系统通过分布式网络交互。

boolean pay(int accountid, BigDecimal amount) // 用于付款,扣除用户的  

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

boolean pay(int orderId, int accountId, BigDecimal amount)

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

上文所阐述的支付系统,针对同一个订单保证支付的幂等性,一旦订单的支付状态确定之后,以后的操作都会返回相同的结果,对用户的扣款也只会有一次。这种接口的幂等性,简化到数据层面的操作:

update userAmount set amount = amount - 'value' ,paystatus = 'paid' where orderId= 'orderid' and paystatus = 'unpay'

其中 value 是用户要减少的订单,paystatus 代表支付状态,paid 代表已经支付,unpay 代表未支付,orderid 是订单号。

在上文中提到的订单系统,订单具有自己的状态(orderStatus),订单状态存在一定的流转。订单首先有提交(0),付款中(1),付款成功(2),付款失败(3),简化之后其流转路径如图:

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

update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0

当 orderStatus 处于 0,1 两种状态时,对订单执行 0 -> 1  的状态流转操作应该是具有幂等性的。这时候需要在执行 update 操作之前检测 orderStatus 是否已经 = 1,如果已经 = 1 则直接返回 true 即可。但是如果此时 orderStatus = 2,再进行订单状态 0 -> 1 时操作就无法成功,但是幂等性是针对同一个请求的,也就是针对同一个 request id 保持幂等。

这时候再执行

 update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0

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

(2)使用 token 机制实现接口幂等性,通用性强的实现方法

token 机制实现步骤:

A、生成全局唯一的 token, token 放到 redis 或 jvm 内存,token 会在页面跳转时获取,存放到 pageScope 中,支付请求提交先获取 token 。

B、提交后后台校验 token,执行提交逻辑,提交成功同时删除 token,生成新的 token 更新 redis,这样当第一次提交后 token 更新了,页面再次提交携带的 token 是已删除的 token 后台验证会失败不让提交。

token 特点:   要申请,一次有效性,可以限流。

注意: redis 要用删除操作来判断 token,删除成功代表 token 校验通过,如果用 select + delete 来校验 token,存在并发问题,不建议使用。

转载于:接口的幂等性 - fy_qxl - 博客园

(SAW:Game Over!)


http://chatgpt.dhexx.cn/article/1cB17IgY.shtml

相关文章

如何保证接口的幂等性?

什么是幂等性?所谓幂等,就是任意多次执行所产生的影响均与一次执行的影响相同。 为什么会产生接口幂等性问题 在计算机应用中,可能遇到网络抖动,临时故障,或者服务调用失败,尤其是分布式系统中,接口调用…

什么是接口的幂等性以及如何实现接口幂等性

目录 1、接口调用存在的问题 2、什么是接口的幂等性 3、不做接口的幂等性会产生什么影响 4、什么情况下需要保证接口的幂等性 4.1 select:查询操作 4.2 insert:新增操作 4.3 delete:删除操作 4.3.1 绝对删除 具有幂等性 4.3.2 相对删…

Python安装pygame教程

Python安装pygame教程 1、版本说明 由于python3.8与pygame存在不兼容的问题,因此在下载Python的时候,需要下载python3.8以下版本的,我下载的是python3.7.5 2、具体步骤 1、在本机控制台通过命令安装pygame pip install pygame 出现错误 …

Pygame 教程(2):重要的概念及对象

本章,将介绍 Pygame 中的重要概念及对象。 导航 上一章:创建第一个应用程序 下一章:绘制图形 文章目录 导航像素坐标Rect 对象Color 对象Surface 对象实例:矩形生成器创建初始窗口创建生成矩形的函数调用函数完整代码 结语 像素…

Pygame教程系列二:MoviePy视频播放篇

【前言】 在pygame 2.0.0版本之前,播放视频可以使用pygame.movie.Movie(xxxx.mpg)播放(只支持.mpg格式的视频),但是在pygame 2.0.0之后,作者因为觉得视频模块维护成本太高就给抛弃了,假如你使用pygame 2.0.0,还调用上述…

Pygame 教程(4):图像传输和绘制文本

本章,你将学会如何传输图像和绘制文本。 导航 上一章:绘制图形 下一章:监测游戏时间 文章目录 导航加载图像导出图像绘制文本实例:画板添加常量限制坐标定义属性绘制色板更改线条粗细处理鼠标事件处理键盘事件调用事件处理方法完…

Pygame基础教程(一)

写在前面的话: 本系列教程仅有一些在本机调试通过的代码(如代码中发现bug,恳请包涵)。除代码中出现的一些主要注释外,不会出现太多其他文字解释,但是,文章中会给出主要模块的官方文档地址。再次…

Pygame 教程(5):监测游戏时间

本章,你将学习如何监测游戏时间。 导航 上一章:图像传输和绘制文本 下一章:努力更新中…… 文章目录 导航监测时间游戏帧速率实例:绘图性能对比结语 监测时间 在游戏程序中,时常需要随着时间的流逝而做出不同的动作…

Pygame教程系列四:播放音频篇

【前言】 pygame播放音频文件这部分相对来说比较简单,主要是用到pygame.mixer模块,不过也有一些地方需要注意的,咱们直接先看看案例 1、案例效果图 2、案例代码 import pygame from mutagen.mp3 import MP3 # 标识是否退出循环 exitFlag Fa…

python3.8安装pygame_Python3.8安装Pygame教程

注:因为最近想用一下Python做一些简单小游戏的开发作为项目练手之用,而Pygame模块里面提供了大量的有用的方法和属性。今天我们就在之前安装过PyCharm的基础上,安装Pygame,下面是安装的步骤,希望能够帮到大家。 第一步…

Pygame 教程(3):绘制图形

本章,你将学习如何在 Pygame 中绘制图形。 导航 上一章:重要的概念及对象 下一章:图像传输和绘制文本 文章目录 导航抗锯齿draw 模块实例:跟随鼠标的图形创建初始窗口添加变量捕捉鼠标事件绘制图形完整代码 结语 抗锯齿 抗锯齿…

Mac Pycharm导入Pygame教程(超细)

首先先新建一个想要使用Pygame的项目 进入项目后,点击文件(File)——新项目设置(settings) 点击新项目的偏好设置(Preferences for new project ) 随后可以看到 点击Python 编译器&#xff0…

mac python3.8上怎么安装pygame 第三方库_Python3.8安装Pygame教程步骤详解

注:因为最近想用一下python做一些简单小游戏的开发作为项目练手之用,而Pygame模块里面提供了大量的有用的方法和属性。今天我们就在之前安装过PyCharm的基础上,安装Pygame,下面是安装的步骤,希望能够帮到大家。 第一步 安装Python和pip 如果已安装,使用python --version …

Pygame教程系列一:快速入门篇

【简介】 Pygame 是python用来开发视频游戏的游戏引擎,底层主要是SDL库实现,算是目前利用python开发小游戏的一个性能比较高的一个游戏框架 一、安装pygame 使用pip下载安装 pip install pygame二、入门案例详析 1、示例效果 2、示例代码 import os …

pygame教程3

目录 做个小游戏精灵类精灵类介绍使用精灵类 做个小游戏 这个小游戏使用了pygame教程2的知识。 #↓初始化 import pygame,sys from pygame.locals import * pygame.init()#初始化pygame。 screen pygame.display.set_mode((800,600)) pygame.display.set_caption("Hell…

pygame教程2

目录 响应键盘上的事件让画面动起来了解x轴和y轴画圆形圆形动起来 响应键盘上的事件 import pygame#导入pygame。 import sys#导入sys from pygame.locals import *#导入pygame所有的常量,方便以后使用。 pygame.init()#初始化pygame。 screen pygame.display.set…

pygame教程笔记

pygame教程 安装pygameGame Development 1-1: Getting Started with PygameGame Development 1-2: Working with SpritesGame Development 1-3: More About SpritesPygame Shmup Part 1: Player Sprite and ControlsPygame Shmup Part 2: Enemy SpritesPygame Shmup Part 3: Co…

pygame基础教程

pygame简介 pygame可以实现python游戏的一个基础包。 pygame实现窗口 初始化pygame,init()类似于java类的初始化方法,用于pygame初始化。 pygame.init() 设置屏幕,(500,400)设置屏幕初始大小为500 * 400的大小, 0和32 是比较高…

Python pygame(GUI编程)模块最完整教程(1)

提示:下滑文章左侧可以查看目录!本教程分为多篇,总目录如下。 总目录: README.md Python-ZZY/Python-Pygame最完整教程 - Gitee.com 1 初识pygame 1.1 简介 pygame是python中一个流行的GUI编程模块,是专门为了开发游…

Pygame教程(非常详细)

文章目录 教程特点阅读条件 Pygame是什么扩展知识 Pygame下载和安装1) pip包管理器安装2) 二进制安装包安装 第一个Pygame程序初始化程序创建Surface对象事件监听游戏循环 Pygame Display显示模块详解Pygame Surface创建图像Pygame Transform图像变形Pygame Time时间控制详解1)…