业务描述

最近在项目中遇到个问题,短信发送的并发请求漏洞:业务需求是需要限制一个号码一分钟内只能获取一次随机码,之前的实现是短信发送请求过来后,先去数据库查询发送记录,根据上一次的短信发送时间和当前时间比较,如果时间差小于一分钟,则提示短信获取频繁,如果超过一分钟,则发送短信,并记录短信发送日志。

问题分析

短信发送是一个很敏感的业务,上面的实现存在一个并发请求的问题,当同一时间有很多请求过来时,同时去查库,同时获取到上一次发送时间没有,或者已超过一分钟,这时候就会重复发送短信了。

使用Redis解决问题

Redis incr 可以实现原子性的递增,可应用于高并发的秒杀活动、分布式序列号生成等场景。这里我使用它来计数实现一分钟内只接受一次请求。

我们在后台接到短信发送请求后,使用Redis的incr设置一个递增KEY(KEY由固定字符串+手机号码组成),并判断该KEY的数值,如果等于1,说明是第一个请求,我们将该KEY值有效期设置为一分钟;如果该KEY的数值大于1,说明是1分钟内的多次请求,这时我们直接返回短信获取频繁,代码如下:

String redisKey = "SMS_LIMIT_" + smsPhone;
long count = redisTemplate.opsForValue().increment(redisKey, 1);
if (count == 1) {
//设置有效期一分钟
redisTemplate.expire(redisKey, 60, TimeUnit.SECONDS);
}
if (count > 1) {
resultMap.put("retCode", "-1");     resultMap.put("retMsg", "每分钟只能发送一次短信");
outPrintJson(resultMap);
return;
}
/** 发送短信 */
......
/** 记录发送日志 */
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用java锁机制解决问题

我们知道Java有一个锁机制,使用synchronized关键字可以将对象或代码块锁定,当有很多请求过来时,第一个获得锁,后面的请求需要等第一个处理完后才能继续,就好像排队上厕所。代码如下: 
1、我们创建一个发短信的工具类,使用单例模式(单例模式的兼容效率和并发的写法,参照我前面的博客【善用设计模式-单例模式】)

private static byte[] LOCK = new byte[0];
private static SMSendUtil smSendUtil;
private SMSendUtil(){};
/*** @Description:单例 (防并发)* @param:@return   * @return:SMSendUtil   * @throws:* @author:pengl* @Date:2016年8月17日 下午4:16:08*/public static SMSendUtil getInstance(){if(smSendUtil != null)return smSendUtil;synchronized (LOCK) {if(smSendUtil == null)smSendUtil = new SMSendUtil();}return smSendUtil;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2、编写发短信的方法,并在方法上加synchronized关键字

/*** 发送登陆验证短信(使用全局锁synchronized避免并发请求)*/public synchronized Map<String, Object> sendSms(String smsPhone) {/** 查询最近一次发送时间 **/....../** 一分钟时间限制判断 **/....../** 发送短信 **/....../** 记录发送日志 */......}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

java锁机制优化

在单例模式下,通过在方法上加synchronized关键字,是可以解决并发问题的,但是这种全局锁,性能非常差,如果有100个用户同时获取短信随机码,这100个用户只能排队一个一个发送短信。我们可以使用更细小颗粒的锁来控制并发问题,这里使用手机号码做为颗粒纬度,优化代码如下: 
1、根据手机号码获取相应的锁对象

private static ConcurrentHashMap<Long, Byte[]> lockerStore = new ConcurrentHashMap<Long, Byte[]>();private static Object getPhoneNumberLock(long phone) {lockerStore.putIfAbsent(phone, new Byte[]{});Byte[] ret = lockerStore.get(phone);return ret;
}/*** 发送登陆验证短信(使用号码颗粒级锁synchronized避免并发请求)*/public Map<String, Object> sendSms(String smsPhone,boolean flag) {synchronized (getPhoneNumberLock(Long.parseLong(smsPhone))) {/** 查询最近一次发送时间 **/....../** 一分钟时间限制判断 **/....../** 发送短信 **/....../** 记录发送日志 */......}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

功能测试

使用Jmeter进行并发测试,同时发送200个线程请求,通过测试发现,以上3种方式均可避免并发请求问题 
 
查看结果,只有第一个请求发送了短信,剩余199个请求均返回失败 
 

性能测试

使用Jmeter进行并发测试,同时发送200个线程请求,查看聚合报告: 
1、使用Redis计数器方式 
 
2、使用全局synchronized 方式 
 
3、使用号码颗粒纬度synchronized 方式 

通过测试发现,使用Redis计数器方式,并发性能最好;由于测试是使用相同号码进行测试的,第2、3两种方式测试结果差不多,实际应用中,第三种的性能肯定要高于第二种的。不过总的来说,使用锁机制性能是比较差的。

使用Redis计数器防止并发请求相关推荐

  1. Redis原子计数器incr,防止并发请求

    一.前言 在一些对高并发请求有限制的系统或者功能里,比如说秒杀活动,或者一些网站返回的当前用户过多,请稍后尝试.这些都是通过对同一时刻请求数量进行了限制,一般用作对后台系统的保护,防止系统因为过大的流 ...

  2. java redis计数器_Redis原子计数器incr,防止并发请求

    一.前言 在一些对高并发请求有限制的系统或者功能里,比如说秒杀活动,或者一些网站返回的当前用户过多,请稍后尝试.这些都是通过对同一时刻请求数量进行了限制,一般用作对后台系统的保护,防止系统因为过大的流 ...

  3. linux单线程处理多个请求,redis是单线程的,如何处理并发请求?

    疑问: redis是单线程的,如何并发处理多个请求? 下面是我个人的理解. 答案是:使用操作系统的多进程机制.也就是我们常说的,多路复用API,多路复用API本质上是对操作系统多路复用功能的封装. 什 ...

  4. PHP中利用redis实现消息队列处理高并发请求思路详解

    在电商活动中,常常会出现高并发的情况,例如很多人同时点击购买按钮,以至于购买人数超出了库存量,这是一种非常不理想的状况,因此,我们在PHP开发中就会引入消息队列来解决这种高并发的问题. 当用户点击按钮 ...

  5. Redis 的高并发实战:抢购系统 --浅奕

    简介: 主要内容: 一.IO 模型和问题 二.资源竞争与分布式锁 三.Redis 抢购系统实例 主要内容: 一.IO 模型和问题 二.资源竞争与分布式锁 三.Redis 抢购系统实例 一.IO 模型和 ...

  6. java redis计数器_使用Redis原子计数器incr实现限速器功能

    点击上方☝ Java编程技术乐园,轻松关注~ 及时获取有趣有料的 技术文章 做一个积极的人编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 一.前言 在一些对高并发请求有限制的系统或者功能 ...

  7. Redis - 高性能 + 高并发

    目录 1.关系型数据库和非关系型数据库的区别 ? 1. KV型NoSql(代表----Redis) 2. 搜索型NoSql(代表--ElasticSearch ) 3. 文档型NoSql(代表---- ...

  8. php 使用redis锁限制并发访问类

    1.并发访问限制问题 对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功. 例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制 ...

  9. php 和mysql实现抢购功能_php处理抢购类功能的高并发请求

    本文以抢购.秒杀为例.介绍如何在高并发状况下确保数据正确. 在高并发请求下容易参数两个问题 1.数据出错,导致产品超卖. 2.频繁操作数据库,导致性能下降. 测试环境 Windows7 apache2 ...

  10. IIS处理并发请求时出现的问题及解决

    一个ASP.NET项目在部署到生产环境时,当用户并发量达到200左右时,IIS出现了明显的请求排队现象,发送的请求都进入等待,无法及时响应,系统基本处于不可用状态.因经验不足,花了很多时间精力解决这个 ...

最新文章

  1. Latex 算法过长 分页显示方法
  2. 强/若类型语言 动/静态语言
  3. 使用 Nexus3镜像搭设私有仓库(Bower 、Docker、Maven、npm、NuGet、Yum、PyPI)
  4. beego原生mysql查询_Beego基础学习(五)Golang原生sql操作Mysql数据库增删改查(基于Beego下测试)...
  5. 解析Windows 2000/XP进程工作集
  6. sql datetime字段 取年月日_写一手好SQL,你该从哪里入手?
  7. anaconda linux安装_deepin系统启动Anaconda时图形界面出问题
  8. : Attribute xmlns was already specified for element web-app.
  9. 原子操作、互斥锁、读写锁
  10. 企业级 SpringCloud 教程 (五)路由网关(zuul)
  11. Javascript设计模式(五)代理模式
  12. 预处理函数在app和蓝图级别的不同使用
  13. Java进阶:Dubbo
  14. 运放放大倍数计算公式_16个问题讲透了运算放大器基础的知识点
  15. unbuntu 安装docker
  16. 缘分,有时就在一刹那
  17. 别让拖延毁掉你自己 | 《拖延心理学》全书解读
  18. 苹果手机上音乐播放的问题
  19. 第四届蓝桥杯JavaC组国(决)赛真题
  20. 微信小程序电商项目商品详情页开发实战之数据绑定与事件应用

热门文章

  1. intellij idea设置代码提示不区分大小写
  2. JVM内存区域(一)
  3. 关于鼓励软件产业和集成电路产业发展有关税收政策问题的通知
  4. UVa 10003 Cutting Sticks(区间DP)
  5. Xcode5 取消项目ARC,或者单个类ARC切换
  6. codeforces590b//Chip 'n Dale Rescue Rangers//Codeforces Round #327 (Div. 1)
  7. 《文献管理与信息分析》速看提问
  8. Scrum项目6.0 和8910章读后感
  9. 【记录】AutoMapper Project To OrderBy Skip Take 正确写法
  10. 转:linux中select()函数分析