java通过redis完成幂等性操作
4 幂等
产生 “重复数据或数据不一致”( 假定程序业务代码没问题 ),绝大部分就是发生了重复的请求,重复请求是指"同一个请求因为某些原因被多次提交"。导致这个情况会有几种场景:
- 微服务场景,在我们传统应用架构中调用接口,要么成功,要么失败。但是在微服务架构下,会有第三个情况『未知』,也就是超时。如果超时了,微服务框架会进行重试;
- 用户交互的时候多次点击。如:快速点击按钮多次;
- MQ 消息中间件,消息重复消费;
- 第三方平台的接口(如:支付成功回调接口),因为异常也会导致多次异步回调;
- 其他中间件/应用服务根据自身的特性,也有可能进行重试。
接口的幂等性实际上就是『接口可重复调用』,在调用方多次调用的情况下,接口『最终得到的结果是一致的』。
接口幂等:接口的幂等性实际上就是 接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。更准确的讲:多次调用接口对系统的产生的影响是一样的,即对资源的作用是一样的,但是返回值允许不同。
幂(mi)等概念
幂等 是一个数学与计算机科学概念,英文 idempotent [aɪˈdempətənt]。幂等这个词源自数学,幂等性是数学中的一个概念,常见于抽象代数中。表达的是N次变换与1次变换的结果相同;
幂等的数学概念
在数学中,幂等用函数表达式就是:f(x) = f(f(x))
如果在一元运算中,x 为某集合中的任意数,如果满足 f(x) = f(f(x)) ,那么该 f 运算具有幂等性。
绝对值运算 abs(a) = abs(abs(a)) 就是幂等性函数
如果在二元运算中,x 为某集合中的任意数,如果满足 f(x,x) = x,前提是 f 运算的两个参数均为 x,那么我们称 f 运算也有幂等性。
求最大值函数 max(x,x) = x 就是幂等性函数
幂等函数或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。幂等性本身是一个数学概念。在计算机的各个领域都有涉及和借用。
幂等的计算机概念
幂等表示一次和多次请求某一个资源应该具有同样的作用。
简单来说就是,如果同一个方法传入相同的参数情况下,调用一次和多次产生的效果是相同的,它就具有幂等性。
幂等的业务概念
幂等性问题在我们的开发中,分布式、微服务架构中是随处可见的:
日常开发中,需要考虑幂等性的场景:
- 前端重复提交:比如提交 form 表单时,如果快速点击提交按钮,就可能产生两条一样的数据。
- 用户恶意刷单:例如在用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,会使投票结果与事实严重不符。
- 接口超时重复提交:很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口的时候,为了防止网络波动等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
- MQ重复消费:消费者读取消息时,有可能会读取到重复消息。
- 因网路波动,可能会引起用户的重复请求
- 用户重复操作,用户在使用产品时可能会无意的触发多次下单多次交易,甚至因为没有响应而有意触发多笔交易;
- 应用使用了失败或超时重试机制(如Nginx重试、RPC重试或业务层重试等)
- 第三方平台的接口(如:支付成功回调接口),因为异常导致多次异步回调
- 中间件/应用服务根据自身的特性,也可能进行重试
- 使用浏览器后退按钮重复之间的操作,导致重复提交表单
- 使用浏览器历史记录重复提交表单
- 浏览器重复的HTTP请求
案例如下:
就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
- 场景1:支付场景
用户购买商品使用支付宝支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了。因此需要对于每一笔订单,操作多次,也只能扣一次钱。 - 场景2:一键三连
小破站有一个一键三连的功能,长按可以对up主进行激励,每个人对每个视频只有一个一键三连的机会。就算再喜欢某个视频,多次操作,也只能有一键三连一次。 - 场景3:统计DAU/MAU
DAU/MAU,又叫日活/月活,是用于反映网站、互联网应用或网络游戏的运营情况的统计指标。所以一个用户当天或者当月登录多次(或者达到某种活跃用户判断机制多次),也只能看作一个活跃用户,不能重复计算。
幂等作用
用户执行同一个功能多次【不知情】,保证多次执行结果一致的。
幂等场景
- 查询,select * from user where id=1,不会对数据产生任何变化,天然具备幂等性
- 新增,insert into user(userid, name) values(1, ‘a’)
如 userid 为主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性
如 userid 不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性,程序就要处理幂等性 - 修改,区分直接赋值和计算赋值
直接赋值,update user set point = 20 where userid = 1,不管执行多少次,point都一样,具备幂等性
计算赋值,update user set point = point + 20 where userid = 1,每次操作 point 数据都不一样,不具备幂等性 - 删除,delete from user where userid = 1,多次操作,结果一样,天然具备幂等性
上面场景中,我们发现新增没有唯一主键约束的数据,和修改计算赋值型操作都不具备幂等性
- 以『增删改查』四大操作来看,『删除』和『查询』操作天然是幂等的,没有( 或不在乎 )重复提交/重复请求问题。因此,幂等需求通常是用在『新增』和『修改』类型的业务上。
- 而『修改』类型的业务通过 SQL 改造和 last_upated_at 字段的结合,也可以实现幂等,不强求必须使用我们这里的 token 和去重表方案。
- 因此,幂等性的处理重点集中在『新增』型业务上。
保证幂等性的方案
前端幂等性的实现【不是可靠的】
按钮只操作一次
思路:一般是提交后把按钮置灰或loading状态,消除用户因为重复点击而产生的副作用,比如:添加操作,因为点击两次而产生两条记录
token机制【日常常用】
页面上允许重复提交,但要保证重复提交不会产生副作用,比如点击n次按钮只产生一条记录;
思路:进入页面时申请一个token,然后页面后面处理的所有请求带着这个token,根据token来避免重复操作
其他幂等实现方式:数据库的乐观锁、悲观锁
使用token+redis实现幂等
token + redis 的幂等方案,适用于绝大部分场景。这种方式的实现过程划分为两个阶段:申请token的阶段和业务操作阶段。
我们在分析业务的时候,哪些业务是存在幂等问题,就必须在执行业务前,先去获取 token,服务器会把 token 保存到 redis 中。
以注册为例:
第一阶段,在进入到注册页面之前,需要注册页面根据用户信息向后台控制器发送一次请求,申请token,后台控制器会将生成的token回送给客户端,同时也会将token存入redis缓存中,为第二阶段的注册业务使用
第二阶段:注册页面拿着申请到的token发起注册请求,控制器会获取客户端发送的token,然后去检查redis中有没有该token,如果存在,则表示是一次发出注册请求,则开始处理注册逻辑,处理完毕后并从redis中删除当前token;当重复请求时,检查缓存中的token不存在,则表示非法请求,服务器响应错误消息给客户端。
需要注意的是
- 这个幂等 token 本质上就是一个字符串,但是它必须是具有唯一性的。例如,UUID 。
- 这个幂等 token 要存取 redis 中。此时存入,是因为在未来需要再从 redis 中删除它
案例实现:注册
reg.html:用户进入页面时,就利用vue钩子函数向后台控制器发送请求,申请token
created(){//幂等性操作:所有用户第一次进入本网页时,为当前这个用户生成一个唯一标识,分别存在两端:1.redis 2.浏览器axios.get("/user/get-token").then(resp=>{let token=resp.data.data;//服务器分发的tokenlocalStorage.setItem("reg-token",token);})
}
UserController:生成token,保存到redis中,并将token响应客户端保存
@RequestMapping("/get-token")@ResponseBodypublic ResponseResult<String> getRegToken(){String token = UUID.randomUUID().toString();//1.redis保存tokenredisMapper.setKey(token,token);return new ResponseResult<>(200,"",token);}
reg.html:当用户提交注册请求时,除了需要携带用户信息,还需要将第一步申请的token一起发送给控制器
onSubmit() {//注册需要带x-token,没有token.axios.post("/user/register",this.user,{headers:{"reg-token":localStorage.getItem("reg-token")}}).then(resp=>{this.$message({showClose:true,message:resp.data.msg,type:'success'});if(resp.data.status===200){//成功,自动跳到登录页面location.href="/login.html";}})
},
UserController.java:处理请求前,需要验证客户端提交的token是否在redis中存在,存在就处理业务,否则响应错误消息
@PostMapping("/register")@ResponseBodypublic ResponseResult<Void> doReg(@RequestBody RegUserFo userFo, @RequestHeader("reg-token") String token){//验证当前这个用户是否已经注册过了,不能出现重复注册的情况/** 如果这个用户第一次来注册,redis保存以他手持token命名的标识,注册成功,redis删除表示* 如果这个用户第二次来注册,redis不存在与他手持token相关的记录,不允许再注册*/if(!redisMapper.existKey(token)){return new ResponseResult<>(505,"重复操作,请稍后再试....");}UserDto userDto=new UserDto();//将userFo的属性复制到userDtoBeanUtils.copyProperties(userFo,userDto);//输出语句log.info("userDto转换后的结果:{}",userDto);userService.register(userDto);//如果执行成功,删除redis保存凭证redisMapper.delKey("reg:token:"+token);return new ResponseResult<>(200,"注册成功");}
值得注意的是:Controller 在调用 Service 层( 执行业务逻辑 )之前,需要从 redis 中将前一个功能中存入 redis 的幂等 token 删除。删除成功,才能继续向下执行。
小细节:控制器会在哪些情况下,返回错误消息呢?
控制器处理完业务功能后,执行删除token操作,逻辑上起的是一个「判断」的作用:判断当前请求是否是合法的第一次请求。
那么上述图例中,造成token删除失败的情况,可能有以下三种:
非法情况一
没有申请过token,因此,没有携带幂等 token;
对于这种情况,因为缺少必要参数,所以 Controller 应该返回失败信息;
非法情况二
携带了幂等 token,但是不是上一步生成、返回的。例如,是自己伪造的;
对于这种情况,因为 redis 种没有这个伪造的幂等 token,所以会删除失败,因此,Controller 应该返回失败信息;
非法情况三
携带的是合法的幂等token,但是是重复请求。
对于这种情况,虽然 redis 中曾经有过这个幂等 token,但是因为已经被「用掉」了,所以,本次请求仍然应该返回失败信息。
扩展:如果用户注册成功,而执行redis删除幂等token时操作失败,怎么解决?
上述案例代码中,我们是先执行注册业务逻辑操作,再执行redis删除。如果出现上述请求,当我们再次提交注册请求时,就会出现重复注册的情况。怎么解决呢?
改良方案:我们可以先删除 redis ,删除成功后,而后再执行业务逻辑代码。
不过这种方案有一个缺点:无论业务逻辑执行成功还是失败,redis 中的幂等 token 都被删除掉了,这是,用户如果需要重试,那么必须重新获得幂等 token 。当然,你可以基于补偿的思维来改进这个方案:去 try-catch service 抛出的异常,一旦 service 执行失败,那么在 catch 代码块中再将幂等 token 重新存回 redis 。
参考代码如下:
@PostMapping("/do-register")
public ResponseResult<Map<String,String>> doRegister(@RequestBody @Validated RegUserFo userFo,BindingResult result,@RequestHeader("reg-token")String regToken){log.info("do-post用户提交的数据:{}", userFo);//判断这个注册请求是否重复提交了/*** redis+token保证幂等性,第二阶段* 1,验证redis中是否有幂等token* 2.如果没有,直接响应错误消息* 如果有,执行注册功能,注册执行完毕后,将幂等token删除掉*/if(!redisMapper.delKey("reg:"+regToken)){return new ResponseResult<>(5005,"请求次数过多,请稍后再试....");}try{RegUserDto userDto=new RegUserDto();BeanListUtils.copyProperties(userFo,userDto);userService.register(userDto);return new ResponseResult<>(2000,"注册成功");}catch(Exception e){//将“误删除”的幂等token再存入redis中redisMapper.setKey("reg:"+token,token);throw new RuntimeException(e);}
}
相关文章:
java通过redis完成幂等性操作
4 幂等 产生 “重复数据或数据不一致”( 假定程序业务代码没问题 ),绝大部分就是发生了重复的请求,重复请求是指"同一个请求因为某些原因被多次提交"。导致这个情况会有几种场景: 微服务场景,在…...
48 旋转图像
解题思路: \qquad 这道题同样需要用模拟解决,原地算法要求空间复杂度尽量小,最好为 O ( 1 ) O(1) O(1)。模拟的关键是找到旋转的内在规律,即旋转前后的位置坐标的变化规律。 \qquad 正方形矩阵类似洋葱,可以由不同大小…...
TDengine 签约青山钢铁,实现冶金全流程质量管控智能化
在不锈钢生产领域,企业面临着信息孤岛和数据分散的挑战,尤其在冶炼、连铸和轧钢等关键工艺以及能源管理上,这种现象导致生产要素(人、机、料、法、环)的分析管理模型难以全面、深入地实施。为了应对这一挑战࿰…...
__pycache__文件夹
__pycache__ 文件夹是 Python 在运行时自动生成的目录,用于存储已编译的字节码文件。这些字节码文件以 .pyc 扩展名结尾,用于加速程序的启动时间,因为不需要每次运行时都重新编译源代码。 主要特点 自动生成:__pycache__ 文件夹…...
利用 Local Data 导入文件到 OceanBase 的方法
背景 在很多传统方法中,数据的传输常依赖于csv格式。为了提高传输效率,属于同一张表的多个csv文件往往会被打包成gz文件进行传输。 当gz文件从上游传递到下游后,为了将其中的csv数据导入数据库,一种直接的做法是: 1…...
改变安全策略的五大实践
随着网络威胁形势的加剧,网络安全计划必须不断发展以保护组织的使命。 为了管理这种持续的网络安全发展,应遵循五项关键的安全计划变更管理实践: 1. 识别并吸引受安全风险影响的业务利益相关者 随着新的网络安全风险被发现,受影…...
在MacOS上安装MongoDB数据库
一、安装方法 1.1 安装包安装 首先,打开MongoDB 官网下载安装包,下载链接:https://www.mongodb.com/try/download/community。 根据自己的系统环境自行选择下载的版本。将下载好的 MongoDB 安装包解压缩,并将文件夹名改为 mon…...
负载均衡--会话保持失败原因及解决方案(五)
会话保持失败可能由多种因素导致,以下是一些主要原因及其解释: 一、服务器及网络问题 服务器故障: 服务器出现故障或不稳定,导致无法正确处理会话信息。这可能是由于硬件故障、网络问题或软件错误等引起的。网络问题:…...
24 Vue3之集成TailwindCSS
Tailwind CSS Tailwind CSS是一个由js编写的CSS 框架 他是基于postCss 去解析的 官网地址Tailwind CSS 中文文档 - Tailwind CSS - 只需书写 HTML 代码,无需书写 CSS,即可快速构建美观的网站。 | TailwindCSS中文文档 | TailwindCSS中文网 对于PostCSS…...
iOS OC 底层原理之 category、load、initialize
文章目录 category底层结构runtime 执行 category 底层原理添加成员变量 load调用形式系统调用形式的内部原理源码实现逻辑 initialize调用形式源码核心函数(由上到下依次调用)如果分类实现了 initialize category 底层结构 本质是结构体。struct _cat…...
另外知识与网络总结
一、重谈NAT(工作在网络层) 为什么会有NAT 为了解决ipv4地址太少问题,到了公网的末端就会有运营商路由器来构建私网,在不同私网中私有IP可以重复,这就可以缓解IP地址太少问题,但是这就导致私有IP是重复的…...
怎样用云手机进行TikTok矩阵运营?
在运营TikTok矩阵时,许多用户常常面临操作复杂、设备过多等问题。如果你也感到操作繁琐,不妨考虑使用云手机。云手机具备丰富的功能,能够帮助电商卖家快速打造高效的TikTok矩阵。接下来,我们将详细解析这些功能如何提升你的运营效…...
RTMP播放器全解析
一、RTMP 播放器概述 (一)RTMP 播放器的定义与作用 RTMP 播放器是一种专门用于播放采用 RTMP(Real Time Messaging Protocol)协议的视频流的工具。在当今的流媒体播放领域中,它扮演着至关重要的角色。RTMP 播放器能够…...
定期清洗ip是为了什么?怎么清洗iip
定期清洗IP(也称为“IP清理”)的目的是确保使用的IP池保持高效、可靠、安全,避免因使用无效或被封禁的IP导致网络操作失败。尤其在数据爬取、负载均衡等使用代理的场景中,定期清洗IP有助于提升整体的性能和数据抓取成功率。 定期…...
谁能给我一个ai现在无法替代画师的理由?
小白可做!全自动AI影视解说一键成片剪辑工具https://docs.qq.com/doc/DYnl6d0FLdHp0V2ll 如何看待现如今的AI绘画 哎呀玫瑰花来了,所有花式都要玩完了。 我相信大家在网上已经看过了太多惊为天人的AI绘画作品,有人抵制,有人支持&a…...
深入理解MySQL InnoDB中的B+索引机制
目录 一、InnoDB中的B 树索引介绍 二、聚簇索引 (一)使用记录主键值的大小进行排序 页内记录排序 页之间的排序 目录项页的排序 (二)叶子节点存储完整的用户记录 数据即索引 自动创建 (三)聚簇索引…...
语言的输入
编程语言提供最基本的输入输出,输入一个预期的数据也不是看起来那么简单,如下一一展开。 不同输入形式 C语言scanf提供格式串输入,程序员负责配置正确的格式,比如%d整型,%s为字符串。可能出现格式串和变量格式、个数不…...
2024年中国电子学会青少年软件编程(Python)等级考试(二级)核心考点速查卡
考前练习 2024年03月中国电子学会青少年软件编程(Python)等级考试试卷(二级)答案 解析 2024年06月中国电子学会青少年软件编程(Python)等级考试试卷(二级)答案 解析 知识点描述 …...
OpenCV系列教程二:基本图像增强(数值运算)、滤波器(去噪、边缘检测)
文章目录 一、基本图像增强(数值运算)1.1 加法 (cv2.add)1.1.1 图像与标量相加(调节亮度)1.1.2 图像与图像相加(两个图像shape要相同)1.1.3 图像的加权加法(渐变切换&…...
什么是文件完整性监控(FIM)
组织经常使用基于文件的系统来组织、存储和管理信息。文件完整性监控(FIM)是一种用于监控和验证文件和系统完整性的技术,识别用户并提醒用户对文件、文件夹和配置进行未经授权或意外的变更是 FIM 的主要目标,有助于保护关键数据和…...
告别C盘爆炸!手把手教你将Dify+Docker数据盘迁移到D盘(附.ENV配置详解)
告别C盘爆炸!手把手教你将DifyDocker数据盘迁移到D盘(附.ENV配置详解) Windows系统盘空间告急是许多开发者的共同烦恼,尤其是当你开始使用Docker部署AI开发环境时。C盘空间像被黑洞吞噬一样迅速消失,系统运行速度也随之…...
从Debezium到Flink RowData:手把手解析Flink CDC 2.3如何优雅处理MySQL的UPDATE事件
从Debezium到Flink RowData:深入解析Flink CDC 2.3处理MySQL UPDATE事件的机制 在实时数据处理的领域中,变更数据捕获(CDC)技术已经成为构建数据管道的核心组件。当MySQL数据库中的一条记录被更新时,如何准确捕获这一变更并将其高效地传递到下…...
Webots R2021a搭配Anaconda环境:从SSL报错到Python API调通的完整避坑指南
Webots R2021a与Anaconda环境深度整合:Python控制器开发全流程解析 当机器人仿真与Python开发环境相遇时,Webots和Anaconda的组合为研究者提供了强大工具链。然而,从环境配置到API调用的完整流程中,开发者常会遇到各种"坑点&…...
避坑指南:ESTUN Editor安装后,TP虚拟示教器bricks.ini配置文件到底在哪?
ESTUN Editor安装后TP虚拟示教器配置文件定位全解析 当你在工业机器人编程中同时安装了ESTUN Editor集成环境和独立TP软件包时,最让人头疼的问题莫过于找不到正确的bricks.ini配置文件。这个问题看似简单,却直接影响着虚拟示教器与机器人控制器的连接稳定…...
【QT】Layout布局间隙优化全攻略(参数调整与实战技巧)
1. 为什么你的QT界面总有"迷之缝隙"? 每次用QT做界面开发时,最让我抓狂的就是那些莫名其妙出现的空白间隙。明明已经按照设计稿精确设置了控件尺寸,但运行起来总会出现几个像素的偏差。后来我发现,这些间隙主要来自三个…...
tmux快速上手指南:3个核心命令与1个关键快捷键解析
1. 为什么你需要tmux? 如果你经常在服务器上工作,肯定遇到过这样的场景:正在跑一个耗时很长的任务,突然网络波动导致SSH连接断开,所有进程都被终止,几个小时的成果瞬间消失。这种时候,tmux就是你…...
4 大平台 “免费拿” 玩法大拆解,看完不踩坑
现在很多平台都有 “0元领东西” 的活动,玩法不一样,难度也差很多。今天用大白话对比拼dd、淘b、京d、全能锦鲤,简单易懂,看完就知道该选哪个。一、各平台免费拿怎么玩?1. 拼dd(老牌砍价)玩法&a…...
专利数据挖掘与商业价值转化:开源工具驱动的技术创新与决策变革
专利数据挖掘与商业价值转化:开源工具驱动的技术创新与决策变革 【免费下载链接】patents-public-data Patent analysis using the Google Patents Public Datasets on BigQuery 项目地址: https://gitcode.com/gh_mirrors/pa/patents-public-data 在数字化转…...
Crawl4AI浏览器配置文件创建与键盘交互处理终极指南:打造个性化爬虫身份
Crawl4AI浏览器配置文件创建与键盘交互处理终极指南:打造个性化爬虫身份 【免费下载链接】crawl4ai 🔥🕷️ Crawl4AI: Open-source LLM Friendly Web Crawler & Scrapper 项目地址: https://gitcode.com/GitHub_Trending/craw/crawl4ai…...
终极DBeaver多线程查询优先级控制:基于查询类型的动态调整指南
终极DBeaver多线程查询优先级控制:基于查询类型的动态调整指南 【免费下载链接】dbeaver DBeaver 是一个通用的数据库管理工具,支持跨平台使用。* 支持多种数据库类型,如 MySQL、PostgreSQL、MongoDB 等;提供 SQL 编辑、查询、调试…...
