1、熔断降级限流
什么是熔断
A服务调用B服务的某个功能,由于网络不稳定问题,或者B服务卡机,导致功能时间超长。如果这样子的次数太多。我们就可以直接将B断路了(A不再请求B接口) ,凡是调用B的直接返回降级数据,不必等待B的超长执行。这样B的故障问题,就不会级联影响到A。
什么是降级
整个网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级停止服务,所有的调用直接返回降级数据。以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。
异同:
相同点:
1、为了保证集群大部分服务的可用性和可靠性,防止崩溃,牺牲小我
2、用户最终都是体验到某个功能不可用
不同点:
1、熔断是被调用方故障,触发的系统主动规则
2、降级是基于全局考虑,停止一些正常服务,释放资源
什么是限流
对打入服务的请求流量进行控制,使服务能够承担不超过自己能力的流量压力,也就是将服务集群的入口流量做限制,保证流量不会超过集群的能力,只要是超过集群的的处理能力就直接丢弃!
对比
这里对比就是告诉我们,Sentinel很牛逼就行了!
这里额外讲一点,在隔离策略上的信号量和线程池的区别;
线程池隔离:
如现在有一个hello请求需要限流保护,限流50个,那么Hystrix就会为这个hello请求创建一个线程池,里面50个线程,那么还有其他的请求如还有一个world请求也需要限流保护,那么这时Hystrix又会为world请求创建一个对应的线程池!每个需要限流保护的请求都需要创建对应的线程池 缺点: 这样整个系统需要保护的请求多了,开辟的线程池也就多了,会极大的影响消耗服务器的性能!优点: 隔离比较彻底,每个需要保护的请求都有直接的独立线程池,单个请求的池子炸了不会影响到其他请求
信号量隔离:
同样50个请求进来需要限流保护,每一个需要限流保护的请求都自己的semaphore,50个请求进来后,semaphore发现50个流量消费完了,优点: 其他的请求直接挡回去,其他多余的请求也不会从新创建线程,直接使用信号量来扣减。缺点: 如果某些请求隔离炸了,会影响整个服务!
这里也不过多介绍了,官方稳定已经很详细了,我就不画蛇添足了alibaba/Sentinel
Sentinel控制台安装
结合项目导入的版本!
下载对应客户端
下载速度慢可以参看这篇文章GitHub下载提速
启动客户端
java -jar .\sentinel-dashboard-1.6.3.jar --server.port=9999
指定日志存放目录
java -Dlogging.file=/log/sentinel-dashboard.log -Dcsp.sentinel.log.dir=/log/ -Dserver.port=9999 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
如果嫌每次启动都需要敲这么长的命令可以将命令做成脚本,创建.sh文件,将上面命令复制出来,赋予这个脚本可执行权限
chmod +x sentinel-start.sh
然后每次直接运行这个脚本即可
默认存放目录
启动成功!
登录控制台
账号密码都是sentinel,这个控制台式懒加载的,没有请求时不显示任何数据!
SpringCloud整合Sentinel
1.导入相关依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.查看版本Sentinel
这里是core包1.6.3-也对应控制台的版本
3.编写配置文件
//Sentinel 控制台地址
spring.cloud.sentinel.transport.dashboard=localhost:9999
//与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.client-ip=启动服务机器ip //防止sentinel抓取虚拟ip
注意:
如果sentinel服务端出现如下异常,且控制台上不显示服务的实时监控数据的话,请加上client-ip
2021-05-01 21:22:46 [pool-2-thread-1] ERROR c.a.c.s.d.metric.MetricFetcher - Failed to fetch metric from <http://192.168.184.1:8720/metric?startTime=1619875224000&endTime=1619875230000&refetch=false> (ConnectionException: 连接超时)
4.启动服务器
启动成功!
5.控制台查看
由于Sentinel是懒加载模式,需要发送请求才能监控得到!
下面测试使用Jmeter来发送并发请求Jmeter测试工具实战测试
流控测试
1.添加接口
@Slf4j
@RestController
public class Test {
@Autowired
TestService testService;
@GetMapping("/test1")
public String finish() {
log.debug("test1");
testService.test1();
return "ok";
}
}
@Override
public String test1() {
log.info("TestImpl-test1");
return null;
}
2.添加流控
可以在流控规则查看刚才添加的流控
这里官网介绍的很详细流量控制
3.查看实际效果
这里限流每秒test1接口请求1次,使用Jmeter发送5条请求
发送请求5个
5个请求都发送成功了,查看服务端日志输出
服务端日志值打印一次请求,看看Jmeter其他请求的响应结果
Jmeter右边响应结果框中并不是我们服务端接口中的ok,这个响应的内容是Sentinel限流后默认返回,这里控制台上限流一个配置成功!下面修改Sentinel默认限流返回
4.流控指定以返回
添加配置类
/**
* @description: 流控自定义返回
* @author TAO
* @date 2020/8/22 14:40
*/
@Configuration
public class SeckillSentinelConfig {
/**
* 可能因为版本冲突导致无法引入 WebCallbackManager
*/
public SeckillSentinelConfig() {
WebCallbackManager.setUrlBlockHandler((request, response, ex) -> {
R error = R.error(BizCodeEnume.TOO_MANY_REQUESTS.getCode(), BizCodeEnume.TOO_MANY_REQUESTS.getMsg());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
});
}
}
上面我是整合1.6.1的版本,当我后来切换到1.7.x版本时发现不起作用了!切换到下面即可
@Slf4j
@Component
public class SeckillSentinelConfig implements BlockExceptionHandler {
/**
* 需前置导入sentinel-web-servlet
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
log.info("===>流控自定义返回");
response.setContentType("application/json;charset=utf-8");
R res = null;
if (ex instanceof FlowException) {
res = R.failed("流控规则被触发");
} else if (ex instanceof DegradeException) {
res = R.failed("降级规则被触发");
} else if (ex instanceof AuthorityException) {
res = R.failed("授权规则被触发");
} else if (ex instanceof ParamFlowException) {
res = R.failed("热点规则被触发");
} else if (ex instanceof SystemBlockException) {
res = R.failed("系统规则被触发");
}
response.setStatus(200);
response.getWriter().write(JSON.toJSONString(res));
}
}
重启服务器,配置限流,并请求测试,这里注意,Sentinel的数据默认是存储在启动服务器的内存中,服务器重启也意味着配置、监控数据的丢失,后面会演示持久化!
自定义限流结果完成!
Sentinel熔断降级
在微服务集群环境下服务见调用比较多,一旦调用链中某个被调用方服务不可用,那么会导致整个系统就会崩溃,这里可以使用Sentinel来做熔断保护!降级!Sentinel熔断降级官网
Sentinel的服务熔断和Hystrix的服务熔断是差不多的
服务熔断
服务熔断不是这里的重点,而且比较简单,这里就不过多演示,在之前有篇文章中有提到过熔断保护
服务降级
在A服务调用B服务是,会形成调用链,在这个调用链上可以设置被调用方的降级策略
RT(平均响应时间,秒级),平均响应时间,超出阈值且在时间窗口内通过的请求>=5两个条件同时满足后触发降级,窗口期过后关闭断路器,RT最大是4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=xxx才能生效)
异常比例数(秒级) QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级!
异常数(分钟级) 异常数超时阈值时,触发降级;时间窗口结束后关闭降级!
sentinel熔断降级会在调用链路中某个资源出现不稳定状态是(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源导致级联错误。当资源被降级后,在接下来的时间窗口之内,对该资源的调用都自动熔断,(默认行为是抛出DegradeException)。Sentinel的断路器是没有半开状态的,半开的状态系统自动去检查是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用,具体可以参考hystrix
这里我就不架设多个服务间的调用了,假设左边的小框中是调用链路,右边的框中是调用方对被调用方的降级设置,
被调用方响应毫秒数,时间窗口是下次嗲用远程服务的间隔时间!
例如:A服务调用B服务,B服务响应时间超过1毫秒时就会触发服务熔断,然后间隔10秒后重试,
Sentinel自定义资源保护
Sentinel于主流框架是默认适配的,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。只需要引入对应的依赖即可方便地整合 Sentinel主流框架的适配
1.抛出异常的方式定义资源
//修改业务层test1代码
@Override
public String test1() {
log.info("TestImpl-test1");
//资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try(Entry entry= SphU.entry("t1")) {//抛出异常的方式定义限流资源名-t1
// 被保护的业务逻辑
// do something here...
log.debug("111");
}catch (BlockException ex){
// 资源访问阻止,被限流或被降级
// 在此处进行相应的处理操作
log.debug("资源被限流");
}
return null;
}
重启服务器,发送请求,查看控制台!
这里多了一个t1的资源,限流等其他操作和普通的接口一样
2.注解方式定义资源
/**
* @SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
*
* value:资源名称,必需项(不能为空)
* entryType:entry 类型,可选项(默认为 EntryType.OUT)
* blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
* fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
* 返回值类型必须与原函数返回值类型一致;
* 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
* fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
* defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
* 返回值类型必须与原函数返回值类型一致;
* 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
* defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
* exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
* @return
*/
@SentinelResource(value = "t2" ,blockHandler = "blockHandler")
@Override
public String test2() {
log.debug("222");
return null;
}
public String blockHandler(BlockException e){
log.error("被限流了---blockHandler");
return null;
}
发送请求,查看流控资源!
更多详情注解支持
网关整合Sentinel
官网介绍网关流控
1.网关服务的pom中导入相关依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
2.查看控制台
配置成功!
3.网关路由配置
- id: test
uri: lb://test
predicates:
- Host=test.gg.com
4.访问测试
5.添加网关流控
GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
ApiDefinition:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
其中网关限流规则 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) 注册动态规则源动态推送(推荐方式)。
参数详情
7.查看网关流控结果
8.控制台输出
这里网关流控是从整个微服务的入口控制的,限制的流量是无法抵达子服务中的,所以5个请求实际进入test服务的只有两个,这里两个是因为配置了阈值为1还有一个Burst size也是1,Burst size是突发情况先而外允许请求数量,所以test能抵达2个!
8.添加指定参数网关限流
这里可以设置一些额外参数作为限流条件
9.添加API分组流控
这里会根据指定API的资源路径作为限流条件
10.自定义网关限流返回
/**
* @description: 网关自定义限流返回
* @author TAO
* @date 2020/8/22 16:00
*/
@Configuration
public class SentinelGatewayConfig {
public SentinelGatewayConfig(){
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
R error = R.error(BizCodeEnume.TOO_MANY_REQUESTS_GATEWAY.getCode(), BizCodeEnume.TOO_MANY_REQUESTS_GATEWAY.getMsg());
String errJson= JSON.toJSONString(error);
Mono<ServerResponse> body= ServerResponse.ok().body(Mono.just(errJson),String.class);
return body;
}
});
}
}
配置成功!