618大促流量一路狂飙,作为威胁检测安全日志通路的入口,不能让程序被打挂导致服务不可用,所以我们采取阶梯的限流模型,以确保大部分流量会被处理,而不至于让程序被打挂导致大量日志丢失。
整体思路:
- 预先设置阶梯的限流策略并将策略加载至内存;
- 当每个请求进来后在spring的AOP切面中对当前这一秒钟的请求数进行累计;
- 累加完成后得到当前的流量大小,将当前流量大小与限流的阶梯策略进行比较;
- 如果满足阶梯策略,我们对流量进行处理,要么放行,要么直接返回达到限流的目的;
程序启动时初始化限流策略
限流处理流程
流量累加
看下流量累加的代码
package com.ikong.demo.service.limiter;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class TrafficMonitorWindow {
private AtomicLong time = new AtomicLong(new Date().getTime());
private AtomicInteger count = new AtomicInteger(0);
private final Integer interval = 1000;//每秒
private static Object lockObj = new Object();
/*
* 计算每秒钟的流量
*/
public Integer trafficIncrement() {
long now = new Date().getTime();
if (now > time.get() + interval) {
synchronized (lockObj) {
if (now > time.get() + interval) { //如果当前时钟已经过1秒,则以当前时间点为基准,切换时钟,注意这里并不是以自然秒钟为单位,而是以有流量的那个时刻来计算的
time.compareAndSet(time.get(), now);
count.compareAndSet(count.get(), 0);
}
}
}
count.getAndIncrement();
return count.get();
}
public Integer getCount() {
return count.get();
}
}
限流策略实体类
package com.ikong.demo.service.limiter;
public class LimitingStrategy {
private Integer limit;//限流阈值
private Integer rate; //丢弃率
public Integer getLimit() {
return limit;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
public Integer getRate() {
return rate;
}
public void setRate(Integer rate) {
this.rate = rate;
}
}
丢弃计算
判断流量是否需要丢弃:limiter:为丢弃率,当limiter=20时,丢弃率为20%,做一个简单的随机算法,随机100以内的数,如果小于20则丢弃,否则放行
package com.ikong.demo.service.limiter;
public class TrafficDiscard {
public boolean discard(int limiter) {
Random random = new Random();
int v = random.nextInt(100);
return v < limiter ;
}
}
丢弃策略配置
limiter.switch=true #是否限流的开关
limiter.max=40000 # 这里是限流阈值
limiter.strategys[0].limit=70 //这里是超过max=40000的70%后,将超过部分按20%丢弃率进行丢弃
limiter.strategys[0].rate=20
limiter.strategys[1].limit=80 //这里是超过max=40000的80%后,将超过部分按50%丢弃率进行丢弃
limiter.strategys[1].rate=50
limiter.strategys[2].limit=100 //这里是超过max=40000的100%后,将超过部分全部丢弃
limiter.strategys[2].rate=100
流量值高效命中策略
关键的问题我们如何高效快速定位当前流量命中哪个策略
items = limiter.strategys;
max = limiter.max;
for (int j = 1; j < max * item.get(0).getLimit() / 100; j++) {
cache.put(j, 0);
}
for (int i = 0; i < items.size() - 1; i++) {
for (int j = max * items.get(i).getLimit() / 100; j < max * items.get(i + 1).getLimit() / 100; j++) {
cache.put(j, items.get(i).getRate());
}
}
for (int i = max; i < 150000; i++) {
cache.put(i, items.get(items.size() - 1).getRate());
}
大体的思路:
- 先讲策略拆分成,[0,70),[70,80),[80,100),[100,15w),4个区间,默认单机请求量极限值不超过15w,超过服务器会宕机;
- 针对4个区间,每一个请求流量大小都把对应的策略放到内存中;
- 当计算完当前流量值后,按流量值直接在内存中取出对应的策略用于计算;