服务端对接口限流
目录
服务端对接口限流
安全发现后端某个接口可以外网中访问,可能存在被刷,就加上了接口限流。
接口限流有很多方式,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组件做限流,需要在配置文件中做改动。