目录
一、简介
1. 熔断降级限流
什么是熔断?
A 服务调用 B 服务的某个功能,由于网络不稳定问题,或者 B 服务卡机,导致功能时间超长。如果这样子的次数太多。我们就可以直接将 B 断路了 (A 不再请求B 接口) ,凡是调用 B 的直接返回降级数据,不必等待 B 的超长执行。 这样 B 的故障问题,就不会级联影响到 A。
什么是降级?
整个网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级[停止服务,所有的调用直接返回降级数据]。以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。
异同:
相同点:
1、为了保证集群大部分服务的可用性和可靠性,防止崩溃,牺牲小我
2、用户最终都是体验到某个功能不可用
不同点:
1、熔断是被调用方故障,触发的系统主动规则
2、降级是基于全局考虑,停止一些正常服务,释放资源
什么是限流?
对打入服务的请求流量进行控制,使服务能够承担不超过自己能力的流量压力。
2. Sentinel 简介
官方文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
项目地址:https://github.com/alibaba/Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
-
丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀 (即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
-
完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
-
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 分为两个部分:
-
核心库 (Java 客户端) 不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
-
控制台 (Dashboard) 基于 Spring Boot 开发,打包后可以直接运行,不需要额外的Tomcat 等应用容器。
Sentinel 基本概念:
资源:
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则:
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
二、Hystrix 与 Sentinel 比较
三、整合 Feign+Sentinel 测试熔断降级
https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。
Sentinel 和 Hystrix 的原则是一致的:
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
熔断降级设计理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过 线程池隔离 的方式,来对依赖 (在 Sentinel 的概念中对应资源) 进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本 (过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。Sentinel 对这个问题采取了两种手段:
- 通过并发线程数进行限制
- 和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
- 除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
整合测试:
A、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
B、使用 Nacos 注册中心
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
C、定义 fallback 实现
在服务消费者中,实现 feign 远程接口,接口的实现方法即为调用错误的容错方法
public class OrderFeignServiceFallBack implements OrderFeignService {
@Override
public Resp<OrderVo> getOrderInfo(String orderSn) {
return null;
}
}
D、定义 fallbackfactory 并放在容器中
@Component
public class OrderFeignFallbackFactory implements FallbackFactory<OrderFeignServiceFallBack> {
@Override
public OrderFeignServiceFallBack create(Throwable throwable) {
return new OrderFeignServiceFallBack(throwable);
}
}
E、改造 fallback 类接受异常并实现容错方法
public class OrderFeignServiceFallBack implements OrderFeignService {
private Throwable throwable;
public OrderFeignServiceFallBack(Throwable throwable){
this.throwable = throwable;
}
@Override
public Resp<OrderVo> getOrderInfo(String orderSn) {
return Resp.fail(new OrderVo());
}
}
F、远程接口配置 feign 客户端容错
@FeignClient(value = "gulimall-oms",fallbackFactory = OrderFeignFallbackFactory.class)
public interface OrderFeignService {
@GetMapping("/oms/order/bysn/{orderSn}")
public Resp<OrderVo> getOrderInfo(@PathVariable("orderSn") StringorderSn);
}
G、开启 sentinel 代理 feign 功能
在 application.properties 中配置
feign.sentinel.enabled=true
测试熔断效果。当远程服务出现问题,会自动调用回调方法返回默认数据。
更快的容错方式
H、使用 @SentinelResource,并定义 fallback
@SentinelResource(value = "order",fallback = "e")
Fallback 和原方法签名一致,但是最多多一个 Throwable 类型的变量接受异常。
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
需要给容器中配置注解切面
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
在控制台添加降级策略
I、测试降级效果
当远程服务停止,前几个服务会尝试调用远程服务,满足降级策略条件以后则不会再尝试调用远程服务
四、整合 Sentinel 测试限流 (流量控制)
什么是流量控制?
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
流量控制设计理念
流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系
- 运行指标,例如 QPS、线程池、系统负载等
- 控制的效果,例如直接限流、冷启动、排队等
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
A、引入 Sentinel starter
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
B、接入限流埋点
- HTTP 埋点
Sentinel starter 默认为所有的 HTTP 服务提供了限流埋点,如果只想对 HTTP 服务进行限流,那么只需要引入依赖,无需修改代码。 - 自定义埋点
如果需要对某个特定的方法进行限流或降级,可以通过 @SentinelResource 注解来完成限流
的埋点,示例代码如下:
@SentinelResource("resource")
public String hello() {
return "Hello";
}
当然也可以通过原始的 SphU.entry(xxx) 方法进行埋点, 可以参见 Sentinel 文档
C、配置限流规则
Sentinel 提供了两种配置限流规则的方式:代码配置 和 控制台配置。
- 通过代码来实现限流规则的配置。一个简单的限流规则配置示例代码如下,更多限流规则配置详情请参考 Sentinel 文档。
https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
#%E5%AE%9A%E4%B9%89%E8%A7%84%E5%88%99
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule = new FlowRule();
rule.setResource(str);
// set limit qps to 10
rule.setCount(10);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
- 通过控制台进行限流规则配置
1、下载控制台:http://edas-public.oss-cn-hangzhou.aliyuncs.com/install_package/demo/sentinel-dashboard.jar
2、启动控制台,执行 Java 命令 java -jar sentinel-dashboard.jar 完成 Sentinel 控制台的启动。 控制台默认的监听端口为 8080。
D、启动应用并配置
增加配置,在应用的 /src/main/resources/application.properties 中添加基本配置信息
spring.application.name=sentinel-example
server.port=18083
spring.cloud.sentinel.transport.dashboard=localhost:8080
E、控制台配置限流规则并验证
访问 http://localhost:8080 页面
如果您在控制台没有找到应用,请调用一下进行了 Sentinel 埋点的 URL 或方法,因为 Sentinel 使用了 lazy load 策略。
任意发送请求,可以在簇点链路里面看到刚才的请求,可以对请求进行流控。
测试流控效果
E、自定义流控响应
package com.fancy.gulimall.seckill.config;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.fancy.common.exception.BizCodeEnume;
import com.fancy.common.utils.R;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
public class SeckillSentinelConfig {
public SeckillSentinelConfig(){
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler(){
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
R error = R.error(BizCodeEnume.TOO_MANY_REQUEST.getCode(), BizCodeEnume.TOO_MANY_REQUEST.getMsg());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
}
});
}
}
F、持久化流控规则
默认的流控规则是保存在项目的内存中,项目停止再启动,流控规则就是失效。我们可以持久化保存规则:
生产环境使用模式:
我们推荐通过控制台设置规则后将规则推送到统一的规则中心, 客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更。
解决方案:
DataSource 扩展常见的实现方式有:
- 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更。
- 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
推模式:使用 Nacos 配置规则
1、引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.6.3</version>
</dependency>
2、编写配置类
@Configuration
public class SentinelConfig {
public SentinelConfig(){
//1、加载流控策略
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel", source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
//2、加载降级策略
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel", source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
//3、加载系统规则
ReadableDataSource<String, List<SystemRule>> systemRuleDataSource = new NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel", source -> JSON.parseObject(source, new TypeReference<List<SystemRule>>() {}));
SystemRuleManager.register2Property(systemRuleDataSource.getProperty());
//4、加载权限策略
ReadableDataSource<String, List<AuthorityRule>>
authorityRuleDataSource = new NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel", source -> JSON.parseObject(source, new TypeReference<List<AuthorityRule>>() {}));
AuthorityRuleManager.register2Property(authorityRuleDataSource.getProperty());
}
}
参照 https://github.com/alibaba/Sentinel/wiki/Dynamic-Rule-Configuration 查看更多控制规则
3、在 nacos 中创建 dataId,并使用 json 格式
4、添加一条流控规则测试
[
{
"resource": "/ums/member/list",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
配置含义说明:
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
- resource:资源名,即限流规则的作用对象
- count: 限流阈值
- grade: 限流阈值类型 (QPS 或并发线程数)
- limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
- strategy: 调用关系限流策略
- controlBehavior: 流量控制效果 (直接拒绝、Warm Up、匀速排队)
5、系统规则,降级规则等均可添加
[
{
"resource": "/ums/member/list",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
},
{
"highestSystemLoad": -1,
"highestCpuUsage": 0.99,
"qps": 2,
"avgRt": 10,
"maxThread": 10
}
]
6、最终效果
Sentinel 控制台改变流控规则,不能推送到 nacos 中,
Nacos 中改变流控规则可以实时观察到变化
第 2 步 API 的方式,可以直接变为配置方式:
在 application.properties 中配置
spring.cloud.sentinel.datasource.ds.nacos.server-addr=127.0.0.1:8848
spring.cloud.sentinel.datasource.ds.nacos.data-id=sentinel
spring.cloud.sentinel.datasource.ds.nacos.group-id=demo
spring.cloud.sentinel.datasource.ds.nacos.rule-type=flow
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=127.0.0.1:8848
spring.cloud.sentinel.datasource.ds1.nacos.data-id=sentinel
spring.cloud.sentinel.datasource.ds1.nacos.group-id=demo
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=system
ds,ds1 是随便写的。