新聞中心
我猜測(cè)很重要的原因可能就是Sentinel關(guān)于這塊做的并不完善,而且從官方的Issue中能看出來(lái),其實(shí)官方對(duì)于這塊后續(xù)并沒(méi)有計(jì)劃去做的更好。

在網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站過(guò)程中,需要針對(duì)客戶的行業(yè)特點(diǎn)、產(chǎn)品特性、目標(biāo)受眾和市場(chǎng)情況進(jìn)行定位分析,以確定網(wǎng)站的風(fēng)格、色彩、版式、交互等方面的設(shè)計(jì)方向。成都創(chuàng)新互聯(lián)公司還需要根據(jù)客戶的需求進(jìn)行功能模塊的開(kāi)發(fā)和設(shè)計(jì),包括內(nèi)容管理、前臺(tái)展示、用戶權(quán)限管理、數(shù)據(jù)統(tǒng)計(jì)和安全保護(hù)等功能。
那么廢話不多說(shuō),在此之前,肯定要先說(shuō)下關(guān)于Sentinel集群限流方面的原理,沒(méi)有原理一切都是空中樓閣。
集群限流原理
原理這方面比較好解釋,就是在原本的限流規(guī)則中加了一個(gè)clusterMode參數(shù),如果是true的話,那么會(huì)走集群限流的模式,反之就是單機(jī)限流。
如果是集群限流,判斷身份是限流客戶端還是限流服務(wù)端,客戶端則和服務(wù)端建立通信,所有的限流都通過(guò)和服務(wù)端的交互來(lái)達(dá)到效果。
對(duì)于Sentinel集群限流,包含兩種模式,內(nèi)嵌式和獨(dú)立式。
內(nèi)嵌式
什么是內(nèi)嵌式呢,簡(jiǎn)單來(lái)說(shuō),要限流那么必然要有個(gè)服務(wù)端去處理多個(gè)客戶端的限流請(qǐng)求,對(duì)于內(nèi)嵌式來(lái)說(shuō)呢,就是整個(gè)微服務(wù)集群內(nèi)部選擇一臺(tái)機(jī)器節(jié)點(diǎn)作為限流服務(wù)端(Sentinel把這個(gè)叫做token-server),其他的微服務(wù)機(jī)器節(jié)點(diǎn)作為限流的客戶端(token-client),這樣的做法有缺點(diǎn)也有優(yōu)點(diǎn)。
限流-嵌入式
首先說(shuō)優(yōu)點(diǎn):這種方式部署不需要獨(dú)立部署限流服務(wù)端,節(jié)省獨(dú)立部署服務(wù)端產(chǎn)生的額外服務(wù)器開(kāi)支,降低部署和維護(hù)復(fù)雜度。
再說(shuō)缺點(diǎn),缺點(diǎn)的話也可以說(shuō)是整個(gè)Sentinel在集群限流這方面做得不夠好的問(wèn)題。
先說(shuō)第一個(gè)缺點(diǎn):無(wú)自動(dòng)故障轉(zhuǎn)移機(jī)制。
無(wú)論是內(nèi)嵌式還是獨(dú)立式的部署方案,都無(wú)法做到自動(dòng)的故障轉(zhuǎn)移。
所有的server和client都需要事先知道IP的請(qǐng)求下做出配置,如果server掛了,需要手動(dòng)的修改配置,否則集群限流會(huì)退化成單機(jī)限流。
比如你的交易服務(wù)有3臺(tái)機(jī)器A\B\C,其中A被手動(dòng)設(shè)置為server,B\C則是作為client,當(dāng)A服務(wù)器宕機(jī)之后,需要手動(dòng)修改B\C中一臺(tái)作為server,否則整個(gè)集群的機(jī)器都將退化回單機(jī)限流的模式。
但是,如果client掛了,則是不會(huì)影響到整個(gè)集群限流的,比如B掛了,那么A和C將會(huì)繼續(xù)組成集群限流。
如果B再次重啟成功,那么又會(huì)重新加入到整個(gè)集群限流當(dāng)中來(lái),因?yàn)闀?huì)有一個(gè)自動(dòng)重連的機(jī)制,默認(rèn)的時(shí)間是N*2秒,逐漸遞增的一個(gè)時(shí)間。
這是想用Sentinel做集群限流并且使用內(nèi)嵌式需要考慮的問(wèn)題,要自己去實(shí)現(xiàn)自動(dòng)故障轉(zhuǎn)移的機(jī)制,當(dāng)然,server節(jié)點(diǎn)選舉也要自己實(shí)現(xiàn)了。
對(duì)于這個(gè)問(wèn)題,官方提供了可以修改server/client的API接口,另外一個(gè)就是可以基于動(dòng)態(tài)的數(shù)據(jù)源配置方式,這個(gè)我們后面再談。
第二個(gè)缺點(diǎn):適用于單微服務(wù)集群內(nèi)部限流。
這個(gè)其實(shí)也是顯而易見(jiàn)的道理,都內(nèi)部選舉一臺(tái)作為server去限流了,如果還跨多個(gè)微服務(wù)的話,顯然是不太合理的行為,現(xiàn)實(shí)中這種情況肯定也是非常少見(jiàn)的了,當(dāng)然你非要想跨多個(gè)微服務(wù)集群也不是不可以,只要你開(kāi)心就好。
第三個(gè)缺點(diǎn):server節(jié)點(diǎn)的機(jī)器性能會(huì)受到一定程度的影響。
這個(gè)肯定也比較好理解的,作為server去限流,那么其他的客戶端肯定要和server去通信才能做到集群限流啊,對(duì)不對(duì),所以一定程度上肯定會(huì)影響到server節(jié)點(diǎn)本身服務(wù)的性能,但是我覺(jué)得問(wèn)題不大,就當(dāng)server節(jié)點(diǎn)多了一個(gè)流量比較大的接口好了。
具體上會(huì)有多大的影響,我沒(méi)有實(shí)際對(duì)這塊做出實(shí)際的測(cè)試,如果真的流量非常大,需要實(shí)際測(cè)試一下這方面的問(wèn)題。
我認(rèn)為影響還是可控的,本身server和client基于netty通信,通信的內(nèi)容其實(shí)也非常的小。
獨(dú)立式
說(shuō)完內(nèi)嵌式的這些點(diǎn),然后再說(shuō)獨(dú)立式,也非常好理解,就是單獨(dú)部署一臺(tái)機(jī)器作為限流服務(wù)端server,就不在本身微服務(wù)集群內(nèi)部選一臺(tái)作為server了。
限流-獨(dú)立式
很明顯,優(yōu)點(diǎn)就是解決了上面的缺點(diǎn)。
- 不會(huì)和內(nèi)嵌式一樣,影響到server節(jié)點(diǎn)的本身性能
- 可以適用于跨多個(gè)微服務(wù)之間的集群限流
優(yōu)點(diǎn)可以說(shuō)就是解決了內(nèi)嵌式的兩個(gè)缺點(diǎn),那么缺點(diǎn)也來(lái)了,這同樣也是Sentinel本身并沒(méi)有幫助我們?nèi)ソ鉀Q的問(wèn)題。
缺點(diǎn)一:需要獨(dú)立部署,會(huì)產(chǎn)生額外的資源(錢)和運(yùn)維復(fù)雜度
缺點(diǎn)二:server默認(rèn)是單機(jī),需要自己實(shí)現(xiàn)高可用方案
缺點(diǎn)二很致命啊,官方的server實(shí)現(xiàn)默認(rèn)就是單機(jī)的,單點(diǎn)問(wèn)題大家懂的都懂,自己實(shí)現(xiàn)高可用,我真的是有點(diǎn)服了。
這么說(shuō)Sentinel這個(gè)集群限流就是簡(jiǎn)單的實(shí)現(xiàn)了一下,真正復(fù)雜的部分他都沒(méi)管,你可以這么理解。
run起來(lái)
那基本原理大概了解之后,還是要真正跑起來(lái)看看效果的,畢竟開(kāi)頭我就說(shuō)了,網(wǎng)上這方面真的是感覺(jué)啥也搜不到,下面以嵌入式集群的方式舉例。
無(wú)論集群限流還是單機(jī)限流的方式,官方都支持寫死配置和動(dòng)態(tài)數(shù)據(jù)源的配置方式,寫的話下面的代碼中也都有,被我注釋掉了,至于動(dòng)態(tài)數(shù)據(jù)源的配置,會(huì)基于Apollo來(lái)實(shí)現(xiàn)。
理解一下動(dòng)態(tài)數(shù)據(jù)源的配置方式,基于這個(gè)我們可以實(shí)現(xiàn)限流規(guī)則的動(dòng)態(tài)刷新,還有重點(diǎn)的一點(diǎn)可以做到基于修改配置方式的半自動(dòng)故障轉(zhuǎn)移。
動(dòng)態(tài)數(shù)據(jù)源支持推和拉兩種方式,比如文件系統(tǒng)和Eureka就是拉取的方式,定時(shí)讀取文件內(nèi)容的變更,Eureka則是建立HTTP連接,定時(shí)獲取元數(shù)據(jù)的變更。
推送的方式主要是基于事件監(jiān)聽(tīng)機(jī)制,比如Apollo和Nacos,Redis官方則是基于Pub/Sub來(lái)實(shí)現(xiàn),默認(rèn)的實(shí)現(xiàn)方式是基于Lettuce,如果想用其他的客戶端要自己實(shí)現(xiàn)。
限流-集群工作模式
首先,該引入的包還是引入。
com.alibaba.csp
sentinel-annotation-aspectj
1.8.4
com.alibaba.csp
sentinel-transport-simple-http
1.8.4
com.alibaba.csp
sentinel-cluster-client-default
1.8.4
com.alibaba.csp
sentinel-cluster-server-default
1.8.4
com.alibaba.csp
sentinel-datasource-apollo
1.8.4
實(shí)現(xiàn)SPI,在resources目錄的META-INF/services下新增名為com.alibaba.csp.sentinel.init.InitFunc的文件,內(nèi)容寫上我們自己實(shí)現(xiàn)的類名,比如我的com.irving.demo.init.DemoClusterInitFunc。
實(shí)現(xiàn)InitFunc接口,重寫init方法,代碼直接貼出來(lái),這里整體依賴的是Apollo的配置方式,注釋的部分是我在測(cè)試的時(shí)候?qū)懰来a的配置方式,也是可以用的。
public class DemoClusterInitFunc implements InitFunc {
private final String namespace = "application";
private final String ruleKey = "demo_sentinel";
private final String ruleServerKey = "demo_cluster";
private final String defaultRuleValue = "[]";
@Override
public void init() throws Exception {
// 初始化 限流規(guī)則
initDynamicRuleProperty();
//初始化 客戶端配置
initClientConfigProperty();
// 初始化 服務(wù)端配置信息
initClientServerAssignProperty();
registerClusterRuleSupplier();
// token-server的傳輸規(guī)則
initServerTransportConfigProperty();
// 初始化 客戶端和服務(wù)端狀態(tài)
initStateProperty();
}
/**
* 限流規(guī)則和熱點(diǎn)限流規(guī)則配置
*/
private void initDynamicRuleProperty() {
ReadableDataSource> ruleSource = new ApolloDataSource<>(namespace, ruleKey,
defaultRuleValue, source -> JSON.parseObject(source, new TypeReference>() {
}));
FlowRuleManager.register2Property(ruleSource.getProperty());
ReadableDataSource> paramRuleSource = new ApolloDataSource<>(namespace, ruleKey,
defaultRuleValue, source -> JSON.parseObject(source, new TypeReference>() {
}));
ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
}
/**
* 客戶端配置,注釋的部分是通過(guò)Apollo配置,只有一個(gè)配置我就省略了
*/
private void initClientConfigProperty() {
// ReadableDataSource clientConfigDs = new ApolloDataSource<>(namespace, ruleKey,
// defaultRuleValue, source -> JSON.parseObject(source, new TypeReference() {
// }));
// ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
ClusterClientConfig clientConfig = new ClusterClientConfig();
clientConfig.setRequestTimeout(1000);
ClusterClientConfigManager.applyNewConfig(clientConfig);
}
/**
* client->server 傳輸配置,設(shè)置端口號(hào),注釋的部分是寫死的配置方式
*/
private void initServerTransportConfigProperty() {
ReadableDataSource serverTransportDs = new ApolloDataSource<>(namespace, ruleServerKey,
defaultRuleValue, source -> {
List groupList = JSON.parseObject(source, new TypeReference>() {
});
ServerTransportConfig serverTransportConfig = Optional.ofNullable(groupList)
.flatMap(this::extractServerTransportConfig)
.orElse(null);
return serverTransportConfig;
});
ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty());
// ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig().setIdleSeconds(600).setPort(transPort));
}
private void registerClusterRuleSupplier() {
ClusterFlowRuleManager.setPropertySupplier(namespace -> {
ReadableDataSource> ds = new ApolloDataSource<>(this.namespace, ruleKey,
defaultRuleValue, source -> JSON.parseObject(source, new TypeReference>() {
}));
return ds.getProperty();
});
ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
ReadableDataSource> ds = new ApolloDataSource<>(this.namespace, ruleKey,
defaultRuleValue, source -> JSON.parseObject(source, new TypeReference>() {
}));
return ds.getProperty();
});
}
/**
* 服務(wù)端配置,設(shè)置server端口和IP,注釋的配置是寫死的方式,這個(gè)在服務(wù)端是不用配置的,只有客戶端需要配置用來(lái)連接服務(wù)端
*/
private void initClientServerAssignProperty() {
ReadableDataSource clientAssignDs = new ApolloDataSource<>(namespace, ruleServerKey,
defaultRuleValue, source -> {
List groupList = JSON.parseObject(source, new TypeReference>() {
});
ClusterClientAssignConfig clusterClientAssignConfig = Optional.ofNullable(groupList)
.flatMap(this::extractClientAssignment)
.orElse(null);
return clusterClientAssignConfig;
});
ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
// ClusterClientAssignConfig serverConfig = new ClusterClientAssignConfig();
// serverConfig.setServerHost("127.0.0.1");
// serverConfig.setServerPort(transPort);
// ConfigSupplierRegistry.setNamespaceSupplier(() -> "trade-center");
// ClusterClientConfigManager.applyNewAssignConfig(serverConfig);
}
private Optional extractClientAssignment(List groupList) {
ClusterGroupEntity tokenServer = groupList.stream().filter(x -> x.getState().equals(ClusterStateManager.CLUSTER_SERVER)).findFirst().get();
Integer currentMachineState = Optional.ofNullable(groupList).map(s -> groupList.stream().filter(this::machineEqual).findFirst().get().getState()).orElse(ClusterStateManager.CLUSTER_NOT_STARTED);
if (currentMachineState.equals(ClusterStateManager.CLUSTER_CLIENT)) {
String ip = tokenServer.getIp();
Integer port = tokenServer.getPort();
return Optional.of(new ClusterClientAssignConfig(ip, port));
}
return Optional.empty();
}
/**
* 初始化客戶端和服務(wù)端狀態(tài),注釋的也是寫死的配置方式
*/
private void initStateProperty() {
ReadableDataSource clusterModeDs = new ApolloDataSource<>(namespace, ruleServerKey,
defaultRuleValue, source -> {
List groupList = JSON.parseObject(source, new TypeReference>() {
});
Integer state = Optional.ofNullable(groupList).map(s -> groupList.stream().filter(this::machineEqual).findFirst().get().getState()).orElse(ClusterStateManager.CLUSTER_NOT_STARTED);
return state;
});
ClusterStateManager.registerProperty(clusterModeDs.getProperty());
// ClusterStateManager.applyState(ClusterStateManager.CLUSTER_SERVER);
}
private Optional extractServerTransportConfig(List groupList) {
return groupList.stream()
.filter(x -> x.getMachineId().equalsIgnoreCase(getCurrentMachineId()) && x.getState().equals(ClusterStateManager.CLUSTER_SERVER))
.findAny()
.map(e -> new ServerTransportConfig().setPort(e.getPort()).setIdleSeconds(600));
}
private boolean machineEqual(/*@Valid*/ ClusterGroupEntity group) {
return getCurrentMachineId().equals(group.getMachineId());
}
private String getCurrentMachineId() {
// 通過(guò)-Dcsp.sentinel.api.port=8719 配置, 默認(rèn)8719,隨后遞增
return HostNameUtil.getIp() + SEPARATOR + TransportConfig.getPort();
}
private static final String SEPARATOR = "@";
}
基礎(chǔ)類,定義配置的基礎(chǔ)信息。
@Data
public class ClusterGroupEntity {
private String machineId;
private String ip;
private Integer port;
private Integer state;
}
然后是Apollo中的限流規(guī)則的配置和server/client集群關(guān)系的配置。
需要說(shuō)明一下的就是flowId,這個(gè)是區(qū)分限流規(guī)則的全局唯一ID,必須要有,否則集群限流會(huì)有問(wèn)題。
thresholdType代表限流模式,默認(rèn)是0,代表單機(jī)均攤,比如這里count限流QPS=20,有3臺(tái)機(jī)器,那么集群限流閾值就是60,如果是1代表全局閾值,也就是count配置的值就是集群限流的上限。
demo_sentinel=[
{
"resource": "test_res", //限流資源名
"count": 20, //集群限流QPS
"clusterMode": true, //true為集群限流模式
"clusterConfig": {
"flowId": 111, //這個(gè)必須得有,否則會(huì)有問(wèn)題
"thresholdType": 1 //限流模式,默認(rèn)為0單機(jī)均攤,1是全局閾值
}
}
]
demo_cluster=[
{
"ip": "192.168.3.20",
"machineId": "192.168.3.20@8720",
"port": 9999, //server和client通信接口
"state": 1 //指定為server
},
{
"ip": "192.168.3.20",
"machineId": "192.168.3.20@8721",
"state": 0
},
{
"ip": "192.168.3.20",
"machineId": "192.168.3.20@8722",
"state": 0
}
]
OK,到這里代碼和配置都已經(jīng)OK,還需要跑起來(lái)Sentinel控制臺(tái),這個(gè)不用教,還有啟動(dòng)參數(shù)。
本地可以直接跑多個(gè)客戶端,注意修改端口號(hào):-Dserver.port=9100 -Dcsp.sentinel.api.port=8720這兩個(gè)一塊改,至于怎么連Apollo這塊我就省略了,自己整吧,公司應(yīng)該都有,不行的話用代碼里的寫死的方式也可以用。
-Dserver.port=9100 -Dcsp.sentinel.api.port=8720 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dcsp.sentinel.log.use.pid=true
因?yàn)橛辛髁恐罂刂婆_(tái)才能看到限流的情況,所以用官方給的限流測(cè)試代碼修改一下,放到Springboot啟動(dòng)類中,觸發(fā)限流規(guī)則的初始化。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
new FlowQpsDemo();
}
}
測(cè)試限流代碼:
public class FlowQpsDemo {
private static final String KEY = "test_res";
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 = 60 + 40;
public FlowQpsDemo() {
tick();
simulateTraffic();
}
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(<
分享名稱:聊聊Sentinel集群限流探索
文章分享:http://fisionsoft.com.cn/article/dhsdpjs.html


咨詢
建站咨詢
