文章目錄
- 概述
- 如何使用
- 流量控制
- 熔斷降級(jí)
- 控制臺(tái)
- 總結(jié)
Sentinel是阿里中間件團(tuán)隊(duì)開源的涩赢,面向分布式服務(wù)架構(gòu)的輕量級(jí)高可用流量控制組件。與Hystrix庫相比,它提供了更豐富的功能,以及更直觀的實(shí)現(xiàn)煌茬。本文主參考Sentinel官方提供的示例,讓更多的朋友體對(duì)sentinel有一個(gè)初步的認(rèn)識(shí)彻桃。在后續(xù)的文章中會(huì)講述它的實(shí)現(xiàn)原理細(xì)節(jié)坛善。
1. 概述
Sentinel主要提供了流量控制、服務(wù)降級(jí)邻眷、系統(tǒng)負(fù)載保護(hù)眠屎、實(shí)時(shí)數(shù)據(jù)監(jiān)控等功能。
先從流量控制說起肆饶,什么是流量控制改衩?顧名思義,和字面名字的含義差不多驯镊,只不過這里的流量一般指的是對(duì)RPC服務(wù)調(diào)用的流量葫督。那么為什么要對(duì)它進(jìn)行控制呢?一個(gè)原因是一般我們的一臺(tái)服務(wù)器可能對(duì)提供過個(gè)RPC服務(wù)板惑,如果一個(gè)服務(wù)占用過多的資源橄镜,那么其他的服務(wù)可能不能正常的提供服務(wù)了。還有過多的請(qǐng)求會(huì)降低服務(wù)質(zhì)量等原因冯乘。
Sentinel提供了兩種流量控制方式洽胶,一是QPS,二是服務(wù)的線程數(shù)裆馒。
關(guān)于服務(wù)熔斷降級(jí)姊氓,老生常談的話題丐怯。Sentinel提供了兩種降級(jí)策略,一是RT(run time),二是異常比例翔横。后面結(jié)合Demo解釋
2. 如何使用读跷?
Sentinel資源保護(hù)(流量控制和熔斷降級(jí)),這里的資源可以使RPC服務(wù)禾唁、方法等舔亭。主要分為兩個(gè)步驟:
- 定義規(guī)則,就是定義某個(gè)資源保護(hù)的規(guī)則蟀俊,比如限流策略、降級(jí)策略等订雾。
- 訪問資源肢预,說白了就是給資源起一個(gè)名字,為了區(qū)分其他資源
其實(shí)官網(wǎng)上把第二步稱為資源定義洼哎,但是個(gè)人認(rèn)為把它成為資源訪問更為合適烫映,因?yàn)镵EY代表了某個(gè)資源,但是具體代表了哪個(gè)資源是通過訪問某資源時(shí)才能確定的噩峦。
3. 流量控制
下面定義規(guī)則锭沟,Sentinel 支持三種規(guī)則:流量控制規(guī)則、熔斷降級(jí)規(guī)則以及系統(tǒng)保護(hù)規(guī)則识补。這里先試試流量控制規(guī)則族淮。
private static void initFlowQpsRule() {
// 規(guī)則對(duì)應(yīng)的類為FlowRule,用List保存凭涂,可以有多個(gè)規(guī)則
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
// QPS為20
rule1.setCount(20);
//限流的類型
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
流量控制規(guī)則對(duì)應(yīng)的類為FlowRule
- setResource(KEY)方法是設(shè)置資源名祝辣,也就是限流規(guī)則的作用對(duì)象,更通俗的講:本條規(guī)則對(duì)哪個(gè)資源生效切油。
- count是限流閾值蝙斜,當(dāng)我們定義的是流量控制規(guī)則是根據(jù)QPS進(jìn)行限流時(shí),它表示QPS的閾值澎胡,當(dāng)然如果是根據(jù)線程數(shù)限流孕荠,它表示線程數(shù)。
- grade表示限流閾值類型攻谁,是按照 QPS 還是線程數(shù)默認(rèn)根據(jù) QPS稚伍。
- controlBehavior表示發(fā)生攔截后是直接拒絕,還是排隊(duì)等待巢株,還是慢啟動(dòng)模式槐瑞,默認(rèn)是直接拒絕,如果設(shè)置為排隊(duì)等待則還需要設(shè)置maxQueueingTimeMs(最大排隊(duì)時(shí)間)阁苞。
最后 FlowRuleManager.loadRules(rules)是將rules生效困檩§舸欤總體的來說Sentinel的API比較直觀易懂,我們繼續(xù)往下悼沿,開始訪問我們定義的資源等舔。
下面結(jié)合一個(gè)例子對(duì)Sentinel有進(jìn)一步的認(rèn)識(shí)。接下來是訪問資源糟趾,其實(shí)就是對(duì)服務(wù)的請(qǐng)求代碼請(qǐng)求進(jìn)行一個(gè)控制慌植,代碼如下:
private static String KEY = "biz"http:// 資源名
public void request() {
try {
// token acquired, means pass
Entry entry = SphU.entry(KEY);
//被保護(hù)biz代碼
} catch (BlockException e1) {
// 資源訪問阻止,被限流或被降級(jí)
} catch (Exception e2) {
// biz exception
} finally {
if (entry != null) {
entry.exit();
}
}
}
這里可以看出所謂的資源其實(shí)就是Entry entry = SphU.entry(KEY);
代碼后的業(yè)務(wù)代碼义郑,并且Sentinel通過一個(gè)KEY作為它的唯一表示蝶柿。
SphU的entry() 方法,將返回KEY的調(diào)用的信息非驮。 當(dāng)超過任何規(guī)則的閾值時(shí)交汤。 將拋出BlockException。進(jìn)入catch BlockException 代碼塊劫笙。
下面是參考官網(wǎng)的的一個(gè)完整的例子:
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 int seconds = 20;
public static void main(String[] args) {
initFlowQpsRule();
//單開一條線程芙扎,統(tǒng)計(jì)流量數(shù)據(jù)
new Thread(new CountFlowTool()).start();
//多線程請(qǐng)求
new simulateFlow(10).start();
}
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);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rule1.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
static class CountFlowTool implements Runnable {
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 simulateFlow {
int threadCount;
simulateFlow(int threadCount) {
this.threadCount = threadCount;
}
public void start() {
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new Runnable() {
public void run() {
while (!stop) {
Entry entry = null;
try {
total.incrementAndGet();
entry = SphU.entry(KEY);
// token acquired, means pass
pass.addAndGet(1);
} catch (BlockException e1) {
block.incrementAndGet();
} catch (Exception e2) {
// biz exception
} finally {
if (entry != null) {
entry.exit();
}
}
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
}
}
}
});
t.start();
}
}
}
}
主方法中顯示定義規(guī)則,讓后通過多線程模擬請(qǐng)求填大,內(nèi)部通過循環(huán)不停的請(qǐng)求資源戒洼。統(tǒng)計(jì)工具是統(tǒng)計(jì)前一秒的請(qǐng)求數(shù)據(jù)≡驶總體代碼比較直觀易懂圈浇。
根據(jù)線程數(shù)的流量限制也是類似。只是規(guī)則設(shè)置代碼不同靴寂。還有一個(gè)問題就是根據(jù)線程數(shù)的流量限制中汉额,Sentinel沒有對(duì)線程的控制權(quán)限,內(nèi)部只是對(duì)請(qǐng)求線程的統(tǒng)計(jì)榨汤。如果超出閾值蠕搜,新的請(qǐng)求會(huì)被立即拒絕。但是根據(jù)QPS進(jìn)行流量控制中可以有多個(gè)選擇:1. 直接拒絕(CONTROL_BEHAVIOR_DEFAULT)收壕,2. 慢啟動(dòng)也叫冷啟動(dòng)(CONTROL_BEHAVIOR_WARM_UP)過"冷啟動(dòng)"妓灌,讓通過的流量緩慢增加,在一定時(shí)間內(nèi)逐漸增加到閾值上限蜜宪,給冷系統(tǒng)一個(gè)預(yù)熱的時(shí)間虫埂,避免冷系統(tǒng)被壓垮的情況。圃验,3. 勻速通過(CONTROL_BEHAVIOR_RATE_LIMITER)掉伏,這種方式嚴(yán)格控制了請(qǐng)求通過的間隔時(shí)間,也即是讓請(qǐng)求以均勻的速度通過,內(nèi)部實(shí)現(xiàn)是漏桶算法斧散。
4. 熔斷降級(jí)
Sentinel中供常,對(duì)資源的調(diào)用都自動(dòng)熔斷。通常用兩種方式來衡量資源是否處于穩(wěn)定的狀態(tài):RT和異常鸡捐。以平均響應(yīng)時(shí)間為例栈暇,如果請(qǐng)求的的時(shí)間大于閾值,那么接下來會(huì)嘗試5次箍镜,如果這5次的請(qǐng)求時(shí)間平均時(shí)間大于閾值源祈,那么在接下來的時(shí)間窗口(timeWindow)內(nèi)將自動(dòng)降級(jí)處理(直接跳入BlockException),當(dāng)時(shí)間窗口結(jié)束再次嘗試5次色迂,以此重復(fù)香缺。這里嘗試五次的觸發(fā)點(diǎn)是RT超過閾值。
下面結(jié)合Demo講解,這里指列出規(guī)則定義的代碼:
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<DegradeRule>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// 設(shè)置響應(yīng)時(shí)間歇僧,50ms
rule.setCount(50);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(5);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
通過異常降級(jí)于此類似赫悄。當(dāng)資源的每秒異常總數(shù)占通過總數(shù)的比值超過閾值(DegradeRule 中的 count)之后馏慨,資源進(jìn)入降級(jí)狀態(tài),即在接下的時(shí)間窗口(DegradeRule 中的 timeWindow姑隅,以 s 為單位)之內(nèi)写隶,對(duì)這個(gè)方法的調(diào)用都會(huì)自動(dòng)地返回。
下面參考官網(wǎng)給出一個(gè)完整的基于RT熔斷的例子:
public class RtDegradeDemo {
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 int seconds = 10000;
private static boolean stop = false;
public static void main(String[] args) throws Exception {
initDegradeRule();
new Thread(new CountFlowTool()).start();
new simulateFlow(1).start();
}
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<DegradeRule>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
rule.setCount(50);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
static class simulateFlow {
int threadCount;
public simulateFlow(int threadCount) {
this.threadCount = threadCount;
}
public void start() {
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
while (!stop) {
Entry entry = null;
try {
total.incrementAndGet();
TimeUnit.MILLISECONDS.sleep(100);
entry = SphU.entry(KEY);
pass.getAndIncrement();
TimeUnit.MILLISECONDS.sleep(100);
} catch (BlockException e) {
block.incrementAndGet();
} catch (Throwable e) {
//biz exception
} finally {
if (entry != null) {
entry.exit();
}
}
}
}).start();
}
}
}
static class CountFlowTool implements Runnable {
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(TimeUtil.currentTimeMillis() + ", laseSec 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);
}
}
}
5. 控制臺(tái)
Sentinel提供了控制臺(tái)模塊讲仰,用于對(duì)實(shí)時(shí)限流熔斷數(shù)據(jù)的可視化和對(duì)規(guī)則(限流和熔斷)的實(shí)時(shí)推送慕趴。控制臺(tái)是一個(gè)基于Spring boot開發(fā)的控制臺(tái)鄙陡。其主要流程為:
- 控制臺(tái)工程啟動(dòng)
- APP啟動(dòng)冕房,向控制臺(tái)發(fā)送心跳數(shù)據(jù)(APP信息包括機(jī)器信息、APP name等)趁矾,可以從代碼找到:
public class Env {
public static final NodeBuilder nodeBuilder = new DefaultNodeBuilder();
public static final Sph sph = new CtSph();
static {
// If init fails, the process will exit.
InitExecutor.doInit();// doInit方法會(huì)調(diào)用sendHeadBeat
}
}
- 前端發(fā)送HTTP請(qǐng)求(請(qǐng)求規(guī)則耙册、實(shí)時(shí)運(yùn)行數(shù)據(jù)) -> 控制臺(tái)HTTP請(qǐng)求-> APP Netty Server
搭建控制臺(tái)步驟:
- 啟動(dòng)控制臺(tái)工程,有兩種方式直接運(yùn)行Jar包和在源碼中啟動(dòng)SpringBoot毫捣,筆者在這里介紹后者详拙。代碼工程如下:
Sentinel
添加如下參數(shù):-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard
2 . 啟動(dòng)APP應(yīng)用,參數(shù)如下:-Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=demo
maven 依賴:
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>
</dependencies>
恭喜你蔓同!
6. 總結(jié)
本文主要介紹了Sentinel的限流和熔斷降級(jí)功能饶辙,但是Sentinel的功能不止于此。比如系統(tǒng)負(fù)載保護(hù)斑粱,系統(tǒng)負(fù)載高于某個(gè)閾值弃揽,就禁止或者減少流量的進(jìn)入;當(dāng) load 開始好轉(zhuǎn),則恢復(fù)流量的進(jìn)入矿微。以及對(duì)注解的支持痕慢、控制面板等。
相比Hystrix來說冷冗,Sentinel的熔斷和限流邏輯更簡(jiǎn)單明了守屉。后續(xù)會(huì)出Sentinel的實(shí)現(xiàn)原理文章。