流量控制,要做的事情,就是监控应用流量,主要是俩个方面,QPS和并发线程数,当这俩个指标达到阈值的时候对流量进行控制,避免瞬时流量或者下游服务高响应时应用本身被冲垮,提高应用的高可用性
今天来聊一聊并发线程数流量控制
考虑一种业务场景,你的服务需要调用下游服务,现在下游服务因为某种原因,出现了高响应延迟甚至接近不可用的状态,那么你作为调用者,会发现吞吐量下降并且线程数占用变多,最糟糕的情况就是服务器线程被耗尽。所以这个时候就需要对线程做一个隔离,一种方案就是用线程池进行隔离,对于不同的业务部署不同的线程池,但是这样会动用很多的线程池和线程,线程上下文切换也是一个头疼的问题,特别是对于网关这样专门管理api的服务,代价巨大。
Sentinel的并发线程数流量控制不负责创建和管理线程池,而是简单的统计当前请求上下文的线程数,然后根据阈值来拒绝请求。效果类似于信号量隔离,这里就看一个官方的Demo
这个官方Demo模拟这样的情况,现在有一个methodA服务,请求下游服务methodB,假设当前环境有100个线程,methodA的并发线程数被限制在20个线程,下游服务methodB模拟高延迟和低延迟俩种情况,用AtomicInteger和volatile保证监控线程在并发条件下的正确性,使得统计结果精确
- 监控变量
//计数:被接受的请求数
private static AtomicInteger pass = new AtomicInteger();
//计数:被拒绝的请求数
private static AtomicInteger block = new AtomicInteger();
//计数:总的请求数
private static AtomicInteger total = new AtomicInteger();
//当前请求上下文被接受的线程数
private static AtomicInteger activeThread = new AtomicInteger();
private static volatile boolean stop = false;
//当前请求上下文总的线程数
private static final int threadCount = 100;
//总的模拟时间,60秒高响应延迟请求 40秒低延迟请求
private static int seconds = 60 + 40;
//高响应处理时间 2秒
private static volatile int methodBRunningTime = 2000;
- 规则的构建
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
//构建限流规则
FlowRule rule1 = new FlowRule();
//标识资源名称
rule1.setResource("methodA");
//methodA资源的线程数限制为20
rule1.setCount(20);
//限流类型,1-QPS/0-并发线程数,这里是并发线程数
rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
//针对对用来源的限流,default为不区分来源
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
- 资源的定义和监控变量的控制(main函数)
public static void main(String[] args) throws Exception {
System.out.println(
"MethodA will call methodB. After running for a while, methodB becomes fast, "
+ "which make methodA also become fast ");
//开启监控线程
tick();
//使规则生效
initFlowRule();
//启动100个线程不断请求资源methodA
for (int i = 0; i < threadCount; i++) {
Thread entryThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Entry methodA = null;
try {
//5毫秒的线程休眠
TimeUnit.MILLISECONDS.sleep(5);
//定义资源methodA
methodA = SphU.entry("methodA");
activeThread.incrementAndGet();
//定义资源methodB
Entry methodB = SphU.entry("methodB");
//模拟下游资源出现高响应延迟的情况
TimeUnit.MILLISECONDS.sleep(methodBRunningTime);
methodB.exit();
pass.addAndGet(1);
} catch (BlockException e1) {
block.incrementAndGet();
} catch (Exception e2) {
// biz exception
} finally {
total.incrementAndGet();
if (methodA != null) {
methodA.exit();
activeThread.decrementAndGet();
}
}
}
}
});
entryThread.setName("working thread");
entryThread.start();
}
}
- 监控线程
//监控线程
static class TimerTask implements Runnable {
@Override
public void run() {
long start = System.currentTimeMillis();
System.out.println("begin to statistic!!!");
long oldTotal = 0;
long oldPass = 0;
long oldBlock = 0;
while (!stop) {
try {
//每秒打印监控信息
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
//每秒请求总数
long globalTotal = total.get();
long oneSecondTotal = globalTotal - oldTotal;
oldTotal = globalTotal;
//每秒被接受请求
long globalPass = pass.get();
long oneSecondPass = globalPass - oldPass;
oldPass = globalPass;
//每秒被拒绝请求
long globalBlock = block.get();
long oneSecondBlock = globalBlock - oldBlock;
oldBlock = globalBlock;
System.out.println(seconds + " total qps is: " + oneSecondTotal);
System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
+ ", pass:" + oneSecondPass
+ ", block:" + oneSecondBlock
+ " activeThread:" + activeThread.get());
//100秒到,停止测试
if (seconds-- <= 0) {
stop = true;
}
//切换低延迟模式
if (seconds == 40) {
System.out.println("method B is running much faster; more requests are allowed to pass");
methodBRunningTime = 20;
}
}
long cost = System.currentTimeMillis() - start;
System.out.println("time cost: " + cost + " ms");
System.out.println("total:" + total.get() + ", pass:" + pass.get()
+ ", block:" + block.get());
System.exit(0);
}
}