Redis实现资产巡检扫描

  • 前言
  • 一、需求一:限制扫描次数
    • 1.1 业务分析
    • 1.2 代码实现
  • 二、需求二:限制同一个位置同一时间只能有一个人扫描
    • 1.1 业务分析
    • 1.2 伪代码实现
    • 1.3 具体代码实现
    • 1.4 调用API
  • 总结

前言

最近小编在公司遇到这么一个需求,现在分享出来给大家一起讨论,用户需求:对设备资产巡检扫描做一个次数限制,就比如我们保安巡逻,每天固定巡逻几次,不能超出限制,还有很多列子,比如一个API接口限制请求次数,大概业务逻辑就是这样。


一、需求一:限制扫描次数

首先Redis的环境准备,这里小编不再多说,随便找我CSDN的博客,SpringBoot整合Redis的文章很多,都是基础功,我们是用RedisTemplate的方式来实现我们的这个需求。

1.1 业务分析

代码都是建立在需求上面的,所以我们再工作中,每个任务,每个功能都要先分析需求,再去实现具体过程,小编的思路经常都是这样,不会盲目的去写代码,至少你写的代码,不会全部是废弃的代码,而是不断的改进,这样的代码才是好的代码,因为可能今天这个接口的场景适用,明天就需要加入新的参数、新的业务逻辑等等,所以循循渐进才是代码之道。

我的分析:使用Redis的自增模式(计数器)来统计扫描次数,并限制请求次数。

计数器:是Redis 的原子性自增操作可实现的最直观的模式了,它的想法相当简单:每当某个操作发生时,向 Redis 发送一个 INCR 命令。

1.2 代码实现

使用Redis的计数器,就是INCR 命令实现统计扫描次数,假设这里5分钟之内扫描3次,当大于三次的时候,我们就给用户反馈,请5分钟后再来扫描,当然这里的5分钟,你也可以看做是5小时、5个周、5个月、5年后。

核心代码:

// 计数器 + 1 操作
redisTemplate.opsForValue().increment(key);
// 设置第一次扫描开始的过期时间
redisTemplate.expire(key,60 * 5,TimeUnit.SECONDS);

具体代码

@PostMapping("/test1/{id}/{key}")
public JSONObject test1(@PathVariable String id, @PathVariable String key){JSONObject jsonObject = new JSONObject();// 统计次数Long increment = 0L;Object time = redisTemplate.opsForValue().get(key);// 第一次扫描的时候,Redis没有值if(time == null){// 计数器 +1 操作increment = redisTemplate.opsForValue().increment(key);// 设置5分钟过期时间redisTemplate.expire(key,60 * 5,TimeUnit.SECONDS);jsonObject.put("code",2000);jsonObject.put("msg","扫描成功");return jsonObject;}else {// 不是第一次扫描Integer count = (Integer) time;System.out.println("count->>>"+count);// 是否大于3次if(count < 3){// 小于3次,每次计数器 +1 操作increment = redisTemplate.opsForValue().increment(key);jsonObject.put("code",2000);jsonObject.put("msg","扫描成功");return jsonObject;}else {// 否则5分钟内超出三次jsonObject.put("code",5000);jsonObject.put("msg","API调用限制3次");return jsonObject;}}
}

调用接口分析

这里我们不同的用户对同一个资产进行扫描,但是每个资产在5分钟之内最多只能扫描3次。

这里资产设备FAC-1被用户1开启了第一次扫描:

下面我们继续扫描,随便用用户2、或者用户3去扫描,当我们调用了三次请求之后:


可以看到我们扫描的次数到达限制,一旦到达3次以后,就不再往Redis的计数器里面写入了,立即给用户提示,五分钟后再来扫描,这里我们是针对资产设备来做的key,并不是针对具体的哪个人只能扫描几次,只是针对这个资产设备允许被扫描的最大的次数,ok到这里结束了,这个需求搞定,好像没多大问题。

二、需求二:限制同一个位置同一时间只能有一个人扫描

1.1 业务分析

针对上面的限制请求次数,已经搞定了,为什么还有下面加锁的步骤呢?

我的分析:如果同一个设备同一个时间同时被两个不同的人扫描了,怎么办?所以这个时候就引入Redis的锁,可以帮助我们解决这个问题。

Redis 锁主要利用 Redis 的 setnx 命令。

  1. 加锁命令:SETNX key value,当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。
  2. 解锁命令:DEL key,通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。
  3. 锁超时:EXPIRE key timeout, 设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。

1.2 伪代码实现

下面我们先来分析伪代码:

public void lock(){try {// 设置自动释放锁时间Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "value", Duration.ofSeconds(10));if (flag){// 获取锁成功// TODO 执行业务逻辑}else {//获取锁失败// TODO 快速失败,响应给客户端}}finally {// 最后一定要释放锁,以免造成死锁redisTemplate.delete("key");}
}

上述锁实现方式需要思考一些问题:

为什么锁要设置超时时间?

如果 setIfAbsent 成功,服务器挂掉、重启或网络问题等,锁没有设置超时时间变成死锁。

为什么释放锁?

一定记得在finally 块释放锁,以免造成死锁。

为什么要设置锁的value?

同一个key,被不同的人访问,锁是同一个无法区分当前锁住的对象,需要标识。

锁误解除了?

假设:用户A、B两个线程来尝试给lock加锁,用户A线程执行setIfAbsent先拿到锁(假如锁10秒后过期),用户B线程就在等待尝试获取锁,到这一步是没有问题的。继续往下执行,如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时用户A的线程的锁自动释放(删除key),用户B线程检测到lock这个key不存在,执行setIfAbsent命令也拿到了锁。但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致用户B线程的锁被用户A线程给释放了。

到这里我们只需要解决最后一个问题即可,锁误解除,遇到这样的场景怎么做呢?

设置不同的value来保证锁的唯一性。

1.3 具体代码实现

使用用户id和资产设备号来做Redis锁的key,这样就能保证锁的唯一性。

// 加锁,防止同时扫描
String LOCK_KEY = "lock";
// id->>>用户id  key->>>资产设备号
String LOCK_VALUE = id + key;// 假设设置30秒自动释放锁
Boolean b = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, LOCK_VALUE, Duration.ofSeconds(30));//释放锁
if (LOCK_VALUE.equals(redisTemplate.opsForValue().get(LOCK_KEY))){redisTemplate.delete(LOCK_KEY);
}

完整代码如下

@PostMapping("/test2/{id}/{key}")
public JSONObject incr(@PathVariable String id,@PathVariable String key) throws InterruptedException {JSONObject jsonObject = new JSONObject();// 加锁,防止同时扫描String LOCK_KEY = "lock";// id->>>用户id  key->>>资产设备号String LOCK_VALUE = id + key;try {// 假设设置30秒自动释放锁Boolean b = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, LOCK_VALUE, Duration.ofSeconds(30));if (b){//获取锁成功//执行业务逻辑// 模拟业务超时Thread.sleep(20000);// 计数器keyString idKey = key;Long increment = 0L;Object time = redisTemplate.opsForValue().get(idKey);if(time == null){increment = redisTemplate.opsForValue().increment(idKey);redisTemplate.expire(idKey,60*5,TimeUnit.SECONDS);jsonObject.put("code",2000);jsonObject.put("msg","扫描成功");return jsonObject;}else {Integer count = (Integer) time;System.out.println("count->>>"+count);if(count < 3){increment = redisTemplate.opsForValue().increment(idKey);jsonObject.put("code",2000);jsonObject.put("msg","扫描成功");return jsonObject;}else {jsonObject.put("code",5000);jsonObject.put("msg","API调用限制3次");return jsonObject;}}}else {//获取锁失败//快速失败,响应给客户端jsonObject.put("code",5001);jsonObject.put("msg","当前设备扫描中!");return jsonObject;}}finally {//释放锁if (LOCK_VALUE.equals(redisTemplate.opsForValue().get(LOCK_KEY))){redisTemplate.delete(LOCK_KEY);}}}

这里我们来模拟业务超时,假设业务超时,其他线程需要获取锁,就需要等待:

// 模拟业务超时 睡眠20秒钟
Thread.sleep(20000);

1.4 调用API

先模拟A用户扫描资产设备DT1,并且另外一个用户B也去扫描DT1,这里可以写多线程模拟,同时请求接口,为了方便测试,我们使用休眠时间来模拟,也是一样的效果,当然测试并发的工具也很多,小编的文章中也有说到过,大家可自行尝试。

用户1扫描中,睡眠20秒中,此时用户2去扫描设备获取锁。

当扫描到达3次以后,限制请求:

ok,结束!!!!!!!!!!!!!!!!!!!!!!!!!

总结

上面需要注意的是锁的超时时间设置,需要把握好,比如业务超时,业务执行时间大于了锁的超时时间,通俗的来说就是:锁过期了,业务还没执行完(针对业务逻辑复杂,消耗时长较大的情况),这种情况又怎么解决呢?后面我们会继续来探讨这个问题。

必看企业级Redis锁资产巡检扫描业务场景实现(加锁限制扫描次数)相关推荐

  1. 【超全必看】Redis基础入门学习笔记(附示例代码)

    Redis简介 许多网站在海量用户访问的高并发情况下出现崩溃问题,根本原因是关系型数据库. 关系型数据库的缺点 性能瓶颈:磁盘IO性能低下 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群 解决思路 ...

  2. 面试前必看:Redis 和 Memcached 的区别

    Redis 的作者 Salvatore Sanfilippo 曾经对这两种基于内存的数据存储系统进行过比较: Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构 ...

  3. laravel mysql sum查询并排行_必看!PHP常见面试题——MySQL篇(二)

    接上期:<必看!PHP常见面试题--MySQL篇(一)> 11.MySQL的默认事务隔离级别是? 读未提交(RU): 一个事务还没提交时, 它做的变更就能被别的事务看到. 读提交(RC): ...

  4. 借助Redis锁,完美解决高并发秒杀问题

    欢迎关注方志朋的博客,回复"666"获面试宝典 场景:一家网上商城做商品限量秒杀. 1 单机环境下的锁 将商品的数量存到Redis中.每个用户抢购前都需要到Redis中查询商品数量 ...

  5. 小白必看 | VRay Next 太阳光如何穿过窗外的背景照射到室内?

    作者:活力网 大家好!今天将结合一个场景案例与大家分享:VRay Next 太阳光如何穿过窗外的背景照射到室内.详细图文讲解,零基础小白必看. 今天的分享将结合以下场景进行操作 模型来源:www.iM ...

  6. 99. 中高级开发面试必问的Redis,看这篇就够了

    中高级开发面试必问的Redis,看这篇就够了! 一.概述 二.数据类型 STRING LIST SET HASH ZSET 三.数据结构 字典 跳跃表 四.使用场景 计数器 缓存 查找表 消息队列 会 ...

  7. 分布式为什么一定要有高可用的分布式锁?一线大厂必看!

    " 现在面试都会聊到分布式系统,其中不免谈到分布式锁这块的知识,今天就来聊聊如何设计高可用的分布式锁. 作者:陈于喆,来自:51CTO技术栈 分布式锁定义 分布式锁在分布式环境下,锁定全局唯 ...

  8. 鸿蒙一号指纹锁,【诉说实情】火车头战狼一号家用防盗门指纹锁怎么样?新手必看内情...

    [诉说实情]火车头战狼一号家用防盗门指纹锁怎么样?新手必看内情有猫眼屏幕 顺丰包邮 包安装 我打算入手,选了很久才看中的,不知道质量怎么样,好不好用,所以来这里请教下大家,希望买过的网友分享一下,客观 ...

  9. Redis 互联网开发必看

    一,搭建环境 Windows环境搭建 redis-server.exe --service-install redis.windows.conf [–loglevel verbose] – 服务部署 ...

最新文章

  1. 骚操作 !用 Python 偷偷抓取女朋友的行踪(女朋友在哪里)
  2. Dart Way 1
  3. 为什么要叫python-为什么叫Python
  4. python文件输出-python将控制台输出保存至文件的方法
  5. Spring boot的第一个demo
  6. 使用Kubespray部署生产可用的Kubernetes集群(1.11.2)
  7. java学习(37):二维数组
  8. C++的多态原理和实现
  9. tongweb php,TongWeb服务器部署
  10. 钉钉微应用怎么进入_海目星激光张荣:激光焊接在锂电池生产应用中越来越多...
  11. 基于java题库及试卷管理模块的设计与开发(含源文件)
  12. vs2005下载|中文版|官方
  13. 数字金额转换成中文大写金额的函数
  14. 基于STM32设计的车库监控报警系统
  15. 零基础学习大数据难不难?小白如何上手大数据?
  16. LTE-5G学习笔记8---PRACH参数规划
  17. Harris角点检测,及其Matlab和OpenCV实现
  18. 电脑远程登录控制Android手机-Webkey For Android使用教程
  19. 什么是生化分析中的反应曲线?
  20. Linux 系统查看服务器SN序列号以及服务器型号

热门文章

  1. 信息学奥赛一本通 2044:【例5.12】回文字串
  2. Lost Array(CF-1043B)
  3. 高精度减法(洛谷-P2142)
  4. nodejs ftp文件服务器,node.js自动上传ftp的脚本分享
  5. mysql安装权限_mysql 新安装时的权限
  6. python中curve fit_scipy.optimize.curve_fit函数用法解析
  7. php编写个人所得税单元测试,php趣味编程-php求个人所得税
  8. 浅谈Opencl四大模型之Platform model
  9. [Unity] 战斗系统学习 3:FlowCanvas 中的 Input System
  10. hls二次加密 m3u8_将视频转换为m3u8,使用AES-128的方式加密HLS真的有效吗?