1、场景介绍

1、对一个接口进行请求频率限制,同一个用户3秒钟只能请求一次,接口就是一个普普通通的接口,大致就是这样。
在这里插入图片描述
2、测试工具我会使用Apache AB,没有安装的可以参考这篇文章。传送门


AB命令:
-C: 设置请求的JSESSIONID
-n: 请求数
-c: 并发数

ab -C "JSESSIONID=7E41844F9D1D5CD6910C5B161C8D8934"  -n100 -c100 http://127.0.0.1:8081/tomorrow/pay/test

2、Redis分布式锁

允许我先吐槽一下,我看到网上好多写的是采用redis的set方法,我的天,想啥了,Redis速度是快,但是 SET key value 根本解决不了问题啊!!!
这个地方我们用Redis的分布式锁,其实这个分布式锁用的还是蛮多的。不是糟老头子不会骗你的,哈哈!
SETNX:若给定的 key 已经存在,则 SETNX 不做任何动作。设置成功,返回 1 ,设置失败,返回 0 。
先看一段:
在这里插入图片描述
我们这个地方基本上和上面的思路就差不多了。直接贴上代码:
需要注意一下对应的版本: SpringBoot版本

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.serializer.RedisSerializer;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;
import java.io.Serializable;

  /**
     * 创建 SETNX
     * @param key key
     * @param value value
     * @param expire 过期时间 秒
     * @return
     */
    public boolean setSetNX(String key, Serializable value, long expire) {
        Boolean result = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis (RedisConnection connection) throws DataAccessException {
                RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                Object obj = connection.execute(
                        "set",
                        keySerializer.serialize(key),
                        valueSerializer.serialize(value),
                        SafeEncoder.encode("NX"),
                        SafeEncoder.encode("EX"),
                        Protocol.toByteArray(expire)
                );
                return obj != null;
            }
        });
        return result;
    }
package com.tomorrow.controllers;

import com.tomorrow.commons.redis.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author Tomorrow
 * @date 2020/3/16 0:19
 */
@RestController
public class TestController {
    // 这里这是测试,我随便起的KEY,实际可以更改为用户的ID
    private final static String REDIS_KEY = "TEST_KEY";

    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/test")
    public Object callBackPayTabs(HttpServletRequest request) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HHmmss");
        String currentTime = simpleDateFormat.format(new Date());
        
        if (!redisUtil.setSetNX(REDIS_KEY, currentTime, 3)) {
            System.out.println("被拦截啦:" + simpleDateFormat.format(new Date()));
            return "FAIL";
        }
        System.out.println("成功啦:------------------------------" + simpleDateFormat.format(new Date()));
        return  "SUCCESS";
    }
}

请求数是:12000,并发数:500
在这里插入图片描述
在这里插入图片描述

3、session

package com.tomorrow.controllers;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author Tomorrow
 * @date 2020/3/16 0:19
 */
@RestController
public class TestController {
    // 这里这是测试,我随便起的KEY,实际可以更改为用户的ID
    private final static String REDIS_KEY = "TEST_KEY";

    @RequestMapping("/test")
    public Object callBackPayTabs(HttpServletRequest request) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HHmmss");
        String currentTime = simpleDateFormat.format(new Date());

        Object attribute = request.getSession().getAttribute(REDIS_KEY);
        if (attribute != null) {
            // 判断现在时间与上次存入session的时间,间隔是否小于3秒
            if ((Integer.valueOf(currentTime) - Integer.valueOf(String.valueOf(attribute))) < 3) {
                System.out.println("被拦截啦:" + simpleDateFormat.format(new Date()));
                return "FAIL";
            }
        }
        request.getSession().setAttribute(REDIS_KEY, currentTime);

        System.out.println("成功啦:------------------------------" + simpleDateFormat.format(new Date()));
        return  "SUCCESS";
    }
}

用户AB压力测试:
这个地方我配置的请求数是:50000,并发数:20000。请求太快了,不然不能展现这个效果,哈哈哈
在这里插入图片描述
在这里插入图片描述
OK, 那么通过session就可以处理这种情况。但是不推荐使用session

// 设置session过期时间,3秒钟,只对该session
request.getSession().setMaxInactiveInterval(3);

4、队列

后面补充吧!!!偷个懒



5、杂谈

1、AB指定Headers
-H: 指定Headers

ab -H "client:xxx" -H "token:ac7dbf4aa47f907e1c70409619f11c82" -H "uid:10004" -C "JSESSIONID=4EB88881AEF4079A59765B401EE94847"  -n100 -c100 http://127.0.0.1:8081/p123rrowOne

2、上面是我在本地压测的,redis是本地,如果是服务器的话,不要设置这么大,要循循渐进,一下压个50000的20000,服务器可能会受不住的
3、上面只是列举的列子,提供一个思路,实际中用的话还是可以优化的,比如加入AOP,这些都是可以的
4、如果做这种限制,其实最需要考虑的是时间的间隔,就比如说Redis的set方法,快是快,但是解决不了问题,既然是做限制,那么就需要考虑这个问题
5、如果用Redis的set方法测试的话,请求数和并发数可以设置大一点,就可以重现我上面所说的这种情况
6、然后也没有什么好说的了,是个java应该都会,就没有写的很详细,见谅见谅






提供一个群:807770565,欢迎各位进来尬聊
在这里插入图片描述

Logo

更多推荐