计数器算法是在一定时间内记录请求次数, 当超过规定时间后计数器就会清零然后重新开始计算, 当请求超过间隔时间内最大次数时就会拒绝访问
计数器算法的特点是: 实现简单, 但是存在"突刺现象"
漏桶算法工作原理类似于一个底部有小孔的桶, 无论流入多少水, 流出的速度始终保持恒定. 当桶满时, 新的水会溢出, 即超出的流量被丢弃, 这种算法能够平滑突发流量, 常用于限制数据的传输速度
漏桶算法提供了一种机制, 可以让突发流量被整形, 以便为系统提供稳定的请求. 如: Sentinel 中流量整形(匀速排队功能)
令牌桶算法中, 存在一个固定大小的令牌桶, 该桶会以恒定的速率源源不断地产生令牌, 当系统需要发送数据时, 会从令牌桶中取出一个令牌, 如果桶中没有可用的令牌, 那么该次数据发送就会被限制, 这种机制确保了即使面临突发流量, 系统也能保持稳定运行
令牌桶算法和漏桶算法的区别是: 漏桶算法是匀速的处理请求, 而令牌桶算法可以应对突发的流量.
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
定义资源可通过代码或者注解实现
通过代码方式定义资源:
@RequestMapping("/getid")
public String getId() {
try(Entry entry = SphU.entry("getid")) { // 定义资源
return "getId: " + new Random().nextInt(100);
}catch (BlockException e) {
// 当前资源已经被限流或熔断
return "被限制";
}
}
通过注解方式定义资源:
@SentinelResource(value = "getname", blockHandler = "myBlockHandler")
@RequestMapping("/getname")
public String getName() throws InterruptedException {
Thread.sleep(100);
return "getName: " + new Random().nextInt(100);
}
/**
* 限流之后的执行方法
* @param blockException
* @return
*/
public String myBlockHandler(BlockException blockException) {
if (blockException instanceof FlowException) {
return "限流";
} else if (blockException instanceof DegradeException) {
return "熔断";
}
return "被限制";
}
注意:
@SentinelResource 属性说明
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("getname"); // 必须, 资源名
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 必须, 限流指标 QPS/线程数
rule.setCount(1); // 必须, 限流数量: 上一步线程数或QPS值
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
private static void initDegradeRules() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("getname"); // 资源名
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 熔断策略
rule.setCount(10); // RT值
rule.setStatIntervalMs(1000); // 统计时长
rule.setSlowRatioThreshold(0.5); // 阈值
rule.setMinRequestAmount(1); // 最小请求数
rule.setTimeWindow(5); // 熔断时长 单位为秒
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
if (blockException instanceof FlowException) {
return "限流";
} else if (blockException instanceof DegradeException) {
return "熔断";
}
Sentinel 异常分为三种: 局部自定义异常, 全局自定义异常, 系统自定义异常
@SentinelResource(value = "getname", blockHandler = "myBlockHandler")
@RequestMapping("/getname")
public String getName() throws InterruptedException {
Thread.sleep(100);
return "getName: " + new Random().nextInt(100);
}
/**
* 限流之后的执行方法
* @param blockException
* @return
*/
public String myBlockHandler(BlockException blockException) {
if (blockException instanceof FlowException) {
return "限流";
} else if (blockException instanceof DegradeException) {
return "熔断";
}
return "被限制";
}
局部自定义异常只需要创建一个方法然后在@SentinelResource 中设置即可
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
/**
* 定义 Sentinel 全局自定义异常
*/
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
int code = HttpStatus.TOO_MANY_REQUESTS.value();
String msg = "未知异常";
if (e instanceof FlowException) {
msg = "被限流";
} else if (e instanceof DegradeException) {
msg = "被熔断";
} else if (e instanceof ParamFlowException) {
msg = "请求触发了热点限流";
} else if (e instanceof AuthorityException) {
code = HttpStatus.UNAUTHORIZED.value();
msg = "暂无权限";
}
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(code);
httpServletResponse.getWriter().println("{\"code\":" + code + ", \"msg\":\"" + msg +"\"}");
}
}
全局自定义异常需要实现 BlockExceptionHandler 接口然后重写 handle 方法
如果只配置熔断和限流的情况下配置全局自定义异常即可, 但是配置一些特殊的规则如: 热点规则, 全局自定义异常就不会起作用, 就需要配置系统自定义异常
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Objects;
/**
* 自定义系统异常
*/
@RestControllerAdvice
public class SystemException {
@ExceptionHandler(ParamFlowException.class)
public HashMap<String, Object> ParamFlowException(ParamFlowException e) {
return new HashMap<>(){{
put("code", HttpStatus.TOO_MANY_REQUESTS.value());
put("msg", "热点限流");
}};
}
}
Sentinel 配置的规则默认存储到内存中, 会随着项目的重启而清空, 所以就需要对这些规则进行持久化配置
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
spring:
application:
name: sentinel-demo
cloud:
sentinel:
transport:
dashboard: localhost:18080
datasource:
ds:
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
data-id: ${spring.application.name}-flow-rules
group-id: DEFAULT_GROUP
data-type: json
rule-type: flow
ds2:
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
data-id: ${spring.application.name}-degrade-demo
group-id: DEFAULT_GROUP
data-type: json
rule-type: degrade
限流:
[
{
"resource":"/user/getname",
"limitApp":"default",
"grade":1,
"count":2,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
},
{
"resource":"/user/getid",
"limitApp":"default",
"grade":1,
"count":2,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
[{
"resource":"/user/getname",
"grade":0,
"count":10,
"timeWindow":5,
"minRequestAmount":1,
"statIntervalMs":1000,
"slowRationThreshold":0.5
}]