Redis如何实现原子性自增自减

一、背景        

假设有一个需求,包含简单的两个步骤:

  1. 第一步是用户获取验证码,检验验证码成功后跳转到表单填写页面;
  2. 第二步是用户填写表单并提交申请

为了防止用户跳过第一步直接提交申请,我们采取了以下策略:

  1. 在验证码验证成功后,我们将用户Id作为Key,剩余可申请次数作为Value,将这个键值对存储在Redis中并设置过期时间;
  2. 在收到用户提交的申请请求后,首先检查Redis中该用户Id对应的Value是否存在且大于0。我们将申请次数减一,并允许用户提交申请;如果不满足条件,则直接抛出异常。

二、increment

针对以上策略的第一步,直接上伪代码

1.检验验证码方法如下:

//以下为伪代码,重点关注redis的调用逻辑//校验验证码方法
public void checkCode(String code){//校验验证码逻辑...//校验通过redisBuryingPoint(userId);}

以上代码,略过了校验验证码的逻辑,在验证码校验成功后,调用了'redisBuryingPoint(userId)'方法进行埋点,其中userId为用户Id。

2.redisBuryingPoint方法如下:

/*** redis埋点* @param userId 用户id
*/
public void redisBuryingPoint(String userId) {int value = redisService.increment(userId, 1 * 60 * 60L);logger.info("用户[{}]验证成功共计[{}]次", userId, value);
}

3.redisService#increment(String key, Long expirationTimeInSeconds) 方法如下:

/*** 自增  * @param key 要加一的键* @param expirationTimeInSeconds 过期时间
*/
public int increment(String key, Long expirationTimeInSeconds) {// +1 操作int result = redisTemplate.opsForValue().increment(key).intValue();// 设置过期时间redisTemplate.expire(KEY, expirationTimeInSeconds, TimeUnit.SECONDS);//返回return result;
}

4.小结

以上就完成了redis埋点的过程。即使同一个用户在同一时间进行多个验证码的验证操作,即在并发场景下多个请求同时调用'increment() '方法时,RedisTemplate 会自动处理并发操作,确保操作的原子性一致性

三、decrement

针对以上策略的第二步,伪代码如下

1.申请提交预校验方法如下:

/*** 预校验:校验短信验证成功的次数* @param userId 用户Id
*/
private void preCheck(String userId) {Long result = redisService.decrement(userId);if (result != null) {// 成功递减logger.info("发起申请成功,用户[{}]验证成功次数剩余[{}]次", userId, result);} else {// 值不存在,无需递减操作logger.error("发起申请失败,用户[{}]验证成功次数不足", userId);throw new RuntimeException("发起申请失败,请先进行短信验证!")}
}

在上述代码中,我们直接调用redisService的 decrement() 方法,我们检查返回的结果是否为 null,如果不为 null,表示递减操作成功,并打印递减后的剩余次数。如果结果为 null,表示值不存在,没有进行递减操作,打印错误日志并抛出异常。

2.redisService#decrement(String key) 方法如下:

private RedisScript<Long> decrementScript;public RedisService() {//定义 Lua 脚本this.decrementScript = new DefaultRedisScript<>("if redis.call('exists', KEYS[1]) == 1 and tonumber(redis.call('get', KEYS[1])) > 0 then " +"   return redis.call('decr', KEYS[1]) " +"else " +"   return nil " +"end",Long.class);}/*** 自减 * @param key 要减一的键* 
*/
public Long decrement(String key) {return redisTemplate.execute(decrementScript, Collections.singletonList(key));    
}

在上述代码中,我们使用 Lua 脚本来执行递减操作。脚本首先检查键是否存在,如果存在则执行递减操作,如果不存在则返回空。在 Java 代码中,我们使用 DefaultRedisScript 来定义 Lua 脚本,并通过 redisTemplate.execute() 方法来执行脚本。

3.小结

以上就完成了redis消费的过程。即使同一个用户在同一时间提交多次申请,即在并发场景下多个请求同时调用'decrement()'方法时,Redis 执行 Lua 脚本,会将整个脚本作为一个原子操作进行执行。确保操作的原子性一致性

四、其他

        当 Redis 的键不存在时,使用 opsForValue().increment() 方法会将键的值初始化为 1,并执行递增操作;使用 opsForValue().decrement() 方法会将键的值初始化为 -1,并执行递减操作。

        所以,如果在调用 increment() 方法时,键对应的值不存在,它将被赋值为 1;如果在调用 decrement() 方法时,键对应的值不存在,它将被赋值为 -1。

    

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

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

相关文章

9 君正音频采集的封装

概述 在IPC中,需要将设备端的声音采集后进行编码,然后发送给客户端或者监控平台。君正SDK中的音频功能包括5个模块,分别是:音频输入、音频输出、回声消除、音频编码和音频解码。其中,音频输入和音频输出存在设备(Device)和通道(Channel)的概念。一个MIC被认为是一个录…

【Java基础教程】(四十四)IO篇 · 上:解析Java文件操作——File类、字节流与字符流,分析字节输出流、字节输入流、字符输出流和字符输入流的区别

Java基础教程之IO操作 上 &#x1f539;本节学习目标1️⃣ 文件操作类&#xff1a;File2️⃣ 字节流与字符流2.1 字节输出流&#xff1a;OutputStream2.2 字节输入流&#xff1a;InputStream2.3 字符输出流&#xff1a;Writer2.4 字符输入流&#xff1a;Reader2.5 字节流与字符…

力扣27 26 283 844 977 移除数组

给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的…

电脑新装系统优化,win10优化,win10美化

公司发了新的笔记本&#xff0c;分为几步做 1.系统优化,碍眼的关掉。防火墙关掉、页面美化 2.安装必备软件及驱动 3.数据迁移 4.开发环境配置 目录 目录复制 这里写目录标题 目录1.系统优化关掉底部菜单栏花里胡哨 2.安装必备软件及驱动新电脑安装360 1.系统优化 关掉底部菜单…

WPF MVVM之INotifyPropertyChanged接口的几种实现方式

序言 借助WPF/Sliverlight强大的数据绑定功能&#xff0c;可以比实现比MFC&#xff0c;WinForm更加优雅轻松的数据绑定。但是在使用WPF/Silverlight绑定时&#xff0c;有件事情是很苦恼的&#xff1a;当ViewModel对象放生改变&#xff0c;需要通知UI。我们可以让VM对象实现INot…

es通过rest接口_search、_delete_by_query查询与删除数据

1、rest接口查询数据 rest查询: http://localhost:9200/index_name/_search 查询表达式&#xff1a; {"query": {"wildcard": {"accountID": {"value": "v*"}}} }postman请求截图&#xff1a; 2、使用Rest接口删除数据 …

re学习(23)BUUCTF 刮开有奖(中间变量的获取)

INT_PTR __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM a3, LPARAM a4) {const char *v4; // esiconst char *v5; // ediint v7[2]; // [esp8h] [ebp-20030h] BYREF 虽然看名称不连续&#xff0c;但是通过看偏移地址&#xff0c;可知&#xff0c;这些变量在内存中是连续的&…

iOS--属性关键字

定义 chat&#xff1a; 在iOS开发中&#xff0c;属性关键字是用于声明类的属性&#xff08;实例变量&#xff09;的修饰符。属性关键字可以影响属性的访问权限、内存管理和生成相关的getter和setter方法。 属性关键字有哪些&#xff1f; 分类属性关键字原子性atomic、nonato…

Android ConstraintLayout使用攻略

原文链接 Android ConstraintLayout使用攻略 ConstraintLayout是新一代的布局&#xff0c;它汲取了众家之长&#xff0c;把布局的概念进行了大统一&#xff0c;灵活且强大&#xff0c;基本上可以干掉以前所有的常用布局&#xff08;LinearLayout, RelativeLayout和FrameLayout…

SD-WAN的ZTP功能是不用企业再配置了吗?

Zero-Touch Provisioning (全自动服务开通)是SD-WAN方案中最具代表性、也是最能打动客户的功能之一。 ZTP功能是完全不用企业用户进行配置了吗&#xff1f; 其实ZTP&#xff08;Zero Touch Provisioning&#xff09;并不是完全不需要配置&#xff0c;而是可以通过预定义的配置和…

【重点:单例模式】特殊类设计

请设计一个类&#xff0c;只能在堆上创建对象 方式如下&#xff1a; 将构造函数设置为私有&#xff0c;防止外部直接调用构造函数在栈上创建对象。向外部提供一个获取对象的static接口&#xff0c;该接口在堆上创建一个对象并返回。将拷贝构造函数设置为私有&#xff0c;并且…

vue中使用tesseract实现OCR/文字识别(识别图片中的文字)

文章目录 tesseract在线使用npm安装依赖&#xff1a;页面引入&#xff1a;js方法&#xff1a; tesseract离线使用克隆仓库拷贝 worker.min.js和tesseract-core.wasm.jsindex.html 引入tesseract.min.js下载语言包文件不上传文件服务器js方法优化 tesseract官网地址&#xff1a;…

webrtc QOS方法二.4(flexfec 实现可优化点)

一、冗余报文和媒体报文组织结构优化点 以单帧10个媒体报文&#xff0c;冗余度20%为例。这里webrtc输出要有10个媒体包2个冗余包。webrtc输出的报文序列如下&#xff1a; 代码实现如下&#xff1a; UlpfecGenerator::AddPacketAndGenerateFec&#xff1a;攒够足够的帧 Forwar…

【Kafka源码走读】Admin接口的客户端与服务端的连接流程

注&#xff1a;本文对应的kafka的源码的版本是trunk分支。写这篇文章的主要目的是当作自己阅读源码之后的笔记&#xff0c;写的有点凌乱&#xff0c;还望大佬们海涵&#xff0c;多谢&#xff01; 最近在写一个Web版的kafka客户端工具&#xff0c;然后查看Kafka官网&#xff0c;…

Android 屏幕适配各种宽高比的手机

由于android 手机的屏幕宽高比样式太多了&#xff0c;在设计UI时&#xff0c;很多时候&#xff0c;会因为宽高比&#xff0c;分辨率不同会有展示上的差异。 我是这样解决的 在activity的onCreate方法前&#xff0c;调用&#xff1a; fun screenFit(context: Context) {val me…

系统架构设计师-软件架构设计(2)

目录 一、基于架构的软件开发方法&#xff08;ABSD&#xff09; 1、架构需求 1.1 需求获取 1.2 标识构件 1.3 架构需求评审 2、架构设计 2.1 提出架构模型 2.2 映射构件 2.3 分析构件的相互作用 2.4 产生架构 2.5 设计评审 3、架构文档化 4、架构复审 5、架构实现 5.1 分析与…

获取大疆无人机的飞控记录数据并绘制曲线

机型M350RTK&#xff0c;其飞行记录文件为加密的&#xff0c;我的完善代码如下 gitgithub.com:huashu996/DJFlightRecordParsing2TXT.git 一、下载安装官方的DJIFlightRecord git clone gitgithub.com:dji-sdk/FlightRecordParsingLib.git飞行记录文件在打开【我的电脑】&am…

结构型设计模式之装饰器模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

Java设计模式-组合模式

组合模式 1.组合模式含义 组合模式&#xff0c;将对象组合成树形结以表示部分-整体的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性 组合模式可以简单的理解为将各个单独的小对象&#xff0c;通过组合模式&#xff0c;组合成一个大的对象&#xff0c;这个…

LiveNVR监控流媒体Onvif/RTSP功能-支持无人机、IPC等设备RTMP推流转码分发H5无插件播放也支持GB28181输出

LiveNVR支持无人机、IPC等设备RTMP推流转码分发H5无插件播放也支持GB28181输出 1、无人机推流转国标2、获取RTMP推流地址2.1、RTMP推流地址格式2.2、推流地址示例 2、设备RTMP推流3、配置拉转RTMP3.1、直播流地址格式3.2、直播流地地址示例3.3、通道配置直播流地址 4、配置级联…