在 Sentinel 中资源定义和规则配置是分离的。先通过 Sentinel API 给对应的业务逻辑定义资源(埋点),然后可以在需要的时候配置规则。

1,引入依赖包

com.alibaba.csp

sentinel-core

1.8.0

com.alibaba.csp

sentinel-annotation-aspectj

1.8.0

使用方式一:Java原生编码方式

步骤1:定义需要保护的资源

public String test(){
Entry entry = null;
try {
//定义资源保护的入口,同时指定资源名(用于与规则匹配)
entry = SphU.entry("sentinelApi");
//业务逻辑(这了调用了另一个方法)
return getMsg() ;
} catch (BlockException e) {
if(e instanceof FlowException){
//限流控制逻辑处理
return "限流了" ;
}
if(e instanceof DegradeException){
//熔断控制逻辑处理
return "降级了" ;
}
return "none" ;
} catch (Exception e){
//记录业务异常数
Tracer.trace(e) ;
return "exe" ;
}finally {
if (entry != null) {
entry.exit();
}
}
}

步骤2:定义资源保护规则

@Component
public class SentinelRuleConfiguration {
@PostConstruct
public void init() {
//限流规则
initFlowRule("sentinelApi") ;
//熔断降级规则
initDegradeRule("sentinelApi") ;
//添加时间监听
addEventObserver() ;
}
/**
*
* 流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,
* 当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
*
* 限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName) 的时候抛出 FlowException 异常。
* FlowException 是 BlockException 的子类,您可以捕捉 BlockException 来自定义被限流之后的处理逻辑。
*
* 流量控制规则 (FlowRule).
* 同一个资源可以同时有多个限流规则,检查规则时会依次检查。
* @param resourceName 资源名,资源名是限流规则的作用对象
* */
private static void initFlowRule(String resourceName) {
List rules = new ArrayList<>();
/*
* 配置策略1: 并发线程数控制【通常在调用端进行配置】
* 并发数控制用于保护业务线程池不被慢调用耗尽
* */
FlowRule thRule = new FlowRule(resourceName);
thRule.setCount(1); //限流阈值
thRule.setGrade(RuleConstant.FLOW_GRADE_THREAD); //限流阈值类型,QPS 模式(1)或并发线程数模式(0)。默认:QPS 模式
thRule.setLimitApp("default"); //流控针对的调用来源。默认:default,代表不区分调用来源
thRule.setClusterMode(false) ; //是否集群限流。默认为false
/*
* 流量控制的效果
* 1)直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式(该方式适用于对系统处理能力确切已知的情况下);
* 2)Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。
* 3)匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)让请求以均匀的速度通过,对应的是漏桶算法(该方式主要用于处理间隔性突发的流量,暂不支持 QPS > 1000 的场景)。
* */
thRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT) ;
rules.add(thRule);
/*
* 配置策略2:QPS流量控制
* 当 QPS 超过某个阈值的时候,则采取措施进行流量控制
* */
FlowRule qpsRule = new FlowRule(resourceName);
qpsRule.setCount(1); //限流阈值
qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS); //限流阈值类型,QPS 模式(1)或并发线程数模式(0)。默认:QPS 模式
qpsRule.setLimitApp("default"); //流控针对的调用来源。默认:default,代表不区分调用来源
qpsRule.setClusterMode(false) ; //是否集群限流。默认为false
/*
* 流量控制的效果
* 1)直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式(该方式适用于对系统处理能力确切已知的情况下);
* 2)Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。
* 3)匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)让请求以均匀的速度通过,对应的是漏桶算法(该方式主要用于处理间隔性突发的流量,暂不支持 QPS > 1000 的场景)。
* */
qpsRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) ;
/*
* 这里设置的等待处理时间较大, 让系统能平稳的处理所有的请求.
* 表示每一个请求的最长等待时间20s
* */
qpsRule.setMaxQueueingTimeMs(20 * 1000);
rules.add(qpsRule);
//定义流量控制规则
FlowRuleManager.loadRules(rules);
}
/**
* 熔断降级规则 (DegradeRule)
*

* 1:慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),

* 请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,

* 并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),

* 若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

*

* 注意:只会统计在单位统计时长(statIntervalMs)内已经响应的请求【这样的请求才能判断是否满足慢调用条件】,

* 如果实际请求的响应时长或设置的慢时长(count)都超出了statIntervalMs,那么将不可能启动熔断的作用

* ----------------------------------------------------

*

*2:异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

*

* 3:异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

* 注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。示例:

* */
private static void initDegradeRule(String resourceName) {
List rules = new ArrayList<>();
/*

* 配置策略1:慢调用比例

* 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断;

*

* 当资源的保护处于CLOSED状态时,当请求进来时开始计时,统计statIntervalMs时间范围内所有请求中有多大比例的请求

* 满足“慢请求”的条件(statIntervalMs时间范围内已经响应的才能判断是否为慢请求),如果满足则开启(OPEN)熔断,熔断时间结束后,接受一条请求通过,

* 如果不是慢请求,则切换到HALF_OPEN状态,然后切换到CLOSE状态;

* 所以:statIntervalMs > count
* */
DegradeRule srule = new DegradeRule(resourceName);
/*

* 熔断策略,支持慢调用比例/异常比例/异常数策略。

* 默认:慢调用比例

**/

srule.setGrade(RuleConstant.DEGRADE_GRADE_RT);

/*

* 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用,单位ms);

* 异常比例/异常数模式下为对应的阈值;

* 注意:该模式下,该值要小于“统计时长”,否则不会生效。

**/
srule.setCount(10);
/*

*熔断时长,单位为 s

**/
srule.setTimeWindow(10);
/*

* 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断。

* 默认值:5

* */
srule.setMinRequestAmount(5) ;
/*

* 统计时长(单位为 ms),如 60*1000 代表分钟级。

* 默认:1000 ms

* 注意与上面count值得联系,同时该时间段内的请求都会进入并参与统计。

* */

srule.setStatIntervalMs(1000) ;

/*

*慢调用比例阈值,仅慢调用比例模式有效

* */

srule.setSlowRatioThreshold(0.6) ;

rules.add(srule);

/*

* 配置策略2:异常比例

* 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。

* 注意:需要通过Tracer.trace(ex)记录非BlockException类型的业务异常数

* */
DegradeRule erule = new DegradeRule(resourceName);
erule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
erule.setCount(0.1) ; //异常比率阈值,阈值范围是 [0.0, 1.0],代表 0% - 100%。
erule.setTimeWindow(10) ; //熔断时长,单位为 s
erule.setMinRequestAmount(5) ; //熔断触发的最小请求数
erule.setStatIntervalMs(1000) ; //单位统计时长
rules.add(erule);
/*

* 配置策略3:异常数

* 当单位统计时长内的异常数目超过阈值之后会自动进行熔断

* 注意:需要通过Tracer.trace(ex)记录非BlockException类型的业务异常数

* */
DegradeRule ecrule = new DegradeRule(resourceName);
ecrule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
ecrule.setCount(2) ; //异常数阈值
ecrule.setTimeWindow(10) ; //熔断时长,单位为 s
ecrule.setMinRequestAmount(5) ; //熔断触发的最小请求数
ecrule.setStatIntervalMs(1000) ; //单位统计时长
rules.add(ecrule);
//加载配置的熔断策略
DegradeRuleManager.loadRules(rules);
}
private void addEventObserver(){
//熔断器事件监听
EventObserverRegistry.getInstance().addStateChangeObserver("logging",
(prevState, newState, rule, snapshotValue) -> {
if (newState == CircuitBreaker.State.OPEN) {
// 变换至 OPEN state 时会携带触发时的值
System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
TimeUtil.currentTimeMillis(), snapshotValue));
} else {
System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
TimeUtil.currentTimeMillis()));
}
});
}
}

通过以上两步,test方法被调用的时候就能受到限流和熔断的保护了。

至于怎么样熔断规则配置才是有效的,可看以下的代码

public static boolean isValidRule(DegradeRule rule) {
boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource())
&& rule.getCount() >= 0 && rule.getTimeWindow() > 0;
if (!baseValid) {
return false;
}
if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) {
return false;
}
switch (rule.getGrade()) {
case RuleConstant.DEGRADE_GRADE_RT:
return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1;
case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
return rule.getCount() <= 1;
case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
return true;
default:
return false;
}
}

使用方式二:使用Spring的AOP进行控制

步骤1:添加所需切面的配置

在Spring的配置文件中添加以下配置

也可以通过JavaConfig的方式来配置

@Configuration(proxyBeanMethods = false)
//SpringBoot下该@EnableAspectJAutoProxy可以不需要,会自动完成装配
//@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}

步骤2:在方法上添加注解

/**

*

* @SentinelResource注解参数介绍

* 1)value 配置资源名称,如果不配置则使用方法全路径名

* 2)entryType 指定规则作用于入口流量还是出口流量。默认为EntryType.OUT

* 3)blockHandler 对应处理 BlockException 的函数名称。

* 该函数的返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的类型为BlockException的参数;

* 默认需要和原方法在同一个类中,也可以配置blockHandlerClass来指定处理的类,但类里的方法必须是static。

* 4)fallback 失败回调的函数名称(优先级低于blockHandler),可以处理除了在参数exceptionsToIgnore中指定排除的所有异常类型,

* 该函数默认与原函数在同一个类型中,也可以配置fallbackClass 来指定处理的类,但类里的方法必须是static,其函数的签名要求如下:

* - 返回类型与原函数类型一致

* - 参数列表与原函数保持一致,同时可以额外添加一个Throwable类型参数来接收对应的异常

* 5)defaultFallback 默认的 fallback 函数名称,可以处理除了在参数exceptionsToIgnore中指定排除的所有异常类型,在未配置fallback的情况下生效。

* 该函数默认与原函数在同一个类型中,也可以配置fallbackClass 来指定处理的类,但类里的方法必须是static,其函数的签名要求如下:

* - 返回类型与原函数类型一致

* - 参数列表为空,同时可以额外添加一个Throwable类型参数来接收对应的异常

* 6)exceptionsToIgnore 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

* */
@SentinelResource(value = "getUserById", fallback = "fallbackForGetUser")
public String getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
/**
* fallback 方法,原方法被降级的时候调用;若需要限流/系统保护的 fallback 可以配置 blockHandler.
* */
public String fallbackForGetUser(String id) {
return "unknown";
}

步骤3:定义资源保护规则(同上)

方法同上,只是要修改资源名为“getUserById”

经过以上几个步骤也就完成了对资源“getUserById”的保护。