接口幂等性实现方式

优质博文:IT-BLOG-CN

幂等 操作的特点是一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。幂等函数或幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。这对于保证系统的一致性和可靠性非常重要。

具体来说,当一个接口被设计为幂等的时候,无论请求被执行多少次,结果都是一样的。这样可以避免由于网络延迟、重试或其他原因导致的重复请求对系统造成的副作用,比如重复创建订单、重复扣款等。实现接口幂等性可以提高系统的可靠性和稳定性,减少不必要的资源消耗和数据错误。同时,对于一些需要保证数据一致性的操作,比如金融交易、库存管理等,实现接口幂等性也是非常重要的。

一、为什么要实现接口幂等性

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

二、什么时候做幂等性

请求类型是否需要幂等描述
Get自身满足幂等性Get 方法用于获取资源。其一般不会也不应当对系统资源进行改变,所以是幂等的。
Post自身不满足幂等性Post 方法一般用于创建新的资源。其每次执行都会新增数据,所以不是幂等的。
Put可能满足,可能不满足Put 方法一般用于修改资源。该操作则分情况来判断是不是满足幂等,更新操作中直接根据某个值进行更新,也能保持幂等。不过执行累加操作的更新是非幂等。
Delete可能满足,可能不满足Delete 方法一般用于删除资源。该操作则分情况来判断是不是满足幂等,当根据唯一值进行删除时,删除同一个数据多次执行效果一样。但带查询条件的删除就不一定满足幂等,例如在根据某条件删除一批数据后,这时候新增加了一条数据也满足条件,然后又执行了一次删除,那么将会导致新增加的这条满足条件数据也被删除。

三、如何实现幂等性

客户端

客户端防止重复提交并不是绝对可靠的,可以通过工具略过前端直接访问后端。优点是实现起来比较简单。

按钮只能操作一次

提交后把按钮置灰或loding状态,消除用户因为重复点击而产生的重复记录,比如添加操作,由于点击两次而产生两条记录。

使用Post/Redirect/Get模式

在提交后执行页面重定向,这就是所谓的Post-Redirect—Get(PRG)模式。当用户提交表单后,跳转到一个重定向的信息页面,避免用户按F5刷新导致的重复提交,而且也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退导致同样重复提交的问题。

服务端

数据库唯一主键

利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式ID充当主键,这样才能能保证在分布式环境下ID的全局唯一性。

唯一索引

与唯一主键的思想一致,通过唯一性确保插入的幂等性。创建订单时,前端先通过接口获取订单号,再请求后端时带入订单号,订单表中订单号添加唯一索引,如果存在插入相同订单号则直接报错。消费MQ消息时,messageId是唯一的,我们可以新添加一种消费记录表,将messageId作为主键,如果重复消费那么就会存在相同的messageId,插入直接报错。

乐观锁

乐观锁就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制。数据库乐观锁一般只能适用于执行“更新操作”的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。每次对该数据库数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据的版本标识。

悲观锁

当对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。如下伪代码通过for update添加悲观锁,这里order_id需要有索引,否则会锁整张表。悲观锁影响性能一般不建议。

begin;  --  1.开始事务--  查询订单,判断状态select order_id,status from orders where order_id='1000234' for update ifstatus != 'S'){-- 非订单状态,不能更新为已完成;return ;}--  更新完成update order set status='s' order_no='1000234' 
commit; -- 2.提交事务

状态码

很多业务表,都是有状态的,比如订单表,一般订单有1-订单创建、2-订单支付、3-订单完成、4-取消订单等订单流程,当我们更新订单状态

update orders set status=3 where order_id='1000234' and status=2;

第一次请求时,将“订单支付”状态修改成“订单完成”,sql执行结果的影响行数是1。
第二次重复请求时,同样将“订单支付”状态修改成“订单完成”,但是sql执行结果的影响行数为0。如果是0,那么我们直接可以返回成功了。不需要做接下来的业务操作,以此来保证保证接口的幂等性。

基于分布式锁

分布式锁实现幂等性的逻辑就是,请求过来时,先去尝试获得分布式锁,如果获得成功,就执行业务逻辑,反之获取失败的话,就舍弃请求直接返回成功。其实前面介绍过的悲观锁,本质是使用了数据库的分布式锁,都是将多个操作打包成一个原子操作,保证幂等。但由于数据库分布式锁的性能不太好,我们都是通过RedisZookeeper来实现分布式锁。

客户端 + 服务端

Token机制

针对客户端连续点击或者调用方的超时重试等情况,可以用Token的机制实现防止重复提交。简单的说就是调用方在调用接口的时候先向后端请求一个全局ID(Token),请求的时候携带这个全局ID一起请求(Token最好将其放到Headers中),后端需要对这个Token作为Key,用户信息作为ValueRedis中进行键值内容校验,如果Key存在且Value匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的KeyValue不匹配就返回重复执行的错误信息,这样来保证幂等操作。

Token工具类: 接收Token串,加上Key前缀形成Key,再传入value值,执行Lua表达式保证命令执行的原子性,查找对应Key与删除操作。执行完成后验证命令的返回结果,如果结果不为空且非0,则验证成功,否则失败。

public class TockenUtlls {@Autowiredprivate RedisTemplate redisTemplate;// token前缀private static final String TOKEN_PRE = "token_";// 创建 token 并传入 Redispublic String generateToken(String value) { // 通过 UUID 创建 TokenString token = UUID.randomUUID().toString();String key = TOKEN_PRE + token;// 存储 Token 到 Redis 并设置超时时间redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);return token;}// 验证 Tokenpublic boolean validToken(String token, String value) {// 设置 LUA 脚本, KEYS[1] 代表 key, KEYS[2] 代表 valueString luaScript = " if redis.call('get', KEYS[1]) == KEYS[2]then return redis.call('get', KEYS[1]) else return 0 end ";RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);// 拼接 Redis KeyString key = TOKEN_PRE + token;// 执行 LUA 脚本Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value));// 根据返回值判断是否匹配成功并删除 Redis 键值对。   结果不为空则表示成功if (result != null && result != 0L) {redisTemplate.opsForValue().getAndDelete(key);return true;}return false;}
}

唯一序号

所谓请求序列号,其实就是每次向服务端请求时候附带一个短时间内唯一不重复的序列号,该序列号可以是一个有序ID,也可以是一个订单号,一般由上游生成,在调用下游服务端接口时附加该序列号和用于认证的ID。当下游服务器收到请求信息后拿取该“序列号”和上游"认证ID"进行组合,形成用于操作RedisKey,然后到Redis中查询是否存在对应的Key的键值对,如果不存在,就以该Key作为Redis的键,以上游关键信息作为存储的值,将该键值对存储到Redis中 ,然后再正常执行对应的业务逻辑即可。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/83342.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCB制造中铜厚度的重要性

电子产品中的PCB是现代电子设备中不可或缺的一部分。在PCB制造过程中&#xff0c;铜厚度是一个非常重要的因素。正确的铜厚度可以保证电路板的质量和性能&#xff0c;同时也影响着电子产品的可靠性和稳定性。 一般我们常见的铜厚有17.5um&#xff08;0.5oz&#xff09;&#x…

捕捉时刻:将PDF文件中的图像提取为个性化的瑰宝(从pdf提取图像)

应用场景&#xff1a; 该功能的用途是从PDF文件中提取图像。这在以下情况下可能会很有用&#xff1a; 图片提取和转换&#xff1a;可能需要将PDF文件中的图像提取出来&#xff0c;并保存为单独的图像文件&#xff0c;以便在其他应用程序中使用或进行进一步处理。例如&#xff…

微信小程序的项目解构

视频链接 黑马程序员前端微信小程序开发教程&#xff0c;微信小程序从基础到发布全流程_企业级商城实战(含uni-app项目多端部署)_哔哩哔哩_bilibili 接口文档 https://www.escook.cn/docs-uni-shop/mds/1.start.html 1&#xff1a;微信小程序宿主环境 1&#xff1a;常见的宿…

linux_常用命令

一、日常使用命令/常用快捷键命令 开关机命令 1、shutdown –h now&#xff1a;立刻进行关机 2、shutdown –r now&#xff1a;现在重新启动计算机 3、reboot&#xff1a;现在重新启动计算机 4、su -&#xff1a;切换用户&#xff1b;passwd&#xff1a;修改用户密码 5、logou…

如何对电脑文件进行备份?介绍五种常用方法

在现代生活中&#xff0c;我们的电脑中存储着大量重要的文件和数据。然而&#xff0c;电脑硬件可能会损坏&#xff0c;文件可能会被误删除或感染病毒等情况&#xff0c;因此定期备份文件至关重要&#xff0c;本文将介绍五种常用的电脑文件备份方法&#xff0c;并解决电脑数据没…

无涯教程-Perl - endpwent函数

描述 此功能告诉系统您不再希望使用getpwent从密码文件读取条目。在Windows下,使用Win32API::Net函数从域服务器获取信息。 语法 以下是此函数的简单语法- endpwent返回值 此函数不返回任何值。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perlwhile(($name, $pas…

分布式 - 服务器Nginx:一小时入门系列之静态网页配置

文章目录 1. 静态文件配置2. nginx listen 命令解析3. nginx server_name 命令解析4. nginx server 端口重复5. nginx location 命令 1. 静态文件配置 在 /home 文件下配置一个静态的AdminLTE后台管理系统&#xff1a; [rootnginx-dev conf.d]# cd /home [rootnginx-dev home…

一站式印度跨境电商平台开发--多用户购物商城搭建

搭建一个一站式印度跨境电商平台开发&#xff0c;需要考虑以下几个方面&#xff1a;平台设计&#xff0c;技术架构&#xff0c;多用户购物商城搭建。 一、平台设计&#xff1a; 1. 市场调研&#xff1a;了解印度电商市场的特点和需求&#xff0c;确定目标用户群体。 2. 平台功…

HIVE语法优化之Join优化

桶用两表关联字段,MapJoin时需要将小表填入内存,这时候,分桶就起到了作用 一个stage阶段代表一个mr执行,好几个MR,会吧每一个MR的结果都压缩 Mysql 慢查询 如果sql语句执行超过指定时间,定义该sql为慢查询,存储日志, 查问题: SQL日志,模拟慢SQL 然后查询执行计划 分组聚合 就…

Leetcode-每日一题【剑指 Offer 18. 删除链表的节点】

题目 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 注意&#xff1a;此题对比原题有改动 示例 1: 输入: head [4,5,1,9], val 5输出: [4,1,9]解释: 给定你链表中值为 5 的第二个节点&#xff0c;那么在调…

SpringCloud 尚硅谷 微服务简介以及Eureka使用

写在前面 该系列博客仅用于本人学习尚硅谷课程SpringCloud笔记&#xff0c;其中的错误在所难免&#xff0c;如有错误恳请指正。 官方源码地址&#xff1a;https://github.com/zzyybs/atguigu_spirngcloud2020 什么是SpringCloud Spring Cloud是微服务一站式服务解决方案&…

生产排查org.apache.http.NoHttpResponseException: 127.0.0.1:9000 failed to respond

生产环境&#xff0c;请求方调用我方地址&#xff0c;发生异常NoHttpResponseException&#xff0c;错误详情&#xff1a; org.apache.http.NoHttpResponseException: 127.0.0.1:9000 failed to respondat org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(Def…

翻出了我当时学习的笔记来了html

php&#xff1a;高级语言 web应用程序 万维网 浏览器中查看 apache&#xff1a;服务器 mysql&#xff1a;数据库 html 标签 css&#xff1a;层叠样式表 javascript&#xff1a;客户端脚本 js jquery mysql数据库基础 php语法基础 面向对象&#xff08;物件&#xff09; smar…

【非欧几里得域信号的信号处理】使用经典信号处理和图信号处理在一维和二维欧几里得域信号上应用低通滤波器研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

java日期常用操作

Testpublic void validateDateUtils(){// 1 字符串转换日期Date result DateUtil.parse("2023-08-01", com.alibaba.excel.util.DateUtils.DATE_FORMAT_10);log.info("result : [{}]" , result);// 2 日期转换字符串final Date date new Date();String f…

ELK中grok插件、mutate插件、multiline插件、date插件的相关配置

目录 一、grok 正则捕获插件 自定义表达式调用 二、mutate 数据修改插件 示例&#xff1a; ●将字段old_field重命名为new_field ●添加字段 ●将字段删除 ●将filedName1字段数据类型转换成string类型&#xff0c;filedName2字段数据类型转换成float类型 ●将filedNam…

面试热题(路径总和II)

给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 在这里给大家提供两种方法进行思考&#xff0c;第一种方法是递归&#xff0c;第二种方式使用回溯的方式进行爆…

博客网站添加复制转载提醒弹窗Html代码

网站如果是完全禁止右键&#xff08;复制、另存为等&#xff09;操作&#xff0c;对用户来说体验感会降低&#xff0c;但是又不希望自己的原创内容直接被copy&#xff0c;今天飞飞和你们分享几行复制转载提醒弹窗Html代码。 效果展示&#xff1a; 复制以下代码&#xff0c;将其…

Linux--core dump打开的情况下,运行下面的代码,会发生什么?

代码&#xff1a; #include <iostream> #include <signal.h> #include <unistd.h>using namespace std;void catchSig(int signum) {cout<< "进程捕捉到了一个信号&#xff0c;正在处理中&#xff1a; "<< signum << " p…

开发工具Eclipse的使用之导入项目(import)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Eclipse使用的相关操作吧 一.导读 上篇我们已经详细介绍了开发工具eclipse&#xff0c;也说明了eclipse的基本使用&#xff0c;那么我们这篇来详细讲述一下怎…