六、Alibaba sentinel之限流原理分析
1、找到該檔案,開啟
2、程式碼如下:
有一個TimeTicker執行緒在做統計,每1秒鐘做一次。有N個RunTask執行緒在模擬請求,被訪問的business code被資源key保護著,根據規則,每秒只允許20個請求通過。
package com.sentinel.sentinel.test;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.util.TimeUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class FlowQpsDemo {
private static final String KEY = "abc";
private static AtomicInteger pass = new AtomicInteger();
private static AtomicInteger block = new AtomicInteger();
private static AtomicInteger total = new AtomicInteger();
private static volatile boolean stop = false;
private static final int threadCount = 32;
private static int seconds = 30;
public static void main(String[] args) throws Exception {
initFlowQpsRule();
tick();
// first make the system run on a very low condition
simulateTraffic();
System.out.println("===== begin to do flow control");
System.out.println("only 20 requests per second can pass");
}
private static void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
// 資源名,資源名是限流規則的作用物件
rule1.setResource(KEY);
// set limit qps to 20 限流閾值
rule1.setCount(20);
// 設定限流型別:根據qps
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 流控針對的呼叫來源
// default,代表不區分呼叫來源
rule1.setLimitApp("default");
rules.add(rule1);
// 載入限流的規則
FlowRuleManager.loadRules(rules);
}
private static void simulateTraffic() {
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new RunTask());
t.setName("simulate-traffic-Task");
t.start();
}
}
private static void tick() {
Thread timer = new Thread(new TimerTask());
timer.setName("sentinel-timer-task");
timer.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 + " send qps is: " + oneSecondTotal);
System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
+ ", pass:" + oneSecondPass
+ ", block:" + oneSecondBlock);
if (seconds-- <= 0) {
stop = true;
}
}
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);
}
}
static class RunTask implements Runnable {
@Override
public void run() {
while (!stop) {
Entry entry = null;
try {
entry = SphU.entry(KEY);
// token acquired, means pass
pass.addAndGet(1);
} catch (BlockException e1) {
block.incrementAndGet();
} catch (Exception e2) {
// biz exception
} finally {
total.incrementAndGet();
if (entry != null) {
entry.exit();
}
}
Random random2 = new Random();
try {
TimeUnit.MILLISECONDS.sleep(random2.nextInt(50));
} catch (InterruptedException e) {
// ignore
}
}
}
}
}
3、程式碼分析
public static void main(String[] args) throws Exception {
// 設定規則
initFlowQpsRule();
// 統計執行緒,每一秒執行以此
tick();
// 模擬qps,Task執行緒,while不斷建立,看1s內能有多少通過
simulateTraffic();
System.out.println("===== begin to do flow control");
System.out.println("only 20 requests per second can pass");
}
private static void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
// 資源名,資源名是限流規則的作用物件
rule1.setResource(KEY);
// set limit qps to 20 限流閾值
rule1.setCount(20);
// 設定限流型別:根據qps
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 流控針對的呼叫來源
// default,代表不區分呼叫來源
rule1.setLimitApp("default");
rules.add(rule1);
// 載入限流的規則
FlowRuleManager.loadRules(rules);
}
// 工作執行緒,建立執行緒執行
private static void simulateTraffic() {
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new RunTask());
t.setName("simulate-traffic-Task");
t.start();
}
}
// 統計執行緒,每s執行1次
private static void tick() {
Thread timer = new Thread(new TimerTask());
timer.setName("sentinel-timer-task");
timer.start();
}
static class TimerTask implements Runnable {
@Override
public void run() {
long start = System.currentTimeMillis();
System.out.println("begin to statistic!!!");
// 總qps
long oldTotal = 0;
// 通過的qps
long oldPass = 0;
// 未通過的qps
long oldBlock = 0;
// 不斷執行統計
while (!stop) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
long globalTotal = total.get();
// 獲得最新1s的總qps
long oneSecondTotal = globalTotal - oldTotal;
oldTotal = globalTotal;
// 獲得最新1s的通過的qps
long globalPass = pass.get();
long oneSecondPass = globalPass - oldPass;
oldPass = globalPass;
// 獲得最新1s的未通過的qps
long globalBlock = block.get();
long oneSecondBlock = globalBlock - oldBlock;
oldBlock = globalBlock;
System.out.println(seconds + " send qps is: " + oneSecondTotal);
System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
+ ", pass:" + oneSecondPass
+ ", block:" + oneSecondBlock);
// 30s後停止
if (seconds-- <= 0) {
stop = true;
}
}
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);
}
}
static class RunTask implements Runnable {
@Override
public void run() {
while (!stop) {
Entry entry = null;
try {
entry = SphU.entry(KEY);
// 通過+1
pass.addAndGet(1);
} catch (BlockException e1) {
// 未通過+1
block.incrementAndGet();
} catch (Exception e2) {
// biz exception
} finally {
// 總+1
total.incrementAndGet();
if (entry != null) {
entry.exit();
}
}
// 睡一會,不然建立的匯流排程太大,qps太大,只是模擬一下,夠大就行
Random random2 = new Random();
try {
TimeUnit.MILLISECONDS.sleep(random2.nextInt(50));
} catch (InterruptedException e) {
// ignore
}
}
}
}
執行後:
可以看到,成功進行了限流!
問題一:pass的數量和我們的預期並不相同,我們預期的是每秒允許pass的請求數是20個,但是目前有很多pass的請求數是超過20個的。
- 原因是,我們這裡測試的程式碼使用了多執行緒,注意看 threadCount 的值,一共有32個執行緒來模擬,而在RunTask的run方法中執行資源保護時,即在 SphU.entry 的內部是沒有加鎖的,所以就會導致在高併發下,pass的數量會高於20。
- 由於pass、block、total等計數器是全域性共享的,而多個RunTask執行緒在執行SphU.entry申請獲取entry時,內部沒有鎖保護,所以會存在pass的個數超過設定的閾值。
問題二:可以看到pass數基本上維持在20,但是第一次統計的pass值還是超過了20。這又是什麼原因導致的呢?
- 其實仔細看下Demo中的程式碼可以發現,模擬請求是用的一個執行緒,統計結果是用的另外一個執行緒,統計執行緒每1秒鐘統計一次結果,這兩個執行緒之間是有時間上的誤差的。從TimeTicker執行緒列印出來的時間戳可以看出來,雖然每隔一秒進行統計,但是當前列印時的時間和上一次的時間還是有誤差的,不完全是1000ms的間隔。
4、深入原理
我們通過上面可以看到,最主要的程式碼其實就是這個:
entry = SphU.entry(KEY);
SphU.entry() 。這個方法會去申請一個entry,如果能夠申請成功,則說明沒有被限流,否則會丟擲BlockException,表面已經被限流了。
剩下的看這個吧:https://zhuanlan.zhihu.com/p/67469675,之後有時間自己再寫。
相關文章
- Sentinel分散式限流元件,SpringCloud Alibaba整合分散式元件SpringGCCloud
- Spring Cloud Alibaba:Sentinel實現熔斷與限流SpringCloud
- Spring Cloud Alibaba | Sentinel: 服務限流基礎篇SpringCloud
- Spring Cloud Alibaba | Sentinel: 服務限流高階篇SpringCloud
- SpringCloud Alibaba系列(三) Sentinel熱點引數限流SpringGCCloud
- 原始碼分析 Alibaba sentinel 滑動視窗實現原理(文末附原理圖)原始碼
- Spring Cloud Alibaba基礎教程:使用Sentinel實現介面限流SpringCloud
- spring cloud alibaba系列(二)Sentinel應用的限流管理SpringCloud
- Spring-Cloud-Alibaba之SentinelSpringCloud
- Spring Cloud Alibaba元件之SentinelSpringCloud元件
- Spring Cloud Alibaba系列(五)sentinel實現服務限流降級SpringCloud
- Spring Cloud Alibaba系列(六)sentinel的實際應用SpringCloud
- Sentinel 實戰-限流篇
- Spring Cloud Alibaba Sentinel 主要原理和核心類介紹SpringCloud
- SpringCloud-Alibaba-SentinelSpringGCCloud
- Spring Cloud Alibaba SentinelSpringCloud
- Sentinel限流之快速失敗和漏桶演算法演算法
- SpringCloud11 -- Alibaba SentinelSpringGCCloud
- 阿里巴巴開源限流元件Sentinel初探之整合Gateway阿里元件Gateway
- SpringCloud-Alibaba-Sentinel(1)初探SpringGCCloud
- Spring Cloud Alibaba(9)---Sentinel概述SpringCloud
- springcloud alibaba sentinel降級 @SentinelResourceSpringGCCloud
- Spring Cloud Alibaba教程:Sentinel的使用SpringCloud
- Spring Cloud Alibaba(四)--Gateway與SentinelSpringCloudGateway
- Sentinel實現限流,竟是如此的簡單!
- 限流原理解讀之guava中的RateLimiterGuavaMIT
- Sentinel系列之SlotChain、NodeSelectorSlot、ClusterBuilderSlot分析AIUI
- GCD原始碼分析之再解 sentinelGC原始碼
- Sentinel原理一覽
- Sentinel 原理-呼叫鏈
- Sentinel 原理-全解析
- 阿里限流神器Sentinel奪命連環 17 問?阿里
- Dubbo之限流TpsLimitFilter原始碼分析MITFilter原始碼
- [Spring-Cloud-Alibaba] Sentinel 規則持久化SpringCloud持久化
- Spring Cloud Alibaba(11)---Sentinel+Nacos持久化SpringCloud持久化
- 阿里開源限流元件 Sentinel 叢集流控全解析阿里元件
- Sentinel入門到實操 (限流熔斷降級)
- Sentinel 原理-滑動視窗