逅弈 轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!
系列文章
Sentinel 原理-全解析
Sentinel 原理-調(diào)用鏈
Sentinel 原理-滑動(dòng)窗口
Sentinel 原理-實(shí)體類
Sentinel 實(shí)戰(zhàn)-限流篇
Sentinel 實(shí)戰(zhàn)-控制臺(tái)篇
Sentinel 實(shí)戰(zhàn)-規(guī)則持久化
Sentinel 實(shí)戰(zhàn)-集群限流篇
Sentinel 系列教程,現(xiàn)已上傳到 github 和 gitee 中:
- GitHub:https://github.com/all4you/sentinel-tutorial
- Gitee:https://gitee.com/all_4_you/sentinel-tutorial

通過 Sentinel 的控制臺(tái),我們可以對(duì)規(guī)則進(jìn)行查詢和修改,也可以查看到實(shí)時(shí)監(jiān)控,機(jī)器列表等信息,所以我們需要對(duì) sentinel 的控制臺(tái)做個(gè)完整的了解。
部署控制臺(tái)
首先需要啟動(dòng)控制臺(tái), sentinel 的控制臺(tái)是用 spring boot 寫的一個(gè)web 應(yīng)用,我們有幾種方式來獲取控制臺(tái):
下載可執(zhí)行 jar 包
從 release 頁面 下載截止目前為止最新版本的控制臺(tái) jar 包,如下圖所示:

下載源碼構(gòu)建
除了可以下載預(yù)先構(gòu)建好的可執(zhí)行 jar 包之外,我們還可以把控制臺(tái)的工程下載下來自行用源碼構(gòu)建,sentinel 是一個(gè)多 maven 模塊的項(xiàng)目,控制臺(tái)是其中的一個(gè)項(xiàng)目,如下圖所示:

如上圖所示,我們可以下載完整的 sentinel 的項(xiàng)目,然后構(gòu)建其中的 sentinel-dashboard 模塊,也可以只下載 sentinel-dashboard 模塊然后構(gòu)建。
這里我選擇將完整的 sentinel 工程下載下來,然后構(gòu)建 sentinel-dashboard 模塊,首先在項(xiàng)目根目錄下執(zhí)行:
cd sentinel-dashboard
將會(huì)進(jìn)入 dashboard 模塊,然后在 dashboard 目錄下執(zhí)行:
mvn clean package
maven將會(huì)把 sentinel-dashboard 模塊打包成一個(gè)可執(zhí)行的 fat jar包,如下圖所示:


啟動(dòng)控制臺(tái)
構(gòu)建成功后,就可以啟動(dòng)控制臺(tái)了,執(zhí)行以下命令:
java -Dserver.port=8080 \
-Dcsp.sentinel.dashboard.server=localhost:8080 \
-jar target/sentinel-dashboard.jar
其中 -Dserver.port=8080 用于指定 Sentinel 控制臺(tái)端口為 8080。
執(zhí)行完之后,你將看到如下信息:


當(dāng)看到 Started DashboardApplication in xx seconds 時(shí),說明你的控制臺(tái)已經(jīng)啟動(dòng)成功了,訪問 http://localhost:8080/ 就可以看到控制臺(tái)的樣子了,如下圖所示:

可以看到當(dāng)前控制臺(tái)中沒有任何的應(yīng)用,因?yàn)檫€沒有應(yīng)用接入。
接入控制臺(tái)
要想在控制臺(tái)中操作我們的應(yīng)用,除了需要部署一個(gè)控制臺(tái)的服務(wù)外,還需要將我們的應(yīng)用接入到控制臺(tái)中去。
引入 transport 依賴
首先需要在我們使用 sentinel 的服務(wù)中引入 sentinel-transport 的依賴,因?yàn)槲覀兊膽?yīng)用是作為客戶端,通過transport模塊與控制臺(tái)進(jìn)行通訊的,依賴如下所示:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>x.y.z</version>
</dependency>
版本依然選擇最新的 1.4.0
配置應(yīng)用啟動(dòng)參數(shù)
引入了依賴之后,接著就是在我們的應(yīng)用中配置 JVM 啟動(dòng)參數(shù),如下所示:
-Dproject.name=xxx -Dcsp.sentinel.dashboard.server=consoleIp:port
其中的consoleIp和port對(duì)應(yīng)的就是我們部署的 sentinel dashboard 的ip和port,我這里對(duì)應(yīng)的是 127.0.0.1 和 8080,按照實(shí)際情況來配置 dashboard 的ip和port就好了,如下圖所示:

從圖中可以看到我設(shè)置的客戶端的應(yīng)用名為:lememo,當(dāng)客戶端連接上控制臺(tái)后,會(huì)顯示該應(yīng)用名。
PS:需要注意的是,除了可通過 JVM -D 參數(shù)指定之外,也可通過 properties 文件指定,配置文件的路徑為 ${user_home}/logs/csp/${project.name}.properties。
配置文件中參數(shù)的key和類型如下所示:

優(yōu)先級(jí)順序:JVM -D 參數(shù)的優(yōu)先級(jí)最高,若 properties 文件和 JVM 參數(shù)中有相同項(xiàng)的配置,以 JVM -D 參數(shù)配置的為準(zhǔn)。
觸發(fā)客戶端連接控制臺(tái)
客戶端配置好了與控制臺(tái)的連接參數(shù)之后,并不會(huì)主動(dòng)連接上控制臺(tái),需要觸發(fā)一次客戶端的規(guī)則才會(huì)開始進(jìn)行初始化,并向控制臺(tái)發(fā)送心跳和客戶端規(guī)則等信息。
客戶端與控制臺(tái)的連接初始化是在 Env 的類中觸發(fā)的,即下面代碼中的 InitExecutor.doInit();:
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();
}
}
埋點(diǎn)
上篇文章中我們創(chuàng)建了一個(gè) UserService 來做驗(yàn)證,正常時(shí)會(huì)返回一個(gè)用戶對(duì)象,被限流時(shí)返回一個(gè)null,但是這樣不太直觀,本篇文章我換一個(gè)更簡單和直觀的驗(yàn)證方式,代碼如下所示:
@GetMapping("/testSentinel")
public @ResponseBody
String testSentinel() {
String resourceName = "testSentinel";
Entry entry = null;
String retVal;
try{
entry = SphU.entry(resourceName,EntryType.IN);
retVal = "passed";
}catch(BlockException e){
retVal = "blocked";
}finally {
if(entry!=null){
entry.exit();
}
}
return retVal;
}
PS:這里有個(gè)需要注意的知識(shí)點(diǎn),就是 SphU.entry 方法的第二個(gè)參數(shù) EntryType 說的是這次請求的流量類型,共有兩種類型:IN 和 OUT 。
IN:是指進(jìn)入我們系統(tǒng)的入口流量,比如 http 請求或者是其他的 rpc 之類的請求。
OUT:是指我們系統(tǒng)調(diào)用其他第三方服務(wù)的出口流量。
入口、出口流量只有在配置了系統(tǒng)規(guī)則時(shí)才有效。
設(shè)置 Type 為 IN 是為了統(tǒng)計(jì)整個(gè)系統(tǒng)的流量水平,防止系統(tǒng)被打垮,用以自我保護(hù)的一種方式。
設(shè)置 Type 為 OUT 一方面是為了保護(hù)第三方系統(tǒng),比如我們系統(tǒng)依賴了一個(gè)生成訂單號(hào)的接口,而這個(gè)接口是核心服務(wù),如果我們的服務(wù)是非核心應(yīng)用的話需要對(duì)他進(jìn)行限流保護(hù);另一方面也可以保護(hù)自己的系統(tǒng),假設(shè)我們的服務(wù)是核心應(yīng)用,而依賴的第三方應(yīng)用老是超時(shí),那這時(shí)可以通過設(shè)置依賴的服務(wù)的 rt 來進(jìn)行降級(jí),這樣就不至于讓第三方服務(wù)把我們的系統(tǒng)拖垮。
下圖描述了流量的類型和系統(tǒng)之間的關(guān)系:

連接控制臺(tái)
應(yīng)用接入 transport 模塊之后,我們主動(dòng)來訪問一次 /testSentinel 接口,順利的話,客戶端會(huì)主動(dòng)連接上控制臺(tái),并將自己的ip等信息發(fā)送給控制臺(tái),并且會(huì)與控制臺(tái)維持一個(gè)心跳。
現(xiàn)在我們在來訪問下控制臺(tái),看到客戶端已經(jīng)連接上來了,如下圖所示:

客戶端連接上dashboard之后,我們就可以為我們定義的資源配置規(guī)則了,有兩種方式可以配置規(guī)則:
- 在【流控規(guī)則】頁面中新增
- 在【簇點(diǎn)鏈路】中添加
我們可以在【流控規(guī)則】頁面中新增,點(diǎn)擊【流控規(guī)則】進(jìn)入頁面,如下圖所示:

在彈出框中,填寫資源名和單機(jī)閾值,其他的屬性保持默認(rèn)設(shè)置即可,如下圖所示:

點(diǎn)擊【新增】后,規(guī)則即生效了。
第二種方式就是在【簇點(diǎn)鏈路】的頁面中找到我們埋點(diǎn)的資源名,然后直接對(duì)該資源進(jìn)行增加流控規(guī)則的操作,如下圖所示:

上圖中右側(cè)的【+流控】的按鈕點(diǎn)擊后,彈出框與直接新增規(guī)則是一樣的,只是會(huì)自動(dòng)將資源名填充進(jìn)去,省去了我們設(shè)置的這一步。
驗(yàn)證效果
規(guī)則創(chuàng)建完成之后,我們就可以在【流控規(guī)則】頁面查詢到了,如下圖所示:

接著我們就可以來驗(yàn)證效果了,讓我們在瀏覽器中快速的刷新來請求 /testSentinel 這個(gè)接口,不出意外,應(yīng)該會(huì)看到如下圖所示的情況:

說明我們設(shè)置的流控規(guī)則生效了,請求被 block 了。
現(xiàn)在我們再到控制臺(tái)的【實(shí)時(shí)監(jiān)控】頁面查詢下,剛剛我們的一頓瘋狂請求應(yīng)該有很多都被 block 了,通過的 qps 應(yīng)該維持在2以下,如下圖所示:

原理
我們知道 sentinel 的核心就是圍繞著幾件事:資源的定義,規(guī)則的配置,代碼中埋點(diǎn)。
而且這些事在 sentinel-core 中都有能力實(shí)現(xiàn),也對(duì)外暴露了相應(yīng)的 http 接口方便我們查看 sentinel 中的相關(guān)數(shù)據(jù)。
CommandCenter
sentinel-core 在第一次規(guī)則被觸發(fā)的時(shí)候,啟動(dòng)了一個(gè) CommandCenter,也就是我們引入的 sentinel-transport-simple-http 依賴中被引入的實(shí)現(xiàn)類:SimpleHttpCommandCenter。
這個(gè) SimpleHttpCommandCenter 類中啟動(dòng)了兩個(gè)線程池:主線程池和業(yè)務(wù)線程池。
主線程池啟動(dòng)了一個(gè) ServerSocket 來監(jiān)聽默認(rèn)的 8719 端口,如果端口被占用,會(huì)自動(dòng)嘗試獲取下一個(gè)端口,嘗試3次。
業(yè)務(wù)線程池主要是用來處理 ServerSocket 接收到的數(shù)據(jù)。
將不重要的代碼省略掉之后,具體的代碼如下所示:
public class SimpleHttpCommandCenter implements CommandCenter {
// 省略初始化
private ExecutorService executor;
private ExecutorService bizExecutor;
@Override
public void start() throws Exception {
Runnable serverInitTask = new Runnable() {
int port;
{
try {
port = Integer.parseInt(TransportConfig.getPort());
} catch (Exception e) {
port = DEFAULT_PORT;
}
}
@Override
public void run() {
// 獲取可用的端口用以創(chuàng)建一個(gè)ServerSocket
ServerSocket serverSocket = getServerSocketFromBasePort(port);
if (serverSocket != null) {
// 在主線程中啟動(dòng)ServerThread用以接收socket請求
executor.submit(new ServerThread(serverSocket));
// 省略部分代碼
} else {
CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");
}
executor.shutdown();
}
};
new Thread(serverInitTask).start();
}
class ServerThread extends Thread {
private ServerSocket serverSocket;
ServerThread(ServerSocket s) {
this.serverSocket = s;
}
@Override
public void run() {
while (true) {
Socket socket = null;
try {
socket = this.serverSocket.accept();
setSocketSoTimeout(socket);
// 將接收到的socket封裝到HttpEventTask中由業(yè)務(wù)線程去處理
HttpEventTask eventTask = new HttpEventTask(socket);
bizExecutor.submit(eventTask);
} catch (Exception e) {
// 省略部分代碼
}
}
}
}
}
具體的情況如下圖所示:

HTTP接口
SimpleHttpCommandCenter 啟動(dòng)了一個(gè) ServerSocket 來監(jiān)聽8719端口,也對(duì)外提供了一些 http 接口用以操作 sentinel-core 中的數(shù)據(jù),包括查詢|更改規(guī)則,查詢節(jié)點(diǎn)狀態(tài)等。
PS:控制臺(tái)也是通過這些接口與 sentinel-core 進(jìn)行數(shù)據(jù)交互的!
提供這些服務(wù)的是一些 CommandHandler 的實(shí)現(xiàn)類,每個(gè)類提供了一種能力,這些類是在 sentinel-transport-common 依賴中提供的,如下圖所示:

查詢規(guī)則
運(yùn)行下面命令,則會(huì)返回現(xiàn)有生效的規(guī)則:
curl http://localhost:8719/getRules?type=<XXXX>
其中,type有以下取值:
-
flow以 JSON 格式返回現(xiàn)有的限流規(guī)則; -
degrade則返回現(xiàn)有生效的降級(jí)規(guī)則列表; -
system則返回系統(tǒng)保護(hù)規(guī)則。
更改規(guī)則
同時(shí)也可以通過下面命令來修改已有規(guī)則:
curl http://localhost:8719/setRules?type=<XXXX>&data=<DATA>
其中,type 可以輸入 flow、degrade 等方式來制定更改的規(guī)則種類,data 則是對(duì)應(yīng)的 JSON 格式的規(guī)則。
其他的接口不再一一詳細(xì)舉例了,有需要的大家可以自行查看源碼了解。
