标题Sentinel 是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
官网地址
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
简单处理
try{
//限流
//http请求消耗线程
map(URI,线程数) //隔离
线程池(线程数)
if (当前线程满了){
throw exception
}
//熔断
//计数 连续失败次数 达到阈值
失败 count++;
if(count == 10){
//试试
随机请求/定时发请求(半请求)
throw exception;
}
}catch(Exception e){
1. 避免返回不友好的错误信息
-> 好看点儿的页面 重试按钮 联系邮箱
2.
return "客官稍后再来";
return 操作写到MQ里,返回成功 发个邮件...
}
配置降级规则
@SysControllerLog(isSaveDB = "true")
@ApiOperation(value = "获取用户基本信息")
@RequestMapping(value = "/basicInfo.do", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('b:PersonalInformationController:basicInfo')")
@SentinelResource(value = "basicInfo")
public ServerResponse<Admin> basicInfo() {
Long adminId = RequestHolder.getCurrentAdmin().getAdmin().getId();
return ServerResponse.createBySuccess("查询成功!", adminService.getById(adminId));
}
出现慢查询后服务被降级
控制台抛出了异常
流量控制
Controller层处理异常
@SysControllerLog(isSaveDB = "true")
@ApiOperation(value = "获取用户部门岗位信息")
@RequestMapping(value = "/listDeptJobs.do", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('m:PersonalInformationController:listDeptJobs')")
public ServerResponse<List<DeptVO>> listDeptJobs() {
Admin admin = RequestHolder.getCurrentAdmin().getAdmin();
try {
List<DeptVO> deptVOS = adminJobService.listDeptJobs(admin.getId(), admin.getDefaultEnterpriseId());
return ServerResponse.createBySuccess("查询成功!", deptVOS);
}catch (BlockException e){
return ServerResponse.createByErrorMessage("服务暂时不可用");
}
}
Service层抛出异常
@Override
@SentinelResource(value = "listDeptJobs")
public List<DeptVO> listDeptJobs(Long adminId, Long enterpriseId) throws BlockException {
return adminJobMapper.listDeptJobs(adminId, enterpriseId);
}
为了方便测试,限制QPS为5
全局配置
由于控制台的配置会在应用重启后重置,应使用持久化的配置
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* Sentinel切面类配置:
*/
@Configuration
public class SentinelAspectConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
//熔断降级
// List<DegradeRule> rules = new ArrayList<>();
// DegradeRule rule = new DegradeRule();
// //资源名
// rule.setResource("index");
// //阈值类型:异常比例
// rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// //阈值
// rule.setCount(200);
// //比例阀值
// rule.setSlowRatioThreshold(0.5);
// //时间窗口为5秒
// rule.setTimeWindow(5);
// //最小
// rule.setMinRequestAmount(1);
// rules.add(rule);
// DegradeRuleManager.loadRules(rules);
//限流
List<FlowRule> flow = new ArrayList<>();
FlowRule rule1 = new FlowRule();
//可以不使用资源名,直接使用路径
rule1.setResource("/sys/enterpriseAdmin/index.do");
// QPS控制
rule1.setCount(100);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
flow.add(rule1);
FlowRuleManager.loadRules(flow);
return new SentinelResourceAspect();
}
}
配置自定义的异常返回
sentinel自带的返回异常会显得不友好,因此需要进行处理
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 com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.training.examination.core.common.ServerResponse;
import lombok.Data;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
String msg = null;
if (ex instanceof FlowException) {//限流
msg = "服务忙,请稍后再试";
} else if (ex instanceof DegradeException) {//降级
msg = "服务暂不可用";
} else if (ex instanceof ParamFlowException) {
msg = "热点参数限流";
} else if (ex instanceof SystemBlockException) {
msg = "系统规则(负载/...不满足要求)";
} else if (ex instanceof AuthorityException) {
msg = "授权规则不通过";
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
// spring mvc自带的json操作工具,叫jackson
new ObjectMapper()
.writeValue(response.getWriter(),
ServerResponse.createByErrorCodeMessage(10000, msg));
}
@PostConstruct
public void init() {
new CustomBlockExceptionHandler();
}
@Data
static class SentinelErrorMsg {
private Integer status;
private String msg;
}
}
Spring Cloud Gateway网关限流
- 参考地址
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81 - 参考github上官方DEMO,对代码做如下调整
配置依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
代码配置
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.online.examination.gateway.handler.CustomBlockRequestHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Sentinel配置
*/
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit() {
initGatewayRules();
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("system_route")
.setCount(200)
.setIntervalSec(1)
//流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
//匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
.setMaxQueueingTimeoutMs(6000)
);
rules.add(new GatewayFlowRule("work_route")
.setCount(200)
.setIntervalSec(1)
//流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
//匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
.setMaxQueueingTimeoutMs(6000)
);
rules.add(new GatewayFlowRule("external_route")
.setCount(200)
.setIntervalSec(1)
//流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
//匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
.setMaxQueueingTimeoutMs(6000)
);
GatewayRuleManager.loadRules(rules);
GatewayCallbackManager.setBlockHandler(new CustomBlockRequestHandler());
}
}
网关限流规则 GatewayFlowRule 的字段解释如下:
resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
resourceMode:规则是针对 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)还是用户在 Sentinel 中定义的 API 分组(RESOURCE_MODE_CUSTOM_API_NAME),默认是 route。
grade:限流指标维度,同限流规则的 grade 字段。
count:限流阈值
intervalSec:统计时间窗口,单位是秒,默认是 1 秒。
controlBehavior:流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
burst:应对突发请求时额外允许的请求数目。
maxQueueingTimeoutMs:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
paramItem:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。其中的字段:
parseStrategy:从请求中提取参数的策略,目前支持提取来源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 参数(PARAM_PARSE_STRATEGY_URL_PARAM)四种模式。
fieldName:若提取策略选择 Header 模式或 URL 参数模式,则需要指定对应的 header 名称或 URL 参数名称。
pattern:参数值的匹配模式,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。(1.6.2 版本开始支持)
matchStrategy:参数值的匹配策略,目前支持精确匹配(PARAM_MATCH_STRATEGY_EXACT)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS)和正则匹配(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本开始支持)
用户可以通过 GatewayRuleManager.loadRules(rules) 手动加载网关规则,或通过 GatewayRuleManager.register2Property(property) 注册动态规则源动态推送(推荐方式)。
自定义返回异常
package com.online.examination.gateway.handler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* 返回自定义限流异常
*/
public class CustomBlockRequestHandler implements BlockRequestHandler {
private static final String DEFAULT_BLOCK_MSG_PREFIX = "服务忙,请稍后再试";
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable ex) {
// 返回http状态码为200
return ServerResponse.status(10000).contentType(MediaType.APPLICATION_JSON_UTF8)
.body(fromObject(buildErrorResult(ex)));
}
private ErrorResult buildErrorResult(Throwable ex) {
return new ErrorResult(10000,
DEFAULT_BLOCK_MSG_PREFIX);
}
private static class ErrorResult {
private final int code;
private final String message;
ErrorResult(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}
并发超过时的结果