六、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,之後有時間自己再寫。
相關文章
- CAS實現單點登入SSO執行原理探究
- com.alibaba.fastjson學習筆記
- 【原創】Linux虛擬化KVM-Qemu分析(六)之中斷虛擬化
- 三、金融業企業服務匯流排鏈路追蹤監控分析平臺的建設實踐--CASSANDRA儲存方案
- 電力系統分析(第二版)Hadi Saadat matlab 程式二(教材搬運)
- 精盡 MyBatis 原始碼分析 - 整體架構
- Linux核心原始碼分析之set_arch (一)
- 基於 abp vNext 微服務開發的敏捷應用構建平臺 - 框架分析
- Java動態代理—框架中的應用場景和基本原理
- ThreadPoolExecutor原始碼分析-面試問爛了的Java執行緒池執行流程,如果要問你具體的執行細節,你還會嗎?
- React基礎與原理實現
- Python 字典實現原理
- 死磕以太坊原始碼分析之Kademlia演算法
- 現代作業系統-原理與實現【讀書筆記】
- Android10_原理機制系列_Activity視窗新增到WMS過程
- 檔案上傳踩坑記及檔案清理原理探究
- 精盡 MyBatis 原始碼分析 - 基礎支援層
- 計算機組成原理期末考試題
- 原始碼分析:CountDownLatch 之倒數計時門栓
- Android ContentProvider 啟動分析