文章目录
特别声明:整理自慕课网大目师兄的微服务视频,链接: https://coding.imooc.com/learn/list/358.html
前情提要: 接续服务容错 Sentinel 上篇
7.热点参数规则限流
热点即经常访问的数据
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
7.1 针对第二个传参限流
规则:如果第二个参数有值,QPS达到阈值,限流;如果第二个参数没传值,不限流
- 准备代码
@SentinelResource注解的作用是把参数纳入统计
@RestController
@Slf4j
@RequestMapping("/hot")
public class HotParamController {
@GetMapping("basic")
// 把方法的参数纳入统计
@SentinelResource("basic")
public String basic(String a,String b){
log.info("basic......");
return a + ":" + b;
}
- 配置规则
参数索引:参数的位置,从0开始。参数b是第二个参数,所以这里配置1
- 测试
不传参数b,疯狂刷新:http://localhost:8081/hot/basic?a=1,不会被限流
带有参数b,疯狂刷新:http://localhost:8081/hot/basic?a=1&b= 或者 http://localhost:8081/hot/basic?a=1&b=hello,被限流
7.2 针对参数具体值的限流
热点规则 ------> 点击编辑(新增的时候是没有的)
- 点开高级,配置规则
下面的规则含义:参数b的阈值是1,但有例外情况,如果b=hello时,阈值是1000;如果b=hai,阈值是2
- 测试
疯狂刷新 http://localhost:8081/hot/basic?a=1&b=,b=""或者其它值,阈值是1,很容易被限流
疯狂刷新 http://localhost:8081/hot/basic?a=1&b=hello,b=“hello”,阈值是1000,凭手速达不到QPS 1000,不会限流
疯狂刷新 http://localhost:8081/hot/basic?a=1&b=hai,b=“hai”,阈值是2,容易限流
8.系统规则限流
- 应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
- 系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效
8.1 四种阈值类型
8.1.1 Load
8.1.1.1 释义
- 当系统load1(一分钟的负载)超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护
- 系统容量:系统的 maxQps * minRt 计算得出
maxQps:秒级统计最大的QP
minRt:秒级统计的最小响应时间 - 阈值:一般是CPU cores * 2.5
- 仅对Linux/Unix-like机器生效
8.1.1.2 查看linux系统的load
uptime
红框里的三个数分别是1分钟内的平均负载,5分钟内的平均负载,15分钟内的平均负载
8.1.2 RT
所有入口流量的平均RT
8.1.3 线程数
所有入口流量的并发线程数
8.1.4 QPS
所有入口流量的QPS
9.授权规则限流
针对应用级别的黑白名单,参数的意思也都很好理解
10.sentinel控制台如何监控微服务数据?
- 微服务把自己注册到sentinel控制台,使用心跳机制通讯
- sentinel的机器列表
- 通讯的api有哪些?
http://192.168.56.1:8720/api
- 服务端查看sentinel详情
http://localhost:8081/actuator/sentinel
11.常用的自定义参数
11.1 服务方常用的自定义参数
11.2 sentinel控制台常用的自定义参数
- 自定义端口示例
java -jar -DServer.port=8090 sentinel-dashboard-1.6.3.jar
端口已改成8090
12.Feign,RestTemplate整合Sentinel
Sentinel不仅可以控制本服务的资源,还可以限流Feign和RestTemplate对外的请求
12.1 RestTemplate整合Sentinel
- 添加注解@SentinelRestTemplate
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- sentinel控制台添加限流规则,簇点链路这边可以看到restTemplate发送的请求了,添加注解之前是没有的
- 关闭注解@SentinelRestTemplate,默认是开启的
resttemplate:
sentinel:
# 关闭 @SentinelRestTemplate 作用
enabled: false
12.2 Feign整合Sentinel
- 写配置
feign:
sentinel:
# IDE不会提示,但是有效
enabled: true
- 使用feign,sentinel这边已经可以看到 GET:http://user-center/reciteHis/testAno 资源了
如果spring-cloud-alibaba是0.9.0版本并且你的Feign是用继承模式实现的,那么会报 NullPointerException 错误,把spring-cloud-alibaba升级到2.1.0.RELEASE就没有了,我踩了这个坑…
13.Sentinel 扩展
13.1 Sentinel规则错误提示优化
之前,我测试的时候,限流规则,降级规则,热点规则等待,返回的都是一句错误消息,或者干脆报异常,无法区别到底违反了哪个规则?这一节的扩展可以优化这个问题
13.1.1 普通spring mvc资源的错误提示优化
- 写 ContentUrlBlockHandler 实现 UrlBlockHandler,并加上@Component
package com.zengchen.content.sentinel;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
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.zengchen.content.common.ResponseVO;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class ContentUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
ResponseVO sentinelErrorMsg = null;
// 根据异常类型区分违反哪种规则
if(e instanceof FlowException){// 限流异常
sentinelErrorMsg = ResponseVO.builder()
.code(100)
.msg("被限流规则阻挡")
.build();
}else if(e instanceof DegradeException){ // 降级异常
sentinelErrorMsg = ResponseVO.builder()
.code(101)
.msg("被降级规则阻挡")
.build();
}else if(e instanceof ParamFlowException){ // 热点参数异常
sentinelErrorMsg = ResponseVO.builder()
.code(102)
.msg("被热点参数规则阻挡")
.build();
}else if(e instanceof SystemBlockException){ // 系统规则异常
sentinelErrorMsg = ResponseVO.builder()
.code(103)
.msg("被系统规则阻挡")
.build();
}else if(e instanceof AuthorityException){ // 认证异常
sentinelErrorMsg = ResponseVO.builder()
.code(104)
.msg("被授权规则阻挡")
.build();
}else{
sentinelErrorMsg = ResponseVO.builder()
.code(999)
.msg("Unknown")
.build();
}
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
httpServletResponse.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(httpServletResponse.getWriter(),sentinelErrorMsg);
}
}
ResponseVO:
package com.zengchen.content.common;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* http请求返回的最外层对象
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResponseVO<T> {
/**
* 错误码
*/
private Integer code;
/**
* 提示信息
*/
private String msg;
/**
* 具体内容
*/
private T data;
public static ResponseVO success(Object data){
ResponseVO responseVO = new ResponseVO();
responseVO.setCode(0);
responseVO.setData(data);
responseVO.setMsg("OK");
return responseVO;
}
public static ResponseVO fail(Integer code,String msg){
ResponseVO responseVO = new ResponseVO();
responseVO.setCode(code);
responseVO.setMsg(msg);
return responseVO;
}
}
- 测试
限流规则
降级规则
热点参数规则
失败了,程序并没有走进ContentUrlBlockHandler,因为热点参数测试方法上添加了@SentinelResource注解,有这个注解就不能使用ContentUrlBlockHandler来做错误页优化,下小节会讲
系统规则
授权规则
13.1.2 使用@SentinelResource注解资源的错误提示优化
主要使用@SentinelResource注解的blockHandler,blockHandlerClass,fallback,fallbackClass四个属性
13.1.2.1 属性详解
- blockHandler
值是方法名,资源受到规则限制,会调用此方法
方法要求:访问控制 public,返回值与资源方法一直,参数是资源方法参数 + BlockException e - blockHandlerClass
当blockHandler方法所在位置于资源方法不在一个类时,用这个属性指定 - fallback
值是方法名,资源方法发生异常时,会调用此方法
方法要求:访问控制 public,返回值与资源方法一直,参数是资源方法参数 + Throwable e(版本1.6.0之和才有Throwable参数) - fallbackClass
当fallback方法所在位置于资源方法不在一个类时,用这个属性指定
13.1.2.2 示例代码
- 资源方法:
@RestController
@Slf4j
public class SentinelResourceController {
@GetMapping("/test-blockHandler")
@SentinelResource(value = "blockHandler",
blockHandler = "block",blockHandlerClass = SentinelResourceBlockHandler.class ,
fallback = "fallBack",fallbackClass = SentinelResourceFallbackHandler.class)
public String testBlockHandler(String a,String b) throws InterruptedException {
throw new IllegalArgumentException();
}
}
- SentinelResourceBlockHandler
@Slf4j
public class SentinelResourceBlockHandler {
public static String block(String a,String b, BlockException e){
String errorMsg = "Unknown";
// 根据异常类型区分违反哪种规则
if(e instanceof FlowException){// 限流异常
errorMsg = "被限流规则阻挡";
}else if(e instanceof DegradeException){ // 降级异常
errorMsg = "被降级规则阻挡";
}else if(e instanceof ParamFlowException){ // 热点参数异常
errorMsg = "被热点参数规则阻挡";
}else if(e instanceof SystemBlockException){ // 系统规则异常
errorMsg = "被系统规则阻挡";
}else if(e instanceof AuthorityException){ // 认证异常
errorMsg = "被授权规则阻挡";
}
log.warn("block method,{}",errorMsg ,e);
return errorMsg;
}
}
- SentinelResourceFallbackHandler
@Slf4j
public class SentinelResourceFallbackHandler {
public static String fallBack(String a,String b,Throwable e){
log.warn("fallBack method",e);
return "出现异常了";
}
}
13.1.3 Feign 通讯资源的错误提示优化
还记得@FeignClient注解吗?也有fallback,fallbackFactory属性
只能配置一个总的,因为都是一个feignClient,入口统一了
- 代码
SentinelFeignFallbackFactory
@Component
@Slf4j
public class SentinelFeignFallbackFactory implements FallbackFactory<UserCenterFeignClient> {
@Override
public UserCenterFeignClient create(Throwable throwable) {
return new UserCenterFeignClient() {
@Override
public Page<ReciteHisOT> memberRctHisAno() {
log.warn("memberRctHisAno FallbackFactory");
return null;
}
};
}
}
UserCenterFeignClient的fallbackFactory
@FeignClient(name = "user-center", fallbackFactory = SentinelFeignFallbackFactory.class)
public interface UserCenterFeignClient
extends UserCenterFeignClientService
{
/**
* FeignClient的name + GetMapping的value
* 相当于 http://user-center/reciteHis/testAno
* 和RestTemplate里写的url一模一样
* @return Page
*/
// @GetMapping(value = "/reciteHis/testAno")
// Page<ReciteHisOT> memberRctHisAno();
}
配置个限流规则,触发规则时,进入fallbackFactory
13.1.4 RestTemplate 通讯资源的错误提示优化
还记得RestTemplate怎么和sentinel整合的吗?也是一个注解@SentinelRestTemplate,这个注解和@SentinelResource一样,有blockHandler,blockHandlerClass,fallback,fallbackClass四个属性
也只能配置一个总的,因为都是一个入口
不写代码了,反正我用Feign
13.2 Sentinel 实现区分来源
授权规则的黑白名单需要来源,流控规则也有针对来源的属性,但是这个来源是怎么来的呢?sentinel提供了RequestOriginParser接口,写个类实现这个接口,唯一方法的返回值就是来源
- 代码
@Component
public class ContentRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// String origin = httpServletRequest.getHeader("origin");
String origin = httpServletRequest.getParameter("origin"); // 放在参数中方便测试
if(StringUtils.isBlank(origin)){
throw new IllegalArgumentException("origin is blank!");
}
return origin;
}
}
- 测试授权规则
13.3 Sentinel 对Restful Url的支持
问题说明:如下图,仅仅id不一样,这规则怎么配置?
sentinel的UrlCleaner会帮助我们解决这个问题
- restful接口DownController
@GetMapping("restful/{id}")
public String restful(@PathVariable Integer id) {
log.info("restful start with {}", id);
return "" + id;
}
@GetMapping("restful/{id}/test")
public String restfulTest(@PathVariable Integer id) {
log.info("restfulTest start with {}", id);
return "test " + id;
}
- UrlCleaner
@Component
@Slf4j
public class ContentUrlCleaner implements UrlCleaner {
@Override
public String clean(String path) {
log.info("ContentUrlCleaner start with {}", path);
String[] paths = path.split("/");
path = Arrays.stream(paths).map(string -> {
if (NumberUtils.isNumber(string)) {
return "{number}";
}
return string;
}).reduce((a, b) -> {
return a + "/" + b;
}).orElse("");
log.info("ContentUrlCleaner end with {}", path);
return path;
}
}
- sentinel控制台,簇点链路,url已经改变
14.Sentinel 规则持久化
现在sentinel有个致命缺陷,服务重启或者sentinel重启,配置的规则都会丢失,如果是生产上,这还了得?这种事绝对不能发生。下面介绍三种解决办法
14.1 拉模式
参考大目老师手记:https://www.imooc.com/article/289402,生产环境不建议用这个,因为使用之后后患不小
14.2 推模式
- 原理
- 实现,工程浩大
参考大目老师手记 https://www.imooc.com/article/289464 - 懒人包
大目老师提供了懒人包: https://github.com/eacdy/Sentinel-Dashboard-Nacos/releases,这个是供学习用的,不要直接用于生产
14.3 阿里 ahas
免费的可以接入5个节点,专业的收费
开通地址:https://ahas.console.aliyun.com
开通说明:https://help.aliyun.com/document_detail/90323.html
14.3.1 加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring-boot-starter-ahas-sentinel-client</artifactId>
<version>1.3.5</version>
</dependency>
14.3.2 添加埋点
springcloud-alibaba已经帮我们做过了,省略
14.3.3 配置启动参数
这里选择公网,才有license
我的ahas.license就不暴露出来了
注释掉
# transport:
# # 指定 sentinel 控制台地址
# dashboard: localhost:8080
14.3.4 重启应用
随便访问一个接口,比如:http://localhost:8081/down/restful/2/test,再刷新阿里的控制台,就可以看到了
,