1.常见的处理服务雪崩的方式
- 超时时间
- 限流
- 仓壁模式
- 断路器模式
2.Sentinel流控方式
流控模式:
- 直接:根据
QPS
或者线程数直接关联,超过阈值直接失败 - 关联:关联资源超过阈值,则失败
- 链路:绑定某入口资源的阈值,超时则失败
流控策略:
- 快速失败:直接失败抛出异常,源码位置
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
- Warm Up : 预热模式,设置初始流量为阈值/codeFactor(默认为3),经过设置的默认时长才达到阈值,源码位置:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
,适用于大流量,避免一次性流量过大导致服务直接崩溃。 - 排队等待:请求匀速通过,阈值必须设置成
QPS
,否则无效。适用于流量激增。
3.降级策略
- RT(秒级统计),在时间窗口内QPS>=5次,并且平均响应时间超出阈值,则触发降级,在时间窗口结束后关闭降级,RT的最大响应设置时间为
4900ms
,若需要指定更大,则需要通过-Dcsp.sentinel.statistic.max.rt=xx
- 异常比例(秒级统计),QPS>=5并且异常比例超过阈值,则会进入熔断
- 异常数(分钟统计),异常数超过阈值,则触发降级,若时间窗设置小于60S,那么可能在降级时间窗结束后在此进入降级
4.热点策略
- 针对api接口参数特定的值进行限流,其中配置参数索引的特点热点值数据只对基本数据类型和String有效
- 可参考相关源码
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
[图片上传失败...(image-87caf8-1565855752793)]
5.系统规则(load1,cpu,..)
load1 = cpu核心数 * 2.5
6.授权规则
针对微服务名称进行黑白名单设置,可以通过定义RequestOriginParser来设置名称规则
7.代码配置方式配置Sentinel
容错策略
ex : 初始化一个简单的流控规则,在资源执行前调用即可
private void initFlowQpsRule() {
List<FlowRule> ruleList = new ArrayList<>();
FlowRule rule = new FlowRule("/c");
//设置阈值
rule.setCount(1);
//设置流控阈值类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置流控模式
rule.setStrategy(RuleConstant.STRATEGY_DIRECT);
//设置流控策略
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rule.setLimitApp("default");
ruleList.add(rule);
FlowRuleManager.loadRules(ruleList);
}
详细所有的配置信息参考 - > 大佬Sentinel博客
8.Sentinel Dashboard 是如何知道微服务的信息?如何通信?
- Sentinel实现了服务注册和发现
- 通过配置的通信端口+服务ip,sentinel可以使用http调用其api来操作设置容错规则和获取监控信息
源码解析:
注册/心跳发送:com.alibaba.csp.sentinel.transport.heardheat.SimpleHttpHeartbeatSender
通信api : com.alibaba.csp.sentinel.commonand.CommandHandler相关实现类
9.Sentinel Api的简单使用
- ContextUtil 定义入口资源
- Sphu 定义资源
- Tracer.trace() 统计资源 , 在非
BlockException
的异常时候需要显示的使用来统计,不然不会自动统计
@GetMapping("/test-sentinel-api")
public String testSentinelApi() {
String resourceName = "test-sentinel-api";
ContextUtil.enter(resourceName, "test-micro-service");
Entry entry = null;
try {
//定义一个资源
entry = SphU.entry(resourceName);
//执行业务逻辑
if (!StringUtils.isNotBlank("")) {
throw new IllegalAccessException("参数非法");
}
return "success";
} catch (BlockException e) {
return "容错触发!!";
} catch (IllegalAccessException e) {
//默认除了BlockException及其子类会计算容错次数,异常数等等。。
//其他异常需要使用Tracer.trace()进行统计
Tracer.trace(e);
return "非法参数";
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
10.sentinel整合RestTemplate和feign
- 直接在注入的
RestTemplate
上面加入@SentinelRestTemplate
注解即可
核心处理原理见 : SentinelBeanPostProcessor
- 整合
feign
,直接在配置文件中指定feing.sentinel.enabled=true
即可。
11.持久化Sentinel规则
- 默认不持久化,保存在内存中
- pull模式,将sentinel规则保存在文件或者数据库中
- push模式(生成环境使用),将sentinel的持久化规则同步到nacos/zk/Apollo等配置中心上
push
模式的集成
改造sentinel-dashboard
控制台
因为push
模式是sentinel-dashboard
直接和远程配置中心直接交互,而不用sentinel
客户端去参加
修改步骤:
- 将sentinel-datasource-nacos依赖的范围变成compile
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- <scope>test</scope>-->
</dependency>
- 修改拉取和推送的策略
具体实现见 github
微服务集成sentinel持久化规则
- 加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8820
datasource:
# 名称随意
flow:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
# 规则类型,取值见:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
degrade:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade
param-flow:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: SENTINEL_GROUP
rule-type: param-flow
12.sentinel在默认是会拦截所有的controller请求
可配置排除
spring:
cloud:
sentinel:
filter:
enabled: false
如何定制默认的controller请求错误拦截页面?实现UrlBlockHandler
即可
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws IOException {
ErrorMsg msg = null;
//根据异常类型的不同判断是发生了限流还是降级
if (e instanceof FlowException) {
msg = ErrorMsg.builder()
.msg("被限流了")
.status(101)
.build();
} else if (e instanceof DegradeException) {
msg = ErrorMsg.builder()
.msg("被降级了")
.status(101)
.build();
}
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("utf-8");
response.setStatus(500);
new ObjectMapper()
.writeValue(
response.getWriter(),
msg
);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
private static class ErrorMsg {
private String msg;
private Integer status;
}
}
13.Sentinel的来源应用配置
- 可以通过不同的来源来配置匹配的策略。
- 可以通过授权规则来添加黑白名单。
通过实现RequestOriginParser
来实现,该方法的返回值就是应用的名称。
/**
* @author hj
* 2019-08-15 13:55
* 自定义区分来源解释器,通过origin请求参数来区分来源应用
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//如果参数中有origin参数那么获取并获取值作为来源
String origin = httpServletRequest.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must not be null");
}
//无则抛出异常
return origin;
}
}
14.sentinel对Restful-url
的支持
sentinel默认对/a/{id}
这种格式url会随着参数变化而产生多个资源,那么如何让这些资源共享一个sentinel规则? 通过UrlCleaner
实现类将这种rest
风格转化成相同的url
实现UrlCleaner
接口
/**
* @author hj
* 2019-08-15 14:24
* 扩展restFul-Url,让/shares/* 使用相同的逻辑
*/
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String url) {
String[] split = url.split("/");
return Arrays.stream(split)
.map(a -> {
if (NumberUtils.isNumber(a)) {
return "{number}";
}
return a;
})
.reduce((a, b) -> a + "/" + b)
.orElse("");
}
}
15总结扩展表格
其实上面的扩展点都是来源于CommonFilter
public class CommonFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
Entry urlEntry = null;
try {
String target = FilterUtil.filterTarget(sRequest);
//处理 REST APIS 的UrlCleaner
UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
if (urlCleaner != null) {
target = urlCleaner.clean(target);
}
if (!StringUtil.isEmpty(target)) {
//处理来源的RequestOriginParser
String origin = parseOrigin(sRequest);
ContextUtil.enter(WebServletConfig.WEB_SERVLET_CONTEXT_NAME, origin);
if (httpMethodSpecify) {
// Add HTTP method prefix if necessary.
String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
} else {
urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
}
}
chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse)response;
//处理错误页面返回的UrlBlockHandler
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
} catch (IOException | ServletException | RuntimeException e2) {
Tracer.traceEntry(e2, urlEntry);
throw e2;
} finally {
if (urlEntry != null) {
urlEntry.exit();
}
ContextUtil.exit();
}
核心接口 | 处理问题 |
---|---|
UrlBlockHandler | 错误处理 |
UrlCleaner | 合并rest apis |
RequestOriginParser | 来源控制ContextUtil |
网友评论