目录

服务端对接口限流

服务端对接口限流

安全发现后端某个接口可以外网中访问,可能存在被刷,就加上了接口限流。

接口限流有很多方式,1.前后端验证码限流,2.单个服务限流 3.分布式限流,前端资源有限我只做了接口限流

1.单个服务限流

1.1 使用令牌桶。不懂原理的可以看 java中提供了 google.guava包可以实现令牌桶,需要导包。

RateLimiter rateLimiter = RateLimiter.create(5.0);
 
 for (int i = 0; i < 10; i++) {
	rateLimiter.acquire(); // 获取许可,如果没有可用许可则等待
	System.out.println("处理请求: " + i);
}

1.2 自定义方法,直接两个map搞定,不需要导包。

//记录访问次数
private static final Map<String, AtomicInteger> BucketCounts =new ConcurrentHashMap<>();

//记录访问时间
private static final Map<String,Long> BucketTimes = new ConcurrentHashMap<>();

在切面中,把接口名字当作key,调getTimeRequestCount方法校验次数

    public boolean getTimeRequestCount(String key){
        //校验时间
        checkTimeOut(key);
        //累积次数
        BucketCounts.putIfAbsent(key,new AtomicInteger(0));
        //次数可以写自定义配置中
        return BucketCounts.get(key).incrementAndGet() > 10;
    }
    public void checkTimeOut(String key) {
    if (BucketTimes.get(key) == null) {
        BucketTimes.put(key, System.currentTimeMillis());
    } else {
        //计算当前时间距离第一次开始点击时间差
        if (isGreaterByOneHour(System.currentTimeMillis (), BucketTimes.get(key)))
            //满足时间差就重新开始累积次数
            BucketCounts.get(key).set(0);
            BucketTimes.put(key, System.currentTimeMillis());
        }
    }
    public static boolean isGreaterByOneHour(long timestamp1, long timestamp2) {
        Instant time1 = Instant.ofEpochMilli(timestamp1);
        Instant time2 = Instant.ofEpochMilli(timestamp2);
        Duration duration = Duration.between(time2, time1);
        //时间可写自定义配置
        return duration.toMinutes() >= 20;
    }

2.分布式服务

2.1 使用redis实现令牌桶进行分布式限流

//切面获取接口
if (!redisTokenBucket.tryConsume(key+methodName,10,300)) {
	throw new RuntimeException("请求频繁");
}

利用redis的lua命令做令牌桶

@Component
public class RedisTokenBucket {
    private final StringRedisTemplate redisTemplate;
    private final DefaultRedisScript<Long> redisScript;

    public RedisTokenBucket(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.redisScript = new DefaultRedisScript<>();
        this.redisScript.setScriptText(
                "local tokens = redis.call('get', KEYS[1]) " +
                        "if tokens then " +
                        "  if tonumber(tokens) > 0 then " +
                        "    redis.call('decr', KEYS[1]) " +
                        "    return 1 " +
                        "  else " +
                        "    return 0 " +
                        "  end " +
                        "else " +
                        "  redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) " +
                        "  return 1 " +
                        "end"
        );
        this.redisScript.setResultType(Long.class);
    }

    /**
     * key redis的key
     * capacity 桶中令牌数
     * refillInterval 多长时间补充令牌(单位秒)
     * */
    public boolean tryConsume(String key, int capacity, int refillInterval) {
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(key),
                String.valueOf(capacity), String.valueOf(refillInterval));
        return result != null && result == 1;
    }
}

2.2 springcloud中使用gateway组件做限流,需要在配置文件中做改动。