準備工作
基于sentine-1.4.2扑馁,在dashboard想要更好的查看集群限流相關配置百侧,需要一些小修改
你也可以直接從github上拉取我的代碼: git@github.com:spilledyear/Sentinel.git,對應的分支是 1.4.2
-
開啟集群規(guī)則界面
修改:resources/app/views/flow_v1.html棍厂,將其中和集群相關的按鈕打開,最終效果如下:
-
規(guī)則持久化
dashboard默認沒有對規(guī)則持久化,但在集群規(guī)則界面添加的規(guī)則锦援,其實是可以持久化到nacos的,只需要做一些簡單的修改剥悟。將dashboard模塊test目錄下的com.alibaba.csp.sentinel.dashboard.rule.nacos類拷貝到java目錄灵寺,如下:
然后修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2文件,將其中的ruleProvider和rulePublisher改成剛剛新增的那兩個
- 啟動nacos
nacos的部署就不過多介紹区岗,可以看官方文檔 nacos手冊
下面將從以下幾個方面簡單介紹集群限流
啟動測試案例
以嵌入式模式為例略板,在源碼的sentinel-demo模塊種,已經(jīng)準備好了相關測試案例慈缔,啟動兩個實例:ClusterDemoApplication叮称,啟動參數(shù)分別如下:
- 實例一
-Dproject.name=clusterapp -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8080
- 實例二
-Dproject.name=clusterapp -Dserver.port=8082 -Dcsp.sentinel.dashboard.server=localhost:8080
為了能夠方便的修改規(guī)則信息,直觀的觀察效果藐鹤,需要啟動控制臺
- 啟動控制臺
-Dserver.port=8080
此時通過localhost:8080訪問控制臺瓤檐,還無法看到任何應用信息,因為此時還沒有任何的服務調(diào)用娱节,通過以下快捷方式訪問兩個服務實例
curl localhost:8081/hello/luo
curl localhost:8082/hello/luo
這時候查看機器列表
菜單選項挠蛉,發(fā)現(xiàn)已經(jīng)有兩個實例了(端口區(qū)分):
但這時候還沒有server和client的概念,需要簡單配置:點擊集群限流
菜單項括堤,然后點擊右上角的"新增Toeken Server"
從中選取一臺server碌秸,另一臺指定為client,即:
此時悄窃,再查看集群流控
菜單項讥电,發(fā)現(xiàn)已經(jīng)有了server信息,通過連接詳情發(fā)現(xiàn)已有兩個連接轧抗,這是這是嵌入式恩敌,server端本身也是一個應用實例
規(guī)則的推送
新建規(guī)則
以上準備工作完成之后,下面可以新建資源了横媚。為了觀察限流效果光差纠炮,新建的資源名與測試案例中的資源名一致:點擊流控規(guī)則
菜單項月趟,然后點擊右上角的回到集群界面
:
為什么這里要在集群界面新建規(guī)則呢?上面已經(jīng)說過了恢口,針對集群規(guī)則界面已經(jīng)做了修改孝宗,規(guī)則可以持久化到nacos配置中心
然后新建一個規(guī)則,有關于規(guī)則的使用這里就不展開了
以上操作完成之后耕肩,會發(fā)現(xiàn)nacos中多了一條配置因妇,具體內(nèi)容就是規(guī)則的具體信息
查看限流效果
通過jmeter測試,讓兩個請求都分別請求不同的實例各20次:
發(fā)現(xiàn)每個請求都通過了10次猿诸,加起來剛好20次婚被,多出來的請求拋出了FlowException異常,執(zhí)行了blockHandler對應的邏輯梳虽,初步符合集群限流的效果
推送原理
在保存規(guī)則信息的時候址芯,發(fā)現(xiàn)請求了以下接口:http://localhost:8080/v2/flow/rule/29
對應FlowControllerV2中的apiUpdateFlowRule,主要邏輯如下:
- 將規(guī)則信息更新到dashboard的內(nèi)存中窜觉,用于界面展示谷炸,這一部分主要和InMemoryRuleRepositoryAdapter的save方法相關;
- 推送規(guī)則信息到nacos注冊中心禀挫,這一部分和FlowRuleNacosPublisher相關淑廊;
如果dashboard使用了nacos持久化規(guī)則,對應的,在嵌入式模式下應該也會在server和client端使用NacosDatasource作為數(shù)據(jù)源特咆,對應的源碼在sentinel-datasource-nacos模塊的NacosDataSource類中:
public NacosDataSource(final Properties properties, final String groupId, final String dataId,Converter<String, T> parser) {
super(parser);
this.configListener = new Listener() {
@Override
public Executor getExecutor() {
return pool;
}
@Override
public void receiveConfigInfo(final String configInfo) {
RecordLog.info(String.format("[NacosDataSource] New property value received for (properties: %s) (dataId: %s, groupId: %s): %s",
properties, dataId, groupId, configInfo));
T newValue = NacosDataSource.this.parser.convert(configInfo);
// Update the new value to the property.
getProperty().updateValue(newValue);
}
};
initNacosListener();
loadInitialConfig();
}
從上可以看出季惩,當規(guī)則信息更新了的時候,會同步到sentinel的內(nèi)存結構中腻格。
這里有一個小問題画拾,如果沒有使用注冊中心,規(guī)則將怎么進行推送菜职?
答案其實在FlowRuleApiPublisher中青抛,如果沒有使用注冊中心,將通過SentinelApiClient發(fā)送http請求酬核,將規(guī)則推送到各個服務實例蜜另,服務實例收到規(guī)則信息之后再加載到sentinel相關的內(nèi)存結構,核心代碼如下:
for (MachineInfo machine : set) {
if (!MachineUtils.isMachineHealth(machine)) {
continue;
}
// TODO: parse the results
sentinelApiClient.setFlowRuleOfMachine(app, machine.getIp(), machine.getPort(), rules);
}
如果針對這個問題再次延申嫡意,還會有一些疑問举瑰,SentinelApiClient怎么就知道要將規(guī)則信息發(fā)送到哪里呢?哪個端口蔬螟?這一部分肯定是sentine為我們隱藏起來了此迅。
第一個問題,哪個端口?這個端口其實是commandPort耸序,即應用端暴露給 Sentinel 控制臺的端口忍些,ip@commandPort,其實就是界面上看到的那兩個坎怪,分別為8720和8721罢坝;
第二個問題,隱藏了哪些細節(jié)搅窿?其實就是隱藏了暴露端口的這部分細節(jié)炸客,都在sentinel-transport模塊中,提供了兩種實現(xiàn)方式戈钢。
方式一,sentinel-transport-simple-http模塊中是尔,通過ServerSocket方式暴露殉了,對應的核心類為SimpleHttpCommandCenter,核心代碼如下
@Override
public void run() {
boolean success = false;
ServerSocket serverSocket = getServerSocketFromBasePort(port);
if (serverSocket != null) {
CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort());
socketReference = serverSocket;
executor.submit(new ServerThread(serverSocket));
success = true;
port = serverSocket.getLocalPort();
} else {
CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");
}
if (!success) {
port = PORT_UNINITIALIZED;
}
TransportConfig.setRuntimePort(port);
executor.shutdown();
}
- 方式二拟枚,在sentinel-transport-netty-http模塊中薪铜,通過netty暴露,核心類是NettyHttpCommandCenter恩溅,核心代碼如下:
@Override
public void start() throws Exception {
pool.submit(new Runnable() {
@Override
public void run() {
try {
server.start();
} catch (Exception ex) {
RecordLog.info("Start netty server error", ex);
ex.printStackTrace();
System.exit(-1);
}
}
});
}
內(nèi)部通過SPI機制加載隔箍,引用了哪個模塊就會使用哪種機制。
節(jié)點發(fā)現(xiàn)
dashboard是如何獲取節(jié)點信息并將其展示在界面上的脚乡?核心原理還是在sentinel-transport模塊中蜒滩,不管是在sentinel-transport-simple-http還是sentinel-transport-netty-http中,都會向dashboard發(fā)送心跳上報當前節(jié)點信息奶稠,請求地址即:
dashboardIp:port/registry/machine俯艰,這里代表 localhost:8080/registry/machine
dashboar收到請求后會將節(jié)點信息保存到內(nèi)存中。
有關于這一部分锌订,sentinel-transport-simple-http模塊中的核心類是SimpleHttpHeartbeatSender竹握;sentinel-transport-netty-http模塊中的核心類是HttpHeartbeatSender;
dashboard相關的邏輯如下
public Result<?> receiveHeartBeat(String app, Long version, String v, String hostname, String ip, Integer port) {
if (app == null) {
app = MachineDiscovery.UNKNOWN_APP_NAME;
}
if (ip == null) {
return Result.ofFail(-1, "ip can't be null");
}
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
if (port == -1) {
logger.info("Receive heartbeat from " + ip + " but port not set yet");
return Result.ofFail(-1, "your port not set yet");
}
String sentinelVersion = StringUtil.isEmpty(v) ? "unknown" : v;
long timestamp = version == null ? System.currentTimeMillis() : version;
try {
MachineInfo machineInfo = new MachineInfo();
machineInfo.setApp(app);
machineInfo.setHostname(hostname);
machineInfo.setIp(ip);
machineInfo.setPort(port);
machineInfo.setTimestamp(new Date(timestamp));
machineInfo.setVersion(sentinelVersion);
appManagement.addMachine(machineInfo);
return Result.ofSuccessMsg("success");
} catch (Exception e) {
logger.error("Receive heartbeat error", e);
return Result.ofFail(-1, e.getMessage());
}
}
所以辆飘,整個過程看起來是這樣子的:
配置項
- NameSpace
NameSpace主要是用于區(qū)分不同的應用啦辐,其實在嵌入式的模式下作用不大,嵌入式模式下一般是一種對等結構蜈项,這時候NameSpace一般就是一個芹关,即:應用名。只有在獨立模式下才能體現(xiàn)它的作用:區(qū)分不同的應用紧卒。
// 如果不配置默認default
ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("cluster-" + appId));
- Supplier
主要作用就是就是根據(jù)NameSpace找到一個DynamicSentinelProperty充边,其實在嵌入式模式下,一般也就是寫死一個DynamicSentinelProperty,因為這時候的NameSpace也就只有一個
// 集群限流規(guī)則配置浇冰,根據(jù)namespace動態(tài)生成Supplier贬媒,其實子
ClusterFlowRuleManager.setPropertySupplier(dataSource.getClusterFlowSupplier());
- ServerTransportProperty
作用比較大,針對server端肘习,會根據(jù)ServerTransportProperty中的信息在server端通過netty開啟一個端口际乘,用于和client交互
// 配置ServerTransportConfig:port、idleSeconds
ClusterServerConfigManager.registerServerTransportProperty(dataSource.getServerTransportConfigProperty());
- ClientConfigProperty
client端的相關配置漂佩,其實只有一個屬性:請求server端的超時時間(requestTimeout)
// 為client設置requestTimeout
ClusterClientConfigManager.registerClientConfigProperty(dataSource.getClusterClientConfigProperty());
- ServerAssignProperty
client端的相關配置脖含,里面保存的是server端的相關信息:server的host和port
// 為client設置server的host和port,即serverHost投蝉、serverPort
ClusterClientConfigManager.registerServerAssignProperty(dataSource.getClusterClientAssignConfigProperty());
- ClusterStateManager
在嵌入式模式下养葵,可以通過API來改變client和server的身份,大致邏輯就是:將server中的那個netty服務stop瘩缆,然后根據(jù)新的配置在client開啟一個新的netty服務(注意关拒,服務開啟成功之后,client就轉(zhuǎn)變成server了)
// 用于設置mode庸娱,設置0 代表client着绊, 設置1代表 server
ClusterStateManager.registerProperty(dataSource.getClusterStateProperty());