01-幂等性解释,问题及常用解决方案
目录
1. 幂等性简介
2. 后端如何解决幂等性问题
2.1 数据库层面
-> 2.1.1 防重表
-> 2.1.2 数据库悲观锁(不建议,容易出现死锁情况)
-> 2.1.3 数据库乐观锁
-> 2.1.4 乐观锁CAS算法原理
2.2 锁层面
2.3 幂等性token层面
-> 2.3.1 简介文字描述:
-> 2.3.2 简介图示:
编辑
-> 2.3.3 创建注解
-> 2.3.4 创建请求拦截器
----->方案一: 使用incr保证原子性
----->方案一代码: 拦截器
====>方案二: 使用分布式锁, 这篇文章暂不介绍
-> 2.3.5 获取幂等token代码
-> 2.3.6 接口测试
-> 2.3.7 使用测试工具进行测试
-> 2.3.8 幂等校验token的优,缺点
-----> 2.3.8.1 使用的优点:
-----> 2.3.8.2 使用的缺点:
3. 前端如何操作来避免幂等问题
-> 3.1前端防重
-> 3.2 PRG模式
1. 幂等性简介
分布式或微服务思想实现系统架构设计中, 服务相互调用,可能存在服务调用延迟或失败情况。服务端可能会进行多次点击提交。如果这样请求多次的话,那最终处理的数据结果就一定要保证统一,如 订单创建,支付扣款,库存扣减,物流发货等。此时就需要通过保证业务幂等性方案来完成。
2. 后端如何解决幂等性问题
2.1 数据库层面
-> 2.1.1 防重表
创建唯一索引: 多用于详情信息, 个人账户等业务,如果并发情况下出现多条,数据库报异常
创建唯一组合索引: 可以创建组合索引来保证唯一性
-> 2.1.2 数据库悲观锁(不建议,容易出现死锁情况)
加入悲观锁, for update, 有主键索引/明确索引就是行锁, 不然就是表锁
文章介绍跳转: -> mysql for update 详细介绍
-> 2.1.3 数据库乐观锁
占用数据库资源, 需要有版本号, 先查询版本号作为老的版本(oldVersion)存起来, 进行修改操作, 然后把这个值也进行修改+1 条件是version= oldVersion, 如果version不正确 修改失败
version= 5 100个线程同时取到5 其中一个修改成功version=6, 则其他99个失败
-> 2.1.4 乐观锁CAS算法原理
简介: compare and swap:比较与交换 , 核心即为 冲突检测和数据更新
文章介绍跳转: ->
2.2 锁层面
锁系列文章跳转(待定) ->
2.3 幂等性token层面
-> 2.3.1 简介文字描述:
1. 先制作幂等性token生成器, 创建token
2. 创建注解, 写入默认值 表示是否开启校验
3. 写拦截器 判断接口上是否有注解 如果没有,放行
4. 有注解的, 判断请求头中是否存在幂等token, 不存在 拦截请求
5. 存在, 判断幂等token真实性(是否过期), 不存在或incr数值异常 删除幂等token 拦截请求
6. 一切正常,删除token,放行请求, 进入controller
-> 2.3.2 简介图示:
-> 2.3.3 创建注解
package *;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 幂等性校验* 需要多线程测试后* 查出是否需要优化* @author pzy* @version 0.1.1 测试版本*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotentAnn {boolean value() default true;
}
-> 2.3.4 创建请求拦截器
----->方案一: 使用incr保证原子性
简介: Redis 的 INCR 命令可以将存储在指定键中的数字值增加 1。INCR 命令是一个原子性操作, 意味着它在执行过程中不会被中断。 例如,假设您有一个名为 "counter" 的键,存储的值为 0。如果您使用 INCR 命令将其 增加 1,那么这个键的值就会变为 1。如果再次使用 INCR 命令将其增加 1,那么这个键的值 就会变为 2。 由于INCR 命令是原子性的,因此可以在并发环境下使用
----->方案一代码: 拦截器
package *;import *;
import *;
import *;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;import static com.aisce.common.constant.RedisKeyConstants.IDEMPOTENT_TOKEN;/*** 幂等性校验拦截器(用哪个工程粘过去即可)* 使用方式:* <p>* <p>* 后端操作: @ApiIdempotentAnn注解 添加到接口位置即可 其他无需操作* <p>* 前端操作: 在请求头中添加 idempotent参数* 示例: "idempotent": "fcc8b373-b867-4d6c-9915-e3bd668db7b6"** @author pzy* @version 0.1.1 初期版本*/
@Slf4j
@Component
@Order(2)
public class ApiIdempotentInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 前置拦截器* 在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回true,则继续调用下一个拦截器。如果返回false,则中断执行,* 也就是说我们想调用的方法 不会被执行,但是你可以修改response为你想要的响应。*/@SuppressWarnings(value = {"all"})@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("===> 执行链2 执行");//如果handler不是和HandlerMethod类型,则返回trueif (!(handler instanceof HandlerMethod)) {return true;}//转化类型final HandlerMethod handlerMethod = (HandlerMethod) handler;//获取方法类final Method method = handlerMethod.getMethod();// 判断当前method中是否有这个注解boolean methodAnn = method.isAnnotationPresent(ApiIdempotentAnn.class);//如果有幂等性注解if (methodAnn && method.getAnnotation(ApiIdempotentAnn.class).value()) {// 需要实现接口幂等性//检查token//1.获取请求的接口方法boolean result = checkToken(request);//如果token有值,说明是第一次调用if (result) {//则放行return super.preHandle(request, response, handler);} else {//如果token没有值,则表示不是第一次调用,是重复调用response.setContentType("application/json; charset=utf-8");PrintWriter writer = response.getWriter();writer.print(new ObjectMapper().writeValueAsString(ResultResponse.error("重复调用,已拦截!")));writer.close();response.flushBuffer();log.error(String.format("===> %s 重复调用%s,已拦截!", request.getRemoteAddr(), request.getRequestURI()));return false;}}//否则没有该自定义幂等性注解,则放行return super.preHandle(request, response, handler);}/*** 幂等token的校验方法*/private boolean checkToken(HttpServletRequest request) {//从请求头对象中获取 幂等性校验token<idempotent>String idempotentToken = request.getHeader("idempotent");//如果不存在,则返回false,说明是重复调用if (StringUtils.isBlank(idempotentToken)) {return false;}//格式化幂等校验tokenidempotentToken = String.format(IDEMPOTENT_TOKEN, idempotentToken);if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(idempotentToken))) {return false;}//保证原子性<方式一> incrLong num = stringRedisTemplate.opsForValue().increment(idempotentToken);if (num != null && num.intValue() != 2) {//只要逻辑不对 删除key, 阻止请求delIdempotentToken(idempotentToken);return Boolean.FALSE;}//否则就是存在,存在则把redis里删除token//return Boolean.TRUE;return delIdempotentToken(idempotentToken);}/*** 删除幂等校验token** @param idempotentToken* @return*/private Boolean delIdempotentToken(String idempotentToken) {return Boolean.TRUE.equals(stringRedisTemplate.delete(idempotentToken));}}
====>方案二: 使用分布式锁, 这篇文章暂不介绍
下篇文章预告: 分布式锁基本使用方式,以及配合注解实现快速加锁
-> 2.3.5 获取幂等token代码
/*** 前端获取token,然后把该token放入请求的header中** @return*/@ApiOperation("获取幂等token")@GetMapping("/getIdempotentToken")public ResultResponse getIdempotentToken() {log.info("===> 获取幂等token <===");String idempotent = UUID.randomUUID().toString();stringRedisTemplate.opsForValue().set(String.format(IDEMPOTENT_TOKEN,idempotent),"1", Duration.ofSeconds(60*2));//2分钟 不随机数了 两分钟幂等失效拦截return ResultResponse.ok().setData(idempotent);}
-> 2.3.6 接口测试
ps: 使用方式: AxPost可以换成任意实体类
ResultResponse 随便的一个统一返回类型,没有写String 类型即可
@ApiIdempotentAnn@PostMapping("/testCheck")public ResultResponse testCheck(@RequestBody AxPost axPosts) {log.info("======>专业测试幂等性接口 <=======");System.out.println(axPosts);return ResultResponse.ok("ok");}
-> 2.3.7 使用测试工具进行测试
测试工具介绍: Postman,ApiPost, Idea httpclient tools,ApiFox后端测试方式

-> 2.3.8 幂等校验token的优,缺点
-----> 2.3.8.1 使用的优点:
通过token机制来保证幂等是常见的解决方案,同时也适合绝大部分场景,使用方便,
业务执行出现异常时,客户端需重新获取令牌并发起访问即可。推荐使用先删除token方案。
-----> 2.3.8.2 使用的缺点:
每次的业务请求, 都会产生一个额外的请求去获取token,但出现失败次数一般不会很多, 算是一种资源的浪费,虽然redis性能很好.
3. 前端如何操作来避免幂等问题
-> 3.1前端防重
通过前端防重保证幂等是最简单的实现方式,JS代码即可完成设置。可靠性并不好,可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。
-> 3.2 PRG模式
PRG模式即POST-REDIRECT-GET。当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。防止通过浏览器按钮前进/后退导致表单重复提交。
相关文章:
01-幂等性解释,问题及常用解决方案
目录 1. 幂等性简介 2. 后端如何解决幂等性问题 2.1 数据库层面 -> 2.1.1 防重表 -> 2.1.2 数据库悲观锁(不建议,容易出现死锁情况) -> 2.1.3 数据库乐观锁 -> 2.1.4 乐观锁CAS算法原理 2.2 锁层面 2.3 幂等性token层面 -> 2.3.1 简介文字描述: …...
SpringBoot配置文件
配置文件有两种格式: .properties .yml .properties是老版配置文件,.yml是新版配置文件 一、properties详解 IDEA社区版不支持 properties格式的日志的提示,需要安装相应插件。 3.1properties 基本语法 (ps:小技巧࿰…...
基于蜣螂算法改进的DELM分类-附代码
蜣螂算法改进的深度极限学习机DELM的分类 文章目录蜣螂算法改进的深度极限学习机DELM的分类1.ELM原理2.深度极限学习机(DELM)原理3.蜣螂算法4.蜣螂算法改进DELM5.实验结果6.参考文献7.Matlab代码1.ELM原理 ELM基础原理请参考:https://blog.c…...
FPGA纯verilog代码实现图像对数变换,提供工程源码和技术支持
目录1、图像对数变换理论2、log系数的matlab生成3、FPGA实现图像对数变换4、vivado与matlab联合仿真5、vivado工程介绍6、上板调试验证并演示7、福利:工程代码的获取1、图像对数变换理论 对数变换可以将图像的低灰度值部分扩展,显示出低灰度部分更多的细…...
【Python百日进阶-Web开发-Vue3】Day516 - Vue+ts后台项目3:首页
文章目录 一、首页头部1.1 element-plus中找到适合的Container布局容器1.2 头部容器Layout 布局1.3 src/views/HomeView.vue二、侧边菜单栏2.1 element-plus中找到适合的Menu侧栏2.2 src/views/HomeView.vue三、侧边栏的动态路由3.1 src/views/HomeView.vue3.2 src/views/Goods…...
分析了 200 个 DeFi 项目,我发现了这些规律
作者:Ren & Heinrich翻译:dongdong在这篇文章中,我分享了我通过分析当前排名前 200 的 DeFi 加密项目的见解。这不是一项学术研究。尽管如此,这些发现对加密货币投资者来说具有附加值。我使用 https://defillama.com/ 的公共数…...
你领证了吗?各地2022下半年软考纸质证书发放中
不少同学都在关注2022下半年软考证书领取时间,截止至目前,江苏、南京、山东、浙江、贵州、云南、大连、广西地区的纸质证书可以领取了,请大家在证书申领时间内及时预约证书邮寄发放哦~ 江苏 证书领取时间:2023年2月3日起 南京 …...
将群晖NAS变为本地盘
本文介绍一个工具,可以在 Windows 系统下将群晖NAS的目录变为本地盘,好处是在外部访问的时候,能够大大改善体验。可以用本地的应用程序直接打开,速度依赖网络带宽,正常情况下,看视频是没有问题的。当然&…...
以太坊上交易异常Pending的处理方法
交易Pending ETH交易pending的原因: 1.交易GasPrice设置过低,共识节点不打包 2.账户Nonce不连续,一直处于交易池队列当中 只要确认了是哪种原因引起的,就可以做出对应的解决方案。 GasPrice设置过低 由于ETH共识节点是按照Gas价格从高到低打包交易,如果每笔交易的GasPr…...
第三节 第一个内核模块
hellomodule 实验 实验说明 硬件介绍 本节实验使用到STM32MP157 开发板 实验代码讲解 本章的示例代码目录为:linux_driver/module/hellomodule 从前面我们已经知道了内核模块的工作原理,这一小节就开始写代码了,跟hello world 一样&…...
从CNN到Transformer:基于PyTorch的遥感影像、无人机影像的地物分类、目标检测、语义分割和点云分类
我国高分辨率对地观测系统重大专项已全面启动,高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成,将成为保障国家安全的基础性和战略性资源。随着小卫星星座的普及,对地观测已具备多次以上的全球覆盖…...
操作系统的奋斗(三)内存管理
第三章 内存管理3.1内存管理概念3.1.1 内存管理的基本原理和要求(1)内存管理的主要功能3.1.2 覆盖和交换(1)覆盖(2)交换3.1.3 连续分配管理方式(1)单一连续分配(2&#x…...
多选多的一种通用处理逻辑
开发的时候,我们经常会涉及元素的多选多,并且还需要对选中的元素进行拖动排序 通用的设计方案如下 游戏资源集合与游戏资源的绑定关系处理(多选多的一种通用处理逻辑) 可能的情况: 1.之前被选中的资源,现…...
Redis 的安装 + SpringBoot 集成 Redis
1.安装 Redis此处的 Redis 安装是针对 Linux 版本的安装, 因为 Redis 官方没有提供 Windows 版本, 只提供了 Linux 版本. 但是我们可以通过Windows 去远程连接 Redis.1.1 使用 yum 安装 Redis使用如下命令, 将 Redis 安装到 Linux 服务器:yum -y install redis1.2 启动 Redis使…...
为什么在容器中 1 号进程挂不上 arthas?
作者:卜比 本文是《容器中的 Java》系列文章之 4/n ,欢迎关注后续连载 😃 。 系列1:JVM 如何获取当前容器的资源限制? 系列2:Java Agent 踩坑之 appendToSystemClassLoaderSearch 问题 系列3:让…...
23种设计模式之策略模式
一、概念 就是将一系列算法封装起来,并使它们之间相互替换。被封装起来的算法具有独立性外部不可改变其特性。 策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算…...
不会做UI自动化测试?一起设计框架再实践吧
目的相信做过测试的同学都听说过自动化测试,而UI自动化无论何时对测试来说都是比较吸引人的存在。相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟,那么存在即合理,自动化UI测试自然也是广大测试同学职业道…...
数据分析实战项目3:RFM用户分群
目录1、RFM模型介绍2、Excel实际RFM划分案例3、RFM案例3.1 数据加载和基本信息查看3.2 数据预处理和RFM的初始值计算3.3 RFM区间和划分和分值计算3.4 RFM计算结果保存3.4.1 保存到excel3.4.2 保存到数据库3.5 RFM计算结果可视化3.6 结果分析(营销建议)3.…...
消息中间件概述
目录1.为什么学习消息队列2.什么是消息中间件3.消息队列应用场景3.1 应用解耦3.2 异步处理3.3 流量削峰3.4 什么是QPS,PV3.5 什么是PV,UV,PR4. AMQP 和 JMS4.1 AMQP4.2 JMS4.3. AMQP 与 JMS 区别5. 消息队列产品6. RabbitMQ6.1 RabbitMQ简介6.2 RabbitMQ 中的相关概…...
vue和js给后端接口返回的数据(如以json数据为元素的数组)添加新的json字段
文章目录vue和js给后端接口返回的数据(如以json数据为元素的数组)添加新的json字段1. res为后端接口的响应2. 获取后端接口返回的数据3. 向 tableData 添加字段3.1. 向 tableData 中添加一个新json元素( {"time", "2023-02-09"} )3.…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

