【Java】java中接口幂等性解决方案

news2024/11/25 10:08:32

文章目录

  • 一、概念
  • 二、场景
  • 三、解决方案
    • 3.1、数据库唯一标识
    • 3.2、乐观锁
    • 3.3、悲观锁
    • 3.4、Token机制
    • 3.5、分布式锁
  • 四、总结

一、概念

一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

二、场景

  • 前端页面在填写一些表单点击提交保存按钮的时候,因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求,后端收到了好几次提交,这时就会在数据库中重复创建了多条记录,这就是接口没有幂等性带来的 bug。

  • 接口恶意调用刷单,比如投票功能,针对某一个用户重复提交,会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。

  • 一个订单创建接口,第一次调用超时了,然后调用方重试了一次,虽然第一次超时了,但是实际也许创建成功了,再次调用接口重试,这个时候就会调用2次创建接口,会创建2张订单,实际我们只想创建一张订单。

  • 电商系统订单消耗库存场景:在订单创建时,我们需要去扣减库存,由于种种原因接口发生了超时,调用方重试了一次,如果接口不是幂等的,就有可能减2次库存。我们重试的目的,其实只是想一次成功的请求,如果真的减去2次库存,那就不满足需求。

  • 电商系统订单退款的场景:当用户发起退款,退款接口超时,长时间未返回是否退款成功的结果,退款接口调用方重试一次,结果2次的退款请求都成功了,则会给用户退2次钱。

  • 使用消息中间件来处理消息队列,且手动 ack 确认消息被正常消费时。如果消费者突然断开连接,那么已经执行了一半的消息会重新放回队列。当消息被其他消费者重新消费时,如果没有幂等性,就会导致消息重复消费时结果异常,如数据库重复数据,数据库数据冲突,资源重复等。

三、解决方案

3.1、数据库唯一标识

在数据库唯一主键或者在相关的字段上添加唯一索引,客户端执行创建请求,调用服务端接口,后端生成布式 ID(雪花id、redis生成全局id等等方法) 充当主键或者建立唯一索引的字段值,这样才能能保证在分布式环境下 ID 的全局唯一性,后端将该条数据插入数据库中,如果插入成功则表示没有重复调用接口。如果抛出主键重复异常,则表示数据库中已经存在该条记录,返回错误信息到客户端;分布式唯一 ID 的 8 种生成方案

3.2、乐观锁

建表test01,在test01表中添加一个标识字段version,初始值设为1;
在这里插入图片描述

现在需要将张继科的age + 10;
更新前先查询张继科当前的version:1;

select version from test01 where name = '张继科';
update test01 set age = age + 100,version = version + 1 where name = '张继科' and version = 1;

更新数据的同时version+1,条件加上version = 1 (当前线程查到的版本号),然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

在这里插入图片描述

由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:

update test01 set age = age + 100,version = version + 1 where name = '张继科' and version = 1;

该update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,where中的version = 1肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。
在这里插入图片描述

注意:乐观锁的更新操作,最好用主键或者唯一索引来更新,这样是行锁,否则更新时会锁表;索引与表锁行锁的问题,我其他文章有详细说明;

3.3、悲观锁

要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

查询是否开启自动提交事务

select @@autocommit;

设置为手动提交事务

set autocommit = 0;

假如实际业务中:需要将张继科的age修改为16;
开启事务:

begin;

查询并锁当前行;注意:name字段上有索引;

select * from test01 where name = '张继科' for UPDATE;

执行业务,张继科的年龄改为16

UPDATE test01 set age = 16 where name =  '张继科';

注意:暂时先不执行: commint

在这里插入图片描述

模拟另一个线程(新建一个查询窗口):
#执行业务,张继科的年龄改为32

UPDATE test01 set age = 32 where name = ‘张继科’;

在这里插入图片描述

它会一直处于阻塞状态;
在这里插入图片描述

直到刚才修改age为16的线程提交(commint),才会释放;其他线程才能更新操作;不影响查询操作;我们现在把刚才修改age为16的线程commint:更新成功;其他线程可以正常更新;

需要特别注意的是:如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事务。此外,这里name字段一定要建立索引,不然会锁住整张表。悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场景,但是在防重场景中是可以的使用的。在这里顺便说一下,防重设计 和 幂等设计,其实是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。

3.4、Token机制

客户端在调用接口的时候向后台请求一个全局id(token),请求的时候就携带这个全局id传到后台,后端对这个token作为key,用户信息(sessionId)作为value,以键值对的方式在redis中进行校验,如果key相同且value匹配则删除,然后执行删除操作(存的是需要设置失效时间,删除时候注意原子性操作),否则属于重复提交;

a、服务端提供生成token的接口,注意全局唯一;
b、客户端调用接口获取token,同时后端将token放到redis中,token作为key,用户信息为value;
c、客户端将获取到的token放到当前表单隐藏域中;
d、客户端在执行提交表单时,把 token 存入到 Headers 中,执行业务请求带上该 Headers;
e、服务端收到请求后,从header中拿到token,根据key在redis中查找是否存在;
f、服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

注意:在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。其实现方法可以使用分布式锁或者使用 Lua 表达式来注销查询与删除操作

3.5、分布式锁

在业务系统插入数据或者更新数据,先获取锁,获取到锁,就继续后面的业务逻辑。如果没有获取到锁,就等待锁的释放直到获取锁,当执行完业务逻辑时,释放锁,当然,锁要设置超时时间,防止意外没有释放到锁,它可以用来解决分布式系统的幂等性,布式锁类似于防重表,将防重并发放到了缓存中,较为高效,同一时间只能完成一次操作,常用的分布式锁实现方案是redis和zookeeper等;目前redision(Redisson分布式锁底层原理)是最常用的,它不需要我们过于考虑原子操作,它包含了常用锁的类型,基本的可重入锁,读写锁,以及CountDownLatch的设置及使用,redisson的作者就是在加锁和解锁的执行层面采用Lua脚本,有原子性保证;总之它很轻大,请参考我之前写的文章Redisson分布式锁的使用(推荐使用)

四、总结

幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在像支付宝,银行,互联网金融公司等涉及的都是钱的系统,既要高效,数据也要准确,所以不能出现多扣款,多打款等问题,这样会很难处理,用户体验也不好。

另外,幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:并行执行的功能改为串行执行,降低了执行效率。增加了额外控制幂等的业务逻辑,复杂化了业务功能;

所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shape_based_matching lineMod开源代码学习

github开源代码地址: https://github.com/meiqua/shape_based_matching 针对匹配精度问题,原作者采用了sub-pixel icp的方法进行了最后的finetune,涉及到的相关原理可以查看:亚像素边缘提取与ICP2D的理解 - 知乎 涉及到的论文…

Precision Neuroscience提出第7层皮层接口:可扩展的微创脑机接口平台

大脑皮层包含六层结构,美国精密神经科学公司(Precision Neuroscience Corporation)的研究人员提出了第7层皮层接口:可拓展的微创脑机接口平台。构建了一种模块化的和可拓展的脑机接口平台,包括高通量薄膜电极阵列和微创…

新加坡市场扩张指南:品牌布局策略与挑战解析

随着全球化的发展和市场竞争的加剧,越来越多的企业开始考虑将业务拓展到新的市场。其中,新加坡作为一个经济繁荣、政治稳定、文化多元的国家,成为许多品牌出海的首选目的地之一。然而,要在这个激烈竞争的市场中取得成功&#xff0…

绘制stm32最小系统板

原理图: 不使用串口烧录,所以BOOT0和BOOT1都接地。 VDD:就是单片机的供电电压。 VDDA:VDD后面有个A,AAnalog,表示模拟的意思,就是芯片内部模拟器件的工作电压。 VSSA:表示模拟器件…

2023 Softing暑期培训计划

通过以实践为导向的培训课程提高能力 Softing将在2023年暑期为您提供全面的培训课程。在Softing线上研讨会中,您将体验到结构紧凑的培训课程,包含实用的用户问题讨论以及完善的理论知识交流,而无需学习冗长而枯燥的标准。无论您是初学者还是…

Unittest二次开发实战

目录 前言 unittest.TestResult类简介 TestResult类定制目标 实现步骤 测试结果summary格式规划 单个用例结果格式规划 用例tags和level的实现 根据测试方法对象获取用例代码 单个用例结果类的实现 TestResult属性及初始化方法 测试开始和测试结束 用例开始和用例结束 1. 重写恢…

Python面向对象编程2-面向过程的银行账号模拟程序 项目2.3 单个账户完整项目版本1

项目总目标:用面向过程思想设计一个简单的银行账号模拟程序。本次将迭代多个程序版本,每个版本都将添加更多功能。虽然这些程序没有达到发布的质量标准,但整个项目的目的是关注于代码如何与一个或多个银行账户的数据进行交互。 分析项目的必…

Windows11 WSL子系统ubuntu22.04 修改hostname

大家都知道以往我们修改 Linux系统的 hostname只要修改 /etc/hostname 这个文件中的名字即可,但是在WSL中修改该文件是无法生效的。 要修改Hostname并使它生效,我们必须修改 /etc/wsl.conf 文件。 编辑配置文件 sudo vi /etc/wsl.conf修改参数 将 ho…

SQL优化--如何定位慢查询?

目录 概述 检测方案 方案一:开源工具 方案二:MySQL自带慢日志 面试回答 大纲 范例 概述 在SQL中,“慢查询”是指执行速度较慢的数据库查询操作。 一般来说聚合查询,多表查询,表数据量过大查询,深度…

埃及极简史

埃及全称为阿拉伯埃及共和国,位于北非东部,领土还包括苏伊士运河以东、亚洲西南端的西奈半岛,埃及既是亚、非之间的陆地交通要冲,也是大西洋于印度洋之间海上航线的捷径,战略位置十分重要。 古埃及是古代四大文明古国之…

龙蜥白皮书精选:Ancert——硬件兼容性验证与守护

文/硬件兼容性 SIG Ancert 是龙蜥操作系统硬件兼容性测试套件,致力于验证硬件设备集成商等厂商发布的整机服务器和各种板卡外设与龙蜥操作系统 不同版本之间的兼容性,推动社区发行版在各种硬件设备上的适配,围绕龙蜥操作系统建立完善的硬件生…

Linux运维监控学习笔记5

监控项和应用集(重点) 监控项(item):监控项是从主机收集的信息。一个监控项是一个独立的子标,代表收集数据或监控的最小的单位。 应用集(applications):代表多个监控项…

手写数字识别Python+TensorFlow+CNN卷积神经网络【完整代码系统】

一、介绍 手写数字识别系统,使用Python语言,基于TensorFlow搭建CNN卷积神经网络算法对数据集进行训练,最后得到模型,并基于FLask搭建网页端界面,基于Pyqt5搭建桌面端可视化界面。 二、效果展示 三、演示视频完整代码…

【面试题】MySQL的面试题-分组后数据的处理

有一张业务表 data busi,字段为 id,业务号 busi no 以及编码 busi code,请写出根据busi code 将 busi no 合并去重的 sal。数据: 要求最后的结果为: 这个问题怎么解决呢? 其实解决这个问题的关键是分组后的数据合并,这里需要使用…

热加载工具spring-boot-devtools,使页面修改不重启服务器可以实时更新选择和创建数据库(谷粒商城)

阿丹&#xff1a; 希望可以通过工具进行来进行代码修改的时候能避免重启服务。来加快代码进度。 在pom.xml中添加spring-boot-devtools依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifact…

FI 创建资产和 子资产 BAPI_FIXEDASSET_OVRTAKE_CREATE

AS01 AS11 对应的BAPI 都是 BAPI_FIXEDASSET_OVRTAKE_CREATE 创建子资产有个标记CREATESUBNUMBER

PV180R1K1T1NMMC德国产派克柱塞泵

PV180R1K1T1NMMC德国产派克柱塞泵 派克柱塞泵的介绍&#xff1a; PARKER柱塞泵根据倾斜元件的不同&#xff0c;有斜盘式和斜轴式两种。斜盘式是斜盘相对回转的缸体有一倾斜角度&#xff0c;而引起柱塞在泵缸中往复运动。传动轴轴线和缸体轴线是一致的。这种结构较简单&#x…

async_await异常捕获

写在前面 不知道大家项目里面是怎么处理 async/await 的异常&#xff0c;我斗胆在我们项目里翻了一下&#xff0c;发现大量使用 try-catch 来处理 async/await 异常。 首先说明一下&#xff0c; try-catch 处理并没有什么问题&#xff0c;我只是觉得这么写代码会有点乱&#x…

如何设计一个高效的分布式日志服务平台

作者 | 百度智能小程序团队 导读 本文首先介绍了分布式服务下日志服务建设的挑战&#xff0c;然后介绍了下业内ELK的通用解决方案及与天眼日志服务的差异性&#xff0c;接下来详细介绍了天眼日志服务平台的整体架构&#xff0c;如何做采集、传输、检索、隔离、清理等机制的&…

自定义element-table列表展示(可设置按钮权限)

<template><!-- 二次封装表格&#xff0c; 仅支持单选 :style"{ height: height }"--><div class"self_table"><el-table:data"tableData"style"width: 100%"v-loading"loading"stripeselection-chang…