接口的幂等性如何设计

前言

所谓幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致

我们在开发中主要操作也就是CURD,其中读取操作和删除操作是天然幂等的,我们所关心的就是创建操作、更新操作。

创建操作一定是非幂等的因为要涉及到新数据的产生,而更新操作有可能幂等有可能非幂等,这个要看具体业务场景。

一、幂等性的使用场景

1、前端重复提交

就好比有个新增商品的功能,有个保存按钮,如果前端连续多次点击保存,后端就会收到多次请求接口,如果没做好幂等就会重复创建了多条记录,
就会出现脏数据。

这个也就是我们所说的如何防止前端重复提交的问题。

2、接口超时重试

当我们调取第三方接口的时候,有可能会因为网络等原因导致调用失败,所以我们会对接口调用添加失败重试的机制,Spring可以通过@Retryable注解实现重试机制。

既然重试就可能出现重复调用接口。这时再次调用时如果没有做好幂等,就可能出现脏数据。

3、消息重复消费

这个是无法避免的,因为我们说MQ在生产端和消费端都有重试机制,也就是同一消息很可能会被重复消费。

如果业务保证多次消费的结果是一样的那没问题,但是如果业务无法满足那就需要通过其它方式来保证消费端的幂等。

二、初级方式来保证尽量幂等

1、插入前先判断数据是否存在

这种是最基础的,也是我们在开发中必须要做的。我们会在插入或者更新前先判断下,当前这个数据数据库中是否已经存在,如果不存在则不允许重复插入,不存在则可插入。

代码示例如下:

public void save(Goods goods) {
        // 1、先通过商品唯一code,查询数据库属否存在   
        Goods goods = findGoods(goods.getCode);
        // 2、如果这条数据在db里已经存在了,此时就直接返回了   
        if (goods != null) {
            return;
        }
        // 3、如果要是这条数据在db里不存在,此时就会执行数据插入逻辑了   
        insertGoods(goods);
    }

2、前端做一些交互控制

好比有个新增商品的功能,有个保存按钮,用户点击保存按钮后,立马按钮置灰,或者页面跳转到商品列表页面,这样可以防止很大部分的前端重复提交。

三、高并发下如何保证幂等?

上面两种初级方法,在高并发下显然是无法保证接口幂等的,所以在高并发下,我们来如何保证接口的幂等呢,这里整理几种常见的解决办法。

1、基于悲观锁

定义: 当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。

这里以更新商品订单状态来举例:一般订单有订单创建订单确认订单支付订单完成取消订单等订单流程。

当我更新订单状态为订单完成的时候,我们首先通过判断该订单的状态是否是订单支付,如果是不是则直接返回,否则更新状态为已完成。

伪代码示例如下

begin; -- 1.开始事务
  -- 查询订单,判断状态
  select order_no,status from order where order_no='20200524-1' 
  if(status !=订单支付状态){
        -- 非订单支付状态,不能更新为已完成;
        return ;
    }
  -- 更新完成
  update order set status='订单完成' order_no='20200524-1' 
   commit; -- 2.提交事务

这是我们常见的一种写法,但这种写法在高并发环境下,可能会造成一个业务被执行两次的情况发生:

同时有两个请求过来,大家几乎同时查数据库订单状态,都是订单支付状态,然后就支持接下来一系列操作,这就导致一个业务被执行了两次,如果接下来一系列操作不是幂等的

那么就会出现脏数据。这里我们就可以通过悲观锁实现,也就是添加for update字段。

伪代码示例如下

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

1)这里order_no需要添加索引,否则会锁表

2) 悲观锁在同一事务操作过程中,锁住了一行数据。悲观锁性能不佳所以一般不建议用悲观锁做这个事情。

2、基于乐观锁

定义:乐观锁就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制

所谓的乐观锁就是在表中新增一个version(版本号)字段。

通过版本号的方式,来控制update的操作的幂等性,用户查询出要修改的数据,系统将数据返回给页面,将数据版本号放入隐藏域,用户修改数据,点击提交,将版本号一同提交

给后台,后台使用版本号作为更新条件。

update set version = version +1 ,count=count+1 where id =xxx and version = ${version};

注意:乐观锁能够保证的是update的操作的幂等性,如果你的update本身就是幂等操作,或者install操作那就不能用乐观锁了。

3、基于状态码

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

update order_table set status=3 where order_no='20200524-1' and status=2;

第一个请求时,成功把 订单确认 状态修改成 订单支付,sql执行结果的影响行数是1。

第二个请求时,同样想把 订单确认 状态修改成 订单支付,但是sql执行结果的影响行数为0。如果是0,那么我们直接可以返回成功了。而不需要做接下来的业务操作,以此来保证保证

接口的幂等性。

4、基于唯一索引

一般来讲悲观锁、乐观锁、状态码作用于update操作来实现幂等,而唯一索引是针对install操作来保证幂等。

1) 创建订单时,前端先通过接口获取订单号,再请求后端时带入订单号,订单表中订单号添加唯一索引,如果存在插入相同订单号则直接报错。

2) 消费MQ消息时,messageId是唯一的,我们可以新添加一种消费记录表,将messageId作为主键,如果重复消费那么就会存在相同的messageId,插入直接报错。

5、基于分布式锁

分布式锁实现幂等性的逻辑就是,请求过来时,先去尝试获得分布式锁,如果获得成功,就执行业务逻辑,反之获取失败的话,就舍弃请求直接返回成功。

其实前面介绍过的悲观锁,本质是使用了数据库的分布式锁,都是将多个操作打包成一个原子操作,保证幂等。但由于数据库分布式锁的性能不太好,

我们可以改用:redis或zookeeper来实现分布式锁。

6、基于 Token

token方案的特点就是:需要两次请求才能完成一次业务的操作。

一般包括两个请求阶段:

1)客户端请求申请获取token,服务端生成token返回。

2)第二次请求带着这个token,服务端验证token,完成业务操作。

注意:,在验证token是否存在,不要用redis.get(token)之后,在用redis.del(token),这样不是原子操作在高并发情况下依然会存在幂等问题。

我们可以直接用redis.del(token)的方式:

redis> SET key1 "Hello"
OK
redis> SET key2 "World"
OK
redis> DEL key1 key2 key3
(integer) 2
redis>

我们看返回是否大于0,就知道是否有数据了,而且因为redis命令操作是单线程的,所以不会出现同时返回1,所以是能够保证幂等的。

这种方式最大的缺点需要两次请求,其实简单点我们可以进行一次请求,那就是前端生成唯一token,而不通过后端获取。

Setnx 命令

在指定的 key 不存在时,为 key 设置指定的值。设置成功,返回1。 设置失败,返回 0。

实例

redis> EXISTS job      --  job 不存在
(integer) 0

redis> SETNX job "programmer"  --  job 设置成功
(integer) 1

redis> SETNX job "code-farmer" --  尝试覆盖 job ,失败
(integer) 0

redis> GET job                 --  没有被覆盖
"programmer"

如果返回1则说明第一次请求,如果返回0则说明不是第一次请求,直接返回。

这里需要注意的是Setnx命令key值不会自动过期的,所以不清除会一直占用内存,我们可以借助Expire命令来设置有效时间。

redis> SETNX mykey "programmer"  --  job 设置成功
(integer) 1
--  如果设置成功,那么设置将该键的超时设置为 10 秒
redis> expire mykey 10

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 如何使用 MockMvc 进行 Web 集成测试

SpringBoot 如何使用 MockMvc 进行 Web 集成测试 介绍 SpringBoot 是一个流行的 Java Web 开发框架,它提供了一些强大的工具和库,使得开发 Web 应用程序变得更加容易。其中之一是 MockMvc,它提供了一种测试 SpringBoot Web 应用程序的方式&…

(EMQX)STM32L+BC20+MQTT协议传输温湿度,ADC,电压,GPS数据到EMQX

1、材料准备 准备以下材料 2、设备连接 2.1 插入物联网卡,天线 首先把BC20核心板从开发板上拆下来 然后将物联卡放置在BC20核心板内 物联卡放置完成将BC20核心板重新插入到开发板内(注意不要弄错方向) 同时接入天线 2.2 连接ST-Link仿真…

RabbitMQ系列(27)--RabbitMQ使用Federation Exchange(联邦交换机)解决异地访问延迟问题

前言: (broker北京)、(broker深圳)彼此之间相距甚远,网络延迟是一个不得不面对的问题。有一个在北京的业务(Client北京)需要连接(broker北京),向其中的交换器exchangeA发送消息,此时的网络延迟很小,(Client北京)可以迅速将消息发…

嵌入式QT- QT使用MQTT

目录 一、MQTT介绍 二、MQTT概念 2.1 订阅(Subscribtion) 2.2 会话(Session) 2.3 主题名(Topic Name) 2.4 主题筛选器(Topic Filter) 2.5 消息订阅 三、MQTT中的角色 3.1 客户端 3.2 服务器 四、X86平…

每个前端开发者都应知道的10个实用网站

1.Documatic Documatic 地址:www.documatic.com/ Documatic 是一个高效的搜索引擎工具,旨在帮助开发人员轻松搜索他们的代码库,找到特定的代码片段、函数、方法和其他相关信息。该工具旨在通过在几秒钟内快速提供准确和相关的搜索结果&…

CSDN博客运营团队2023年H1总结

一、运营工作的指导思路 CSDN的核心价值之一是帮助开发者/技术人快速的学习成长和解决技术问题。这个核心价值的建立离不开无数开发者的无私贡献,而运营工作的核心就是要保证这个价值链不断放大:让内容贡献者体验更好,付出有回报&#xff0c…

Maya适合哪个工作站?

Autodesk Maya 提供多种功能,可以适应电影、游戏和建筑等不同行业的需求。定制的 Autodesk Maya 工作站可以帮助您提高行业领先的 3D 计算机动画、建模、模拟和渲染软件的工作效率和用户体验。 根据您的特定需求定制的快速、强大的工作站可以帮助您充分利用 Maya 工…

python脚本(Word转PDF格式小工具)

from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QMessageBox from PyQt5.QtCore import Qt import sys import os from comtypes import client#文件转换脚本 class FileDropWidget(QWidget):def __init__(self):super().__init__()self.initUI()de…

【SQL应知应会】行列转换(二)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习,有基础也有进阶,有MySQL也有Oracle 行列转换 • Mysql版 前言一、MySQL行列转换1.数据…

LayUI入门,以及介绍

一.LayUI基本情况 1.LayUI介绍 Layui(流行于 layui.com)是一款轻量级的前端UI框架,专注于提供简洁、易用、灵活的界面组件和交互体验。它基于HTML5和CSS3技术,采用模块化开发的思想,提供了丰富的UI组件、常用的工具函数…

一套完全开源,支持多租户,界面配置单点的后端框架JVS

JVS的多租户体系统 在IT系统中,“租户”(tenant)通常用于指代一种多租户架构(multi-tenancy),它是一种软件架构模式,允许多个用户或组织共享相同的应用程序或系统实例,但彼此之间的…

短视频抖音账号矩阵系统源码---功能架构示例1

一、短视频账号矩阵系统源码开发服务器集群化处理开发成本更低(前提) 什么是集群化处理视频内存内耗?集群化处理视频内存内耗是指通过建立集群系统,将视频处理任务分配给多个计算节点进行并行处理,以减少单个计算节点…

PHP 音乐欣赏网站mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP音乐欣赏网站 是一套完善的web设计系统,对理解php编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 代码下载 https://download.csdn.net/download/qq_41221322/88041034https://download.…

目标检测+车道线识别+追踪+测距(代码+部署运行)

目标检测车道线识别追踪测距 本文主要讲述项目集成:从车道线识别、测距、到追踪,集各种流行模型于一体! 不讲原理,直接上干货! 把下文环境配置学会,受益终生! 各大项目皆适用! …

什么是CI/CD?让你的项目变得更加敏捷!

在今天这个快速变化的时代,开发者们需要与时俱进,不断提升自己的工作效率。在这篇文章里,将一起探讨如何使用CI/CD和Github Action让你的项目更加高效,快速响应市场变化。 一、什么是CI? CI(持续集成&…

前端基础(三十六):读取本地音视频设备并进行播放

效果 navigator.mediaDevices.enumerateDevices 请求媒体输入和输出设备的列表,例如麦克风,摄像机,耳机设备等 navigator.mediaDevices.getUserMedia 会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream&#xff0c…

有关于我在用uniapp开发的问题

1、uniapp做微信小程序,内嵌H5进行支付 其本质是这样的,在微信小程序种,用web-view嵌入H5页面,然后通过web-view的url对H5进行传参,H5传参后可以做一些处理,然后再把参数传给小程序进行支付 //uniapp小程…

云开发校园微社区-二手交易兼职交友微信小程序

云开发校园微社区微信小程序开源源码,这是一款云开发校园微社区-二手交易_兼职_交友_项目微信小程序开源源码,可以给你提供快捷方便的校园生活,有很多有趣实用的板块和功能,如:闲置交易、表白交友、疑问互答、任务兼职…

使用腾讯云轻量应用服务器搭建网站教程

腾讯云轻量应用服务器怎么搭建网站?太简单了,轻量服务器选择宝塔Linux镜像,然后在宝塔面板上添加站点,以WordPress建站为例,腾讯云服务器网来详细说下腾讯云轻量应用服务器搭建网站全流程,包括轻量服务器配…

【企业微信多选的星期数据生成如“周一、周三至周六“】

目标效果如下图 实现这个过程首先是要找到逻辑,这个看似简单的操作却属实让我想了很久。 首先要了解需求 根据拿到的数据得到生成符合要求的字符串。中间有连续的星期大于三天的,开始和结束星期中间要有"至"。 分析逻辑 一、判断开始日期和…