防止接口重复请求的后台限制

需求描述

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

问题分析

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

使用Redis incr解决问题

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

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

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

 

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;

}

/** 发送短信 */

......

/** 记录发送日志 */

......

如果需要登陆限制时,可以 通过所有接口的拦截器完成对是否重复请求进行拦截

public class LoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String servletPath = request.getServletPath();
        String reqPath = request.getRequestURI();
         //如果用户在 保存时,存在重复保存或更新的接口风险时,设置为十秒之内不可重复请求
        if(ExcludeMatcher.limitTime(servletPath)) {
            String token = request.getHeader(KEY);
            String redisKey = token + "_" + reqPath;
            long count = redis.increment(redisKey, 1); //对redisTemplate按照以上进行封装后的方法
            if(count == 1) {
                redis.expire(redisKey, 10, TimeUnit.SECONDS);
            }
            if(count > 1) {
                GwsLogger.info("outLimieTime", "十秒内请勿重复请求");
                errorResponse(response,"outLimieTime", "十秒内请勿重复请求");
                return false;
            }
        }
        return true;
    }
    /**
     *错误处理方法,
     * 也就是在返回对象response中存放json错误信息
     * @param response
     * @param code
     * @param message
     */
    private void errorResponse(HttpServletResponse response, String code, String message) throws IOException {
        RetResult result = RetResult.setFalse(code, message);
        OutputStream ps = response.getOutputStream();
        try {
            response.setContentType("application/json; charset=utf-8");
            JSONObject resultJson = JSON.parseObject(JSON.toJSONString(result));
            ps.write(resultJson.toString().getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                ps.flush();
                ps.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

测试

      使用Jmeter进行并发测试,同时发送200个线程请求,通过测试发现可避免并发请求问题 :

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值