一、背景
在高并发的状态下服务器被访问量很多,但是服务器中可连接的线程数目有限。故访问量一旦超限,很容易导致其他服务无法访问,甚至让整个系统崩溃。为了解决这种问题,需要对某些接口访问的数量进行限制来对该服务进行保护。为此,服务降级与熔断产生了。
二、服务保护的基本概念和基本理论
2.1 服务限流/熔断
服务限流指的是限制某个服务接口高并发状态下的访问数目,即阈值。当服务请求数量超过阈值时,服务器就开启自我保护机制,不让访问请求访问业务逻辑层,而是执行服务降级方法fallback,给个自定义的提示,这就是服务熔断。
2.2 服务降级
当服务开启保护模式时,服务在超过阈值的情况下,不访问业务逻辑层,而执行fallback方法给个自定义提示,对应的fallback方法为服务降级。
2.3 服务雪崩
默认情况下,tomcat服务器只有一个线程处理请求。在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上, 那么tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问。比如,笔者在工作的第一家公司因为这个问题导致上货失败,出现503错误,被罚款了300元。为了解决该问题,线程隔离机制就此诞生了。
2.4 服务隔离
服务隔离就是讲不同的请求相互进行隔离,分为信号量和线程池隔离模式两种模式。
服务的线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
服务的信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。
三、sentinel相关的定义和使用
3.1 sentinel的基本定义
Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。其中文官方介绍地址是https://github.com/alibaba/Sentinel/wiki。
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 的主要特性:
3.2 sentinel与Hystrix 区别
hystrix官网中介绍的情况翻译成中文是这样的:Hystrix是一种能够通过添加潜在兼容性和容错性逻辑来控制分布式系统的交互的库。hystrix通过隔离服务间的接触点、阻断这些接触点的错误传播,以及提供服务降级机制的方式来实现以上功能,这些方法能够完整地提升系统的全局的韧性。
可以看到 Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。
具体对比如下面的表格:
3.3 sentinel整合spring boot
3.3.1 maven引入
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.3.2手动配置相关类:
下面是对应的规则配置,与controller分离解耦:
@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
private static final String GETORDER_KEY = "getOrder";
@Override
public void run(ApplicationArguments args) throws Exception {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY); //拦截的url
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
log.info(">>>限流服务接口配置加载成功>>>");
}
}
controller对应的接口加入@SentinelResource注解,value表示的是rule中对应限流的url,blockHandler指的是对应服务降级的方法,效果如下:
@RestController
@Slf4j
public class OrderController {
@SentinelResource(value = "getOrder", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrder")
public String getOrder() {
return "order";
}
/**
* 被限流后返回的提示
*
* @param e
* @return
*/
public String getOrderQpsException(BlockException e) {
log.error("该接口已经被限流啦!", e);
return "该接口已经被限流啦!";
}
}
3.3.3 与gateway网关的整合
在gateway网关部分,由于引入spring-boot-starter-web与gateway网关核心的依赖出现冲突造成启动报错,故需要有特殊的方式引入相关sentinel与网关的依赖。Maven引入如下:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.7.1</version>
</dependency>
相关类的配置:
@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
initGatewayRules();
}
/**
* 配置限流规则
*/
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("fxf")
// 限流阈值
.setCount(1)
// 统计时间窗口,单位是秒,默认是 1 秒
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
}
@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("fxf")
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
GatewayRuleManager.loadRules(rules);
}
}
四、参考资料
每特教育第六期sentinel
sentinel官方文档