幂等方案分析

news2024/9/23 16:43:10

幂等性介绍

幂等是一个数学上的概念 f(n) = 1^ n 无论n为多少 f(n)的值永远为1

在我们的编程中定义为: 无论对某一个资源操作了多少次,其影响都应是相同的。

以SQL为例:

select * from table where id=1。此SQL无论执行多少次,虽然结果有可能出现不同,都不会对数据产生改变,具备幂等性。

insert into table(id,name) values(1,‘heima’)。

此SQL如果id或name有唯一性约束,多次操作只允许插入一条记录,则具备幂等性。如果不是,则不具备幂等性,

多次操作会产生多条数据。

update table set score=100 where id = 1。此SQL

无论执行多少次,对数据产生的影响都是相同的。具备幂等性。

update table set score=50+score where id = 1。

此SQL涉及到了计算,每次操作对数据都会产生影响。不具备幂等性。

delete from table where id = 1。此SQL多次操作,产生的结果相同,具备幂等性

接口幂等

对于幂等的考虑,主要解决两点前后端交互与服务间交互。这两点有时都要考虑幂等性的实现。从前端的思路解决的话,主要有三种:前端防重、PRG模式、Token机制

前端防重

通过前端防重保证幂等是最简单的实现方式,前端相关属性和JS代码即可完成设置。可靠性并不好,有经验的人员可以通过工具跳过页面仍能重复提交。主要适用于表单重复提交或按钮重复点击。

PRG模式

PRG模式即POST-REDIRECT-GET。当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。同时防止了通过浏览器按钮前进/后退导致表单重复提交。是一种比较常见的前端防重策略。

token机制

通过token机制来保证幂等是一种非常常见的解决方案,同时也适合绝大部分场景。该方案需要前后端进行一定程度的交互来完成。

流程图

image-ljrv.png

1)服务端提供获取token接口,供客户端进行使用。服务端生成token后,如果当前为分布式架构,将token存放于redis中,如果是单体架构,可以保存在jvm缓存中。

2)当客户端获取到token后,会携带着token发起请求。

3)服务端接收到客户端请求后,首先会判断该token在redis中是否存在。如果存在,则完成进行业务处理,业务处理完成后,再删除token。如果不存在,代表当前请求是重复请求,直接向客户端返回对应标识。

先执行业务再删除token带来的问题

但是现在有一个问题,当前是先执行业务再删除token。在高并发下,很有可能出现第一次访问时token存在,完成具体业务操作。但在还没有删除token时,客户端又携带token发起请求,此时,因为token还存在,第二次请求也会验证通过,执行具体业务操作。

解决办法

对于这个问题的解决方案的思想就是并行变串行。会造成一定性能损耗与吞吐量降低。

第一种方案:对于业务代码执行和删除token整体加线程锁。当后续线程再来访问时,则阻塞排队。

第二种方案:借助redis单线程和incr是原子性的特点。当第一次获取token时,以token作为key,对其进行自增。然后将token进行返回,当客户端携带token访问执行业务代码时,对于判断token是否存在不用删除,而是对其继续incr。如果incr后的返回值为2。则是一个合法请求允许执行,如果是其他值,则代表是非法请求,直接返回。

那如果先删除token再执行业务呢?其实也会存在问题,假设具体业务代码执行超时或失败,没有向客户端返回明确结果,那客户端就很有可能会进行重试,但此时之前的token已经被删除了,则会被认为是重复请求,不再进行业务处理。

这种方案无需进行额外处理,一个token只能代表一次请求。一旦业务执行出现异常,则让客户端重新获取令牌,重新发起一次访问即可。推荐使用先删除token方案

但是无论先删token还是后删token,都会有一个相同的问题。每次业务请求都回产生一个额外的请求去获取token。但是,业务失败或超时,在生产环境下,一万个里最多也就十个左右会失败,那为了这十来个请求,让其他九千九百多个请求都产生额外请求,就有一些得不偿失了。虽然redis性能好,但是这也是一种资源的浪费

服务幂等

防重表

对于防止数据重复提交,还有一种解决方案就是通过防重表实现。防重表的实现思路也非常简单。首先创建一张表作为防重表,同时在该表中建立一个或多个字段的唯一索引作为防重字段,用于保证并发情况下,数据只有一条。在向业务表中插入数据之前先向防重表插入,如果插入失败则表示是重复数据。

select+insert防重提交

对于一些后台系统,并发量并不高的情况下,对于幂等的实现非常简单,通过select+insert思想即可完成幂等控制。在业务执行前,先判断是否已经操作过,如果没有则执行,否则判断为重复操作。

image-gln4.png

Mysql乐观锁

版本号

基于条件

通过版本号控制是一种非常常见的方式,适合于大多数场景。但现在库存扣减的场景来说,通过版本号控制就是多人并发访问购买时,查询时显示可以购买,但最终只有一个人能成功,这也是不可以的。其实最终只要商品库存不发生超卖就可以。那此时就可以通过条件来进行控制。

Redis分布式锁

image-fb2q.png

单节点的Redis实现分布式锁

redis实现分布式锁也很简单,基于客户端的几个API就可以完成,主要涉及三个核心API:

setNx():向redis中存key-value,只有当key不存在时才会设置成功,否则返回0。用于体现互斥性。

expire():设置key的过期时间,用于避免死锁出现。

delete():删除key,用于释放锁。

但是也带来一个问题

锁误删

解锁时,要避免当前线程将别人的锁释放掉。假设线程A加锁成功,当过了一段时间线程A来解锁,但线程A的锁已经过期了,在这个时间节点,线程B也来加锁,因为线程A的锁已经过期,所以线程B时可以加锁成功的。此时,就会出现问题,线程A将线程B的锁给释放了。对于这个问题,就需要使用到加锁时的requestId。当解锁时要判断当前锁键的value与传入的value是否相同,相同的话,则代表是同一个人,可以解锁。否则不能解锁。

锁续期

当对业务进行加锁时,锁的过期时间,绝对不能想当然的设置一个值。假设线程A在执行某个业务时加锁成功并设置锁过期时间。但该业务执行时间过长,业务的执行时间超过了锁过期时间,那么在业务还没执行完时,锁就自动释放了。接着后续线程就可以获取到锁,又来执行该业务。就会造成线程A还没执行完,后续线程又来执行,导致同一个业务逻辑被重复执行。因此对于锁的超时时间,需要结合着业务执行时间来判断,让锁的过期时间大于业务执行时间。上面的方案是一个基础解决方案,但是仍然是有问题的。业务执行时间的影响因素太多了,无法确定一个准确值,只能是一个估值。无法百分百保证业务执行期间,锁只能被一个线程占有。如想保证的话,可以在创建锁的同时创建一个守护线程,同时定义一个定时任务每隔一段时间去为未释放的锁增加过期时间。当业务执行完,释放锁后,再关闭守护线程。 这种实现思想可以用来解决锁续期

服务单点 集群问题

在单点redis虽然可以完成锁操作,可一旦redis服务节点挂掉了,则无法提供锁操作。在生产环境下,为了保证redis高可用,会采用异步复制方法进行主从部署。当主节点写入数据成功,会异步的将数据复制给从节点,并且当主节点宕机,从节点会被提升为主节点继续工作。假设主节点写入数据成功,在没有将数据复制给从节点时,主节点宕机。则会造成提升为主节点的从节点中是没有锁信息的,其他线程则又可以继续加锁,导致互斥失效

单机Redisson实现分布式锁

基于redisson实现分布式锁很简单,直接基于lock()&unlock()方法操作即可

锁续期之看门狗

所谓的看门狗是redisson用于自动延长锁有效期的实现机制。其本质是一个后台线程,用于不断延长锁key的生存时

要开启看门狗机制也很简单,只需要将加锁时使用lock()改为

tryLock()即可。

红锁

当在单点redis中实现redis锁时,一旦redis服务器宕机,则无法进行锁操作。因此会考虑将redis配置为主从结构,但在主从结构中,数据复制是异步实现的。假设在主从结构中,master会异步将数据复制到slave中,一旦某个线程持有了锁,在还没有将数据复制到slave时,master宕机。则slave会被提升为master,但被提升为slave的master中并没有之前线程的锁信息,那么其他线程则又可以重新加锁

消息幂等

在系统中当使用消息队列时,无论做哪种技术选型,有很多问题是无论如何也不能忽视的,如:消息必达、消息幂等等。

消息队列的消息幂等性,主要是由MQ重试机制弓I起的。因为消息生产者将消息发送到MQ-Server后,MQ-Server会将消息推送到具体的消息消费者。假设由于网络抖动或出现异常时,MQ-Server根据重试机制就会将消息重新向消息消费者推送,造成消息消费者多次收到相同消息,造成数据不一致。

消息防重表

解决思路与服务间幂等的防重表一致

redis

image-mhig.png

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

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

相关文章

prometheus入门(简单使用)

架构与组成 先上一张官网的架构图: Prometheus的构成: The Prometheus ecosystem consists of multiple components, many of which are optional: the main Prometheus server which scrapes and stores time series data(Prometheus serv…

基本数据类型及命令

String String 是Redis最基本的类型,Redis所有的数据结构都是以唯一的key字符串作为名称,然后通过这个唯一的key值获取相应的value数据。不同的类型的数据结构差异就在于value的结构不同。 String类型是二进制安全的。意思是string可以包含任何数据&…

三大低速总线之SPI

三大低速总线之SPI 文章目录 三大低速总线之SPI前言一、基本概念1.1 物理层1.2 协议1.3 传输过程 二、实战FLASH芯片2.1 SPI-Flash 全擦除实验2.1.1 程序设计 2.2 SPI-Flash 扇区擦除实验2.2.1 整体设计 2.3 SPI-Flash 页写实验2.3.1 操作时序 2.4 SPI_Flash 读数据实验2.4.1 时…

rasterization

在cityfm中有说道 Raster is a rasterization function that maps a closed polygon, represented as an ordered list of nodes, to a binary image 要在Python中实现一个将多边形映射到二值图像的光栅化函数,你可以按照以下步骤进行: 创建一个函数&…

网络安全 day3 --- WAFCDNOSS反向代理正向代理负载均衡

WAF(网页防火墙) 原理:Web应用防火墙,旨在提供保护 影响:常规Web安全测试手段会受到拦截 实验:Windows2022 IIS D盾 作用是防范网络安全入侵。 如下图,我们在网站目录下放一个简单的一句话木马…

JavaScript初级——文档的加载

1、浏览器在加载一个页面时,是按照自上向下的顺序加载的,读取到一行就运行一行,如果将 script 标签写到页面的上边,在代码运行时,页面还没有加载,页面没有加载DOM对象也没有加载,会导致无法获取…

一个计算勒让德多项式的HTML页面

效果如下 HTML代码 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>勒让德多项式</ti…

ZooKeeper体系架构、安装、HA

一、主从架构的单点故障问题 主从架构 Hadoop采用了主从架构&#xff0c;其中包含一个主节点和多个从节点。主节点负责管理整个集群的元数据、任务分配等关键任务&#xff0c;而从节点则负责执行具体的数据存储、计算等操作。 单点故障 在Hadoop主从架构中&#xff0c;主节点作…

Linux并发与竞争

一.概念 Linux 是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。 Linux 系统并发产生的原因很复杂,总结一下有下面几个主要原…

wegege

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

使用 setResponseStatus 函数设置响应状态码

title: 使用 setResponseStatus 函数设置响应状态码 date: 2024/8/25 updated: 2024/8/25 author: cmdragon excerpt: 通过 setResponseStatus 函数,你可以轻松地在 Nuxt.js 中设置响应的状态码。这不仅能帮助用户更好地理解发生了什么,还能在需要时显示自定义的错误页面。…

深入探讨与优化:常见排序算法的原理、实现与应用场景分析

目录 引言 排序算法的重要性 排序的基本概念 常见排序算法 插入排序 交换排序 选择排序 归并排序 分配排序 排序算法的实现与优化 总结与应用 引言 排序算法在计算机科学中占据了重要位置&#xff0c;它不仅仅是数据处理的基础&#xff0c;也是优化许多复杂算法的关…

初识redis:Zset有序集合

Set作为集合&#xff0c;有两个特点&#xff1a;唯一且无序。 Zset是有序集合&#xff0c;在保证唯一的情况下&#xff0c;是根据什么来排序的呢&#xff1f;排序的规则是什么&#xff1f; Zset中的member引入了一个属性&#xff0c;分数&#xff08;score&#xff09;&#…

初识redis:类型补充

Redis最关键的五个数据类型&#xff1a;String List Hash Set Zset 我们已经学完了&#xff0c;接下来我们再了解一下不是那么重要的&#xff0c;但是仍然有用的类型。 Stream Redis Stream 是 Redis 5.0 版本引入的一种新的数据类型&#xff0c;它提供了一种存储时间顺序消息…

《机器学习》—— OpenCV 对图片的各种操作

文章目录 1、安装OpenCV库2、读取、显示、查看图片3、对图片进行切割4、改变图像的大小5、图片打码6、图片组合7、图像运算8、图像加权运算 1、安装OpenCV库 使用pip是最简单、最快捷的安装方式 pip install opencv-python3.4.2还需要安装一个包含了其他一些图像处理算法函数的…

智慧交通——铁路检测相关数据集

数据集列表 智慧交通系列数据集——铁路相关数据集&#xff0c;用于轨道交通、自动化、计算机等专业结合深度学习、目标检测、语义分割、实例分割相关技术实现应用型研究&#xff01;&#xff01;&#xff01; 下载链接&#xff1a;私信获取 目前已更新数据集类型如下&#x…

cola_os学习笔记(下)

cola_os学习笔记&#xff08;上&#xff09; os文件夹 cola_device.c ​ .h放在.c的同层级。作者采用了字符设备注册的方式&#xff0c;在.h中可以看到设备属性。也就是把LED这些设备抽象&#xff0c;外面传入"LED1"这样的参数&#xff0c;使我联想到java的new一个…

GoWeb 设置别名和多环境配置

别名 vite.config.ts中添加代码如下即可 //设置别名resolve: {alias: {"": path.resolve(process.cwd(),"src"),//用替代src}}随后即可使用 配置多环境 vite.config.ts中添加代码如下 envDir: ./viteenv,//相对路径随后在项目根目录创建对应的viteenv…

Flink内存调优

Flink内存调优 JVM 我们知道Flink是基于JobManager和TaskManager管理和运行任务&#xff0c;而他们都是以Java进程的形式运行的&#xff0c;所以在了解 Flink 内存时&#xff0c;我们需要先了解一下Java运行时环境Java虚拟机(JVM) 。 JVM 是可运行 Java 代码的假想计算机 &a…

Visio如何对自画的“不规则封闭图案”填充颜色?

Visio如何对自画的“不规则封闭图案”填充颜色&#xff1f; 当我们想要画一个如下所示的不规则图案时&#xff0c;可以根据Visio工具栏中的曲线/直线等进行拼接组成。 但是&#xff0c;画出来的图形即使是组合后也不能直接填充颜色&#xff0c;这是因为软件并不能识别其为一个…