一、什么是削峰填谷:
某瞬時(shí)來(lái)了大流量的請(qǐng)求, 而如果此時(shí)要處理所有請(qǐng)求,很可能會(huì)導(dǎo)致系統(tǒng)負(fù)載過(guò)高,影響穩(wěn)定性。但其實(shí)可能后面幾秒之內(nèi)都沒(méi)有消息投遞,若直接把多余的消息丟掉則沒(méi)有充分利用系統(tǒng)處理消息的能力。Sentinel的Rate Limiter模式能在某一段時(shí)間間隔內(nèi)以勻速方式處理這樣的請(qǐng)求, 充分利用系統(tǒng)的處理能力, 也就是削峰填谷, 保證資源的穩(wěn)定性.
Sentinel會(huì)以固定的間隔時(shí)間讓請(qǐng)求通過(guò), 訪問(wèn)資源。當(dāng)請(qǐng)求到來(lái)的時(shí)候,如果當(dāng)前請(qǐng)求距離上個(gè)通過(guò)的請(qǐng)求通過(guò)的時(shí)間間隔不小于預(yù)設(shè)值,則讓當(dāng)前請(qǐng)求通過(guò);否則,計(jì)算當(dāng)前請(qǐng)求的預(yù)期通過(guò)時(shí)間,如果該請(qǐng)求的預(yù)期通過(guò)時(shí)間小于規(guī)則預(yù)設(shè)的 timeout 時(shí)間,則該請(qǐng)求會(huì)等待直到預(yù)設(shè)時(shí)間到來(lái)通過(guò);反之,則馬上拋出阻塞異常。
使用Sentinel的這種策略, 簡(jiǎn)單點(diǎn)說(shuō), 就是使用一個(gè)時(shí)間段(比如20s的時(shí)間)處理某一瞬時(shí)產(chǎn)生的大量請(qǐng)求, 起到一個(gè)削峰填谷的作用, 從而充分利用系統(tǒng)的處理能力, 下圖能很形象的展示這種場(chǎng)景: X軸代表時(shí)間, Y軸代表系統(tǒng)處理的請(qǐng)求.

二、驗(yàn)證
模擬1000個(gè)請(qǐng)求同時(shí)并發(fā)的訪問(wèn)資源, 這1000個(gè)請(qǐng)求, 如果設(shè)置QPS閾值為100, 按照默認(rèn)sentinel默認(rèn)的RuleConstant.CONTROL_BEHAVIOR_DEFAULT拒絕策略, 那么剩余的900個(gè)請(qǐng)求會(huì)被立即直接拒絕掉, 拋出BlockException.



同樣是模擬1000個(gè)請(qǐng)求同時(shí)并發(fā)訪問(wèn)資源, 同樣設(shè)置QPS閾值100, 但是拒絕策略修改為Rate Limiter勻速RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER方式, 還需要設(shè)置setMaxQueueingTimeMs(20 * 1000)表示每一請(qǐng)求最長(zhǎng)等待時(shí)間, 這里等待時(shí)間大一點(diǎn), 以保證讓所有請(qǐng)求都能正常通過(guò)。
假設(shè)這里設(shè)置的排隊(duì)等待時(shí)間過(guò)小的話, 導(dǎo)致排隊(duì)等待的請(qǐng)求超時(shí)而拋出異常BlockException, 最終結(jié)果可能是這1000個(gè)并發(fā)請(qǐng)求中只有幾個(gè)請(qǐng)求或幾十個(gè)才能正常通過(guò), 所以使用這種模式得根據(jù)訪問(wèn)資源的耗時(shí)時(shí)間決定排隊(duì)等待時(shí)間。
按照目前這種設(shè)置, QPS閾值為100的話, 每一個(gè)請(qǐng)求相當(dāng)于是以勻速10ms左右通過(guò)。
@GetMapping("/paceFlow")
public void paceFlow() throws InterruptedException{
System.out.println("pace behavior");
countDown = new CountDownLatch(1);
// queuing隊(duì)列方式, 勻速處理流量
initPaceFlowRule();
// 直接并發(fā)同時(shí)啟動(dòng)1000個(gè)線程, 模擬1000個(gè)并發(fā)請(qǐng)求資源
simulatePulseFlow();
countDown.await();
System.out.println("done");
System.out.println("total pass:" + pass.get() + ", total block:" + block.get());
System.out.println();
}
勻速模式的規(guī)則
private static void initPaceFlowRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
rule1.setCount(count);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
/*
* CONTROL_BEHAVIOR_RATE_LIMITER means requests more than threshold will be queueing in the queue, until the
* queueing time is more than {@link FlowRule#maxQueueingTimeMs}, the requests will be rejected.
*/
rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
// 這里設(shè)置的等待處理時(shí)間較大, 讓系統(tǒng)能平穩(wěn)的處理所有的請(qǐng)求
rule1.setMaxQueueingTimeMs(20 * 1000);// 表示每一個(gè)請(qǐng)求的最長(zhǎng)等待時(shí)間20s
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
并發(fā)啟動(dòng)1000線程模擬請(qǐng)求
private static void simulatePulseFlow() {
for (int i = 0; i < requestQps; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
long startTime = TimeUtil.currentTimeMillis();
Entry entry = null;
try {
entry = SphU.entry(KEY);
pass.incrementAndGet();
} catch (BlockException e1) {
System.out.println("===>BlockException");
block.incrementAndGet();
} catch (Exception e2) {
// biz exception
} finally {
if (entry != null) {
entry.exit();
// pass.incrementAndGet();
long cost = TimeUtil.currentTimeMillis() - startTime;
System.out.println(TimeUtil.currentTimeMillis() + " one request pass, cost " + cost
+ " ms");
}
}
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e1) {
// ignore
}
if (done.incrementAndGet() >= requestQps) {
countDown.countDown();
}
}
}, "Thread " + i);
thread.start();
}
}
啟動(dòng)后控制臺(tái)打?。?/p>

可以看到pass:1000,block:0,可以看到在闊值設(shè)定為100,勻速模式下仍可以全部通過(guò),實(shí)現(xiàn)了削峰填谷。
sentinel控制臺(tái)展示效果圖:

可以看到由于闊值是100,而模擬請(qǐng)求是1000,通過(guò)的qps就是100,勻速通過(guò)直到全部pass。
還可以在控制臺(tái)看到代碼所限定的流控規(guī)則:
