一、熱點(diǎn)
熱點(diǎn)就是訪問(wèn)非常頻繁的參數(shù),例如商城系統(tǒng)中首頁(yè)的數(shù)據(jù);熱點(diǎn)參數(shù)就比如是商城系統(tǒng)商品的id。
那么 Sentinel 是怎么知道哪些參數(shù)是熱點(diǎn),哪些參數(shù)不是熱點(diǎn)的呢?Sentinel 利用 LRU 策略,結(jié)合底層的滑動(dòng)窗口機(jī)制來(lái)實(shí)現(xiàn)熱點(diǎn)參數(shù)統(tǒng)計(jì)。LRU 策略可以統(tǒng)計(jì)單位時(shí)間內(nèi),最近最常訪問(wèn)的熱點(diǎn)參數(shù),而滑動(dòng)窗口機(jī)制可以幫助統(tǒng)計(jì)每個(gè)參數(shù)的 qps。
說(shuō)簡(jiǎn)單點(diǎn)就是,Sentinel 會(huì)先檢查出提交過(guò)來(lái)的參數(shù),哪些是熱點(diǎn)的參數(shù),然后在應(yīng)用熱點(diǎn)參數(shù)的限流規(guī)則,將qps 超過(guò)設(shè)定閾值的請(qǐng)求給 block 掉,整個(gè)過(guò)程如下圖所示:

二、如何應(yīng)用
1、引入依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>x.y.z</version>
</dependency>
2、定義資源
使用熱點(diǎn)限流時(shí),定義資源和普通限流的操作方式是一致的。例如我們可以定義資源名為:
/**
* 資源名
*/
private static String RESOURCE_NAME = "freqParam";
3、定義規(guī)則
定義好資源之后,就可以來(lái)定義規(guī)則了,我們還是先用簡(jiǎn)單的硬編碼的方式來(lái)演示,實(shí)際的使用過(guò)程中還是要通過(guò)控制臺(tái)來(lái)定義規(guī)則的。
熱點(diǎn)參數(shù)的規(guī)則是通過(guò) ParamFlowRule 來(lái)定義的,跟流控的規(guī)則類 FlowRule 差不多,具體的屬性如下表所示:

定義好規(guī)則之后,可以通過(guò) ParamFlowRuleManager 的 loadRules 方法更新熱點(diǎn)參數(shù)規(guī)則,如下所示:
private void init() {
// 定義熱點(diǎn)限流的規(guī)則,對(duì)第一個(gè)參數(shù)設(shè)置 qps 限流模式,閾值為5
ParamFlowRule rule = new ParamFlowRule(RESOURCE_NAME)
.setParamIdx(0)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(5);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
4、埋點(diǎn)
我們定義好資源,也定義好規(guī)則了,最后一步就是在代碼中埋點(diǎn)來(lái)使應(yīng)用熱點(diǎn)限流的規(guī)則了。
那么如何傳入對(duì)應(yīng)的參數(shù)來(lái)讓 Sentinel 進(jìn)行統(tǒng)計(jì)呢?我們可以通過(guò) SphU 類里面幾個(gè) entry 重載方法來(lái)傳入
其中最后的一串 args 就是要傳入的參數(shù),有多個(gè)就按照次序依次傳入。
還是通過(guò)一個(gè)簡(jiǎn)單的 Controller 方法來(lái)進(jìn)行埋點(diǎn),如下所示:
/**
* 熱點(diǎn)參數(shù)限流
* 構(gòu)造不同的uid的值,并且以不同的頻率來(lái)請(qǐng)求該方法,查看效果
*/
@GetMapping("/freqParamFlow")
public String freqParamFlow(@RequestParam("uid") Long uid, @RequestParam("ip") Long ip) {
Entry entry = null;
String retVal;
try{
// 只對(duì)參數(shù) uid 的值進(jìn)行限流,參數(shù) ip 的值不進(jìn)行限制
entry = SphU.entry(RESOURCE_NAME, EntryType.IN,1,uid);
retVal = "passed";
}catch(BlockException e){
retVal = "blocked";
}finally {
if(entry!=null){
entry.exit();
}
}
return retVal;
}
5、查看效果


sentinel控制臺(tái)展示結(jié)果:

完整代碼:
package com.gzf.sentinel.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
/**
* @program: sentinel-tutorial
* @description: 集群熱點(diǎn)參數(shù)限流規(guī)則
* @author: Gaozf
* @create: 2020-07-24 16:58
**/
@RestController
public class ParamFlowController {
private static String RESOURCE_NAME = "freqParam";
private static final String APP_NAME = "single";
private static final String REMOTE_ADDRESS = "localhost";
private static final String GROUP_ID = "DEFAULT_GROUP";
private static final String PARAM_FLOW_POSTFIX = "-param-rules";
public ParamFlowController(){
//registerClusterFlowRuleProperty();
init();
}
private void init() {
// 定義熱點(diǎn)限流的規(guī)則,對(duì)第一個(gè)參數(shù)設(shè)置 qps 限流模式,閾值為5
ParamFlowRule rule = new ParamFlowRule(RESOURCE_NAME)
.setParamIdx(0)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(5);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
/**
* 注冊(cè)動(dòng)態(tài)規(guī)則Property
* 當(dāng)client與Server連接中斷,退化為本地限流時(shí)需要用到的該規(guī)則
*/
private void registerClusterFlowRuleProperty(){
// 使用 Nacos 數(shù)據(jù)源作為配置中心,需要在 REMOTE_ADDRESS 上啟動(dòng)一個(gè) Nacos 的服務(wù)
ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(REMOTE_ADDRESS, GROUP_ID, APP_NAME+PARAM_FLOW_POSTFIX,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
// 為集群客戶端注冊(cè)動(dòng)態(tài)規(guī)則源
FlowRuleManager.register2Property(ds.getProperty());
}
/**
* 熱點(diǎn)參數(shù)限流
* 構(gòu)造不同的uid的值,并且以不同的頻率來(lái)請(qǐng)求該方法,查看效果
*/
@GetMapping("/freqParamFlow")
public String freqParamFlow(@RequestParam("uid") Long uid, @RequestParam("ip") Long ip) {
Entry entry = null;
String retVal;
try{
// 只對(duì)參數(shù) uid 的值進(jìn)行限流,參數(shù) ip 的值不進(jìn)行限制
entry = SphU.entry(RESOURCE_NAME, EntryType.IN,1,uid);
retVal = "passed";
}catch(BlockException e){
retVal = "blocked";
}finally {
if(entry!=null){
entry.exit();
}
}
return retVal;
}
@GetMapping("/freqParamFlowTwo")
public String freqParamFlowTwo(@RequestParam("uid") Long uid, @RequestParam("ip") Long ip) {
Entry entry = null;
String retVal;
try{
// 只對(duì)參數(shù) uid 的值進(jìn)行限流,參數(shù) ip 的值不進(jìn)行限制
entry = SphU.entry(RESOURCE_NAME, EntryType.IN,1);
retVal = "passed";
}catch(BlockException e){
retVal = "blocked";
}finally {
if(entry!=null){
entry.exit();
}
}
return retVal;
}
}