基於sentine-1.4.2,在dashboard想要更好的查看集羣限流相關配置,須要一些小修改html
你也能夠直接從github上拉取個人代碼: git@github.com:spilledyear/Sentinel.git,對應的分支是 1.4.2java
開啓集羣規則界面 修改:resources/app/views/flow_v1.html,將其中和集羣相關的按鈕打開,最終效果以下: git
規則持久化 dashboard默認沒有對規則持久化,但在集羣規則界面添加的規則,實際上是能夠持久化到nacos的,只須要作一些簡單的修改。將dashboard模塊test目錄下的com.alibaba.csp.sentinel.dashboard.rule.nacos類拷貝到java目錄,以下: github
啓動nacos nacos的部署就不過多介紹,能夠看官方文檔 nacos手冊api
下面將從如下幾個方面簡單介紹集羣限流bash
以嵌入式模式爲例,在源碼的sentinel-demo模塊種,已經準備好了相關測試案例,啓動兩個實例:ClusterDemoApplication,啓動參數分別以下:app
-Dproject.name=clusterapp -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8080
複製代碼
-Dproject.name=clusterapp -Dserver.port=8082 -Dcsp.sentinel.dashboard.server=localhost:8080
複製代碼
爲了可以方便的修改規則信息,直觀的觀察效果,須要啓動控制檯curl
-Dserver.port=8080
複製代碼
此時經過localhost:8080訪問控制檯,還沒法看到任何應用信息,由於此時尚未任何的服務調用,經過如下快捷方式訪問兩個服務實例socket
curl localhost:8081/hello/luo
curl localhost:8082/hello/luo
複製代碼
這時候查看機器列表
菜單選項,發現已經有兩個實例了(端口區分): ide
但這時候尚未server和client的概念,須要簡單配置:點擊集羣限流
菜單項,而後點擊右上角的"新增Toeken Server"
此時,再查看集羣流控
菜單項,發現已經有了server信息,經過鏈接詳情發現已有兩個鏈接,這是這是嵌入式,server端自己也是一個應用實例
以上準備工做完成以後,下面能夠新建資源了。爲了觀察限流效果光差,新建的資源名與測試案例中的資源名一致:點擊流控規則
菜單項,而後點擊右上角的回到集羣界面
:
爲何這裏要在集羣界面新建規則呢?上面已經說過了,針對集羣規則界面已經作了修改,規則能夠持久化到nacos配置中心
而後新建一個規則,有關於規則的使用這裏就不展開了 ![]()
![]()
以上操做完成以後,會發現nacos中多了一條配置,具體內容就是規則的具體信息
經過jmeter測試,讓兩個請求都分別請求不一樣的實例各20次:
發現每一個請求都經過了10次,加起來恰好20次,多出來的請求拋出了FlowException異常,執行了blockHandler對應的邏輯,初步符合集羣限流的效果
複製代碼
在保存規則信息的時候,發現請求瞭如下接口:http://localhost:8080/v2/flow/rule/29 對應FlowControllerV2中的apiUpdateFlowRule,主要邏輯以下:
若是dashboard使用了nacos持久化規則,對應的,在嵌入式模式下應該也會在server和client端使用NacosDatasource做爲數據源,對應的源碼在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();
}
複製代碼
從上能夠看出,當規則信息更新了的時候,會同步到sentinel的內存結構中。
這裏有一個小問題,若是沒有使用註冊中心,規則將怎麼進行推送? 答案其實在FlowRuleApiPublisher中,若是沒有使用註冊中心,將經過SentinelApiClient發送http請求,將規則推送到各個服務實例,服務實例收到規則信息以後再加載到sentinel相關的內存結構,核心代碼以下:
for (MachineInfo machine : set) {
if (!MachineUtils.isMachineHealth(machine)) {
continue;
}
// TODO: parse the results
sentinelApiClient.setFlowRuleOfMachine(app, machine.getIp(), machine.getPort(), rules);
}
複製代碼
若是針對這個問題再次延申,還會有一些疑問,SentinelApiClient怎麼就知道要將規則信息發送到哪裏呢?哪一個端口?這一部分確定是sentine爲咱們隱藏起來了。
第一個問題,哪一個端口?這個端口實際上是commandPort,即應用端暴露給 Sentinel 控制檯的端口,ip@commandPort,其實就是界面上看到的那兩個,分別爲8720和8721;
第二個問題,隱藏了哪些細節?其實就是隱藏了暴露端口的這部分細節,都在sentinel-transport模塊中,提供了兩種實現方式。
方式一,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();
}
複製代碼
@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);
}
}
});
}
複製代碼
內部經過SPI機制加載,引用了哪一個模塊就會使用哪一種機制。
dashboard是如何獲取節點信息並將其展現在界面上的?核心原理仍是在sentinel-transport模塊中,不論是在sentinel-transport-simple-http仍是sentinel-transport-netty-http中,都會向dashboard發送心跳上報當前節點信息,請求地址即:
dashboardIp:port/registry/machine,這裏表明 localhost:8080/registry/machine
複製代碼
dashboar收到請求後會將節點信息保存到內存中。
有關於這一部分,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());
}
}
複製代碼
因此,整個過程看起來是這樣子的:
// 若是不配置默認default
ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("cluster-" + appId));
複製代碼
// 集羣限流規則配置,根據namespace動態生成Supplier,其實子
ClusterFlowRuleManager.setPropertySupplier(dataSource.getClusterFlowSupplier());
複製代碼
// 配置ServerTransportConfig:port、idleSeconds
ClusterServerConfigManager.registerServerTransportProperty(dataSource.getServerTransportConfigProperty());
複製代碼
// 爲client設置requestTimeout
ClusterClientConfigManager.registerClientConfigProperty(dataSource.getClusterClientConfigProperty());
複製代碼
// 爲client設置server的host和port,即serverHost、serverPort
ClusterClientConfigManager.registerServerAssignProperty(dataSource.getClusterClientAssignConfigProperty());
複製代碼
// 用於設置mode,設置0 表明client, 設置1表明 server
ClusterStateManager.registerProperty(dataSource.getClusterStateProperty());
複製代碼