1、高并发带来的问题
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因 或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现 网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。
2、服务保护的基本概念
服务限流/熔断
服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用我们的服务降级的方法,不会执行业务逻辑操作,直接走本地falback的方法,返回一个友好的提示。
服务降级
在高并发的情况下, 防止用户一直等待,采用限流/熔断方法,使用服务降级的方式返回一个友好的提示给客户端,不会执行业务逻辑请求,直接走本地的falback的方法。
提示语:当前排队人数过多,稍后重试~
服务的雪崩效应
默认的情况下,Tomcat或者是Jetty服务器只有一个线程池去处理客户端的请求,
这样的话就是在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上,
那么就会产生tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问。
假设我们的tomcat线程最大的线程数量是为20,这时候客户端如果同时发送100个请求会导致有80个请求暂时无法访问,就会转圈。
服务的隔离的机制
服务的隔离机制分为信号量和线程池隔离模式
服务的线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
服务的信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。
3、常见的容错方案
常见的容错思路
常见的容错思路有隔离、超时、限流、熔断、降级这几种,下面分别介绍一下。
隔 离
它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发 生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。常见的隔离方式有:线程池隔离和信号量隔离.
超 时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断 开请求,释放掉线程。
限 流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行, 一旦达到的需要 限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可 用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
服务熔断一般有三种状态:
·熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
· 熔断开启状态 (Open)
后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
· 半熔断状态 (Half-Open)
尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服 务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
降 级
降级其实就是为服务提供一个托底方案, 一旦服务无法正常调用,就使用托底方案。
4、常见的容错组件
·Hystrix
Hystrix是 由Netflflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联 失败,从而提升系统的可用性与容错性。
·Resilience4J
Resilicence4一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替 代产品。不仅如此,Resilicence4j还原生支持Spring Boot 1x/2.x,而且监控也支持和prometheus 等 多款主流产品进行整合。
·Sentinel
Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。
下面是三个组件在各方面的对比:
Sentinel | Hystrix | resilience4 | |
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 | 信号量隔离 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 | 基于异常比率、响应 时间 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基 于RxJava) | Ring Bit Buffer |
动态规则配置 | 支持多种数据源 | 支持多种数据 源 | 有限支持 |
扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
基于注解的支持 | 支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
流量整形 | 支持预热模式、匀速器模式、预热排队 模式 | 不支持 | 简单的Rate Limiter 模式 |
系统自适应保护 | 支持 | 不支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、 查看秒级监控、机器发现等 | 简单的监控查 看 | 不提供控制台,可对 接其它监控系统 |
Sentinel 与hytrix区别
前哨以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。
前哨具有以下特征:
1.丰富的应用场景:前哨兵承接了阿里巴巴近10年的双十一大促流的核心场景,例如秒杀(即突然流量控制在系统容量可以承受的范围),消息削峰填谷,传递流量控制,实时熔断下游不可用应用等。
2.完备的实时监控:Sentinel同时提供实时的监控功能。您可以在控制台中看到接收应用的单台机器秒级数据,甚至500台以下规模的整合的汇总运行情况。
广泛的开源生态:Sentinel提供开箱即用的与其他开源框架/库的集成模块,例如与Spring Cloud,Dubbo,gRPC的整合。您只需要另外的依赖并进行简单的配置即可快速地接入Sentinel。
3.完善的SPI扩展点:Sentinel提供简单易用,完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理,适应动态数据源等。
Sentinel中文文档介绍:
介绍 · alibaba/Sentinel Wiki · GitHub
5、 什么是Sentine
Sentinel (分布式系统的流量防卫兵)是阿里开源的一套用于服务容错的综合性解决方案。它以流量
为切入点从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel 具有以下特征:
· 丰富的应用场景: Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用 应用等。
· 完备的实时监控:Sentinel提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒 级数据,甚至500台以下规模的集群的汇总运行情况。
· 广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与SpringCloud、 Dubbo、gRPC的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
· 完善的SPI扩展点:Sentinel提供简单易用、完善的SPI扩展接口。您可以通过实现扩展接口来快 速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel分为两个部分:
·核心库 (Java 客户端)不依赖任何框架/库,能够运行于所有Java 运行时环境,同时对 Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard) 基于 Spring Boot 开发,打包后可以直接运行,不需要额外的Tomcat 等应用容器。
6、Sentinel实现限流(SpringBoot项目整合)
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
方式一:手动配置管理Api限流接口
private static final String GETORDER_KEY = "getOrder";
@RequestMapping("/initFlowQpsRule")
public String initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
return "....限流配置初始化成功..";
}
@RequestMapping("/getOrder")
public String getOrders() {
Entry entry = null;
try {
entry = SphU.entry(GETORDER_KEY);
// 执行我们服务需要保护的业务逻辑
return "getOrder接口";
} catch (Exception e) {
e.printStackTrace();
return "该服务接口已经达到上线!";
} finally {
// SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
if (entry != null) {
entry.exit();
}
}
}
手动放入到项目启动自动加载
@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>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
log.info(">>>限流服务接口配置加载成功>>>");
}
}
方式二:注解形式配置管理Api限流
@SentinelResource value参数:流量规则资源名称、
blockHandler 限流/熔断出现异常执行的方法
Fallback 服务的降级执行的方法
@SentinelResource(value = GETORDER_KEY, blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderAnnotation")
public String getOrderAnnotation() {
return "getOrder接口";
}
/**
* 被限流后返回的提示
*
* @param e
* @return
*/
public String getOrderQpsException(BlockException e) {
e.printStackTrace();
return "该接口已经被限流啦!";
}
7、安装Sentinel 控制台
Sentinel 提供一个轻量级的控制台,它提供机器发现、单机资源实时监控以及规则管理等功能。
1 下载jar包,解压到文件夹
Releases · alibaba/Sentinel · GitHub
2 启动控制台
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar
8718属于 界面端口号 8719 属于api通讯的端口号
\#直接使用jar 命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.8.jar
3 通过浏览器访问localhost:8080进入控制台(默认用户名密码是 sentinel/sentinel)
Sentinel 的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上, 即在微服务中指定控制台的地址,并且还要开启一个跟控制台传递数据的端口,控制台也可以通过此端口调用微服务中的监控程序获取微服务的各种信息。
8、sentinel实现熔断降级
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
降级的策略
1.平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx 来配置。
2.异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
3.异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
① 平均的响应时间
如果在1s秒,平均有5个请求的响应时间大于配置的10rt毫秒时间 阈值,则会执行一定时间窗口的熔断和降级。
@SentinelResource(value = "getOrderDowngradeRtType", fallback = "getOrderDowngradeRtTypeFallback")
@RequestMapping("/getOrderDowngradeRtType")
public String getOrderDowngradeRtType() {
try {
Thread.sleep(300);
} catch (Exception e) {
}
return "getOrderDowngradeRtType";
}
public String getOrderDowngradeRtTypeFallback() {
return "服务降级啦,当前服务器请求次数过多,请稍后重试!";
}
对于降级策略RT的理解
平均响应时间RT超出阈值且在时间窗口内通过的请求超出---> 触发降级(断路器打开,这段时间窗口期都不能使用) --- > 等时间窗口结束后 ----> 关闭降级 --> 继续使用
② 异常的比例
当我们每秒的请求大于5的时候,会根据一定比例执行我们的熔断降级的策略
@SentinelResource(value = "getOrderDowngradeErrorType", fallback = "getOrderDowngradeErrorTypeFallback")
@RequestMapping("/getOrderDowngradeErrorType")
public String getOrderDowngradeErrorType() {
int j = 1 / 0;
return "正常执行我们的业务逻辑";
}
public String getOrderDowngradeErrorTypeFallback(int age) {
return "服务降级啦,当前服务器请求次数过多,请稍后重试!";
}
③ 异常的次数
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
对于降级策略异常数的理解:首先它是分钟级的,时间窗口一定要大于等于60秒
异常数(分钟统计)超过阈值 --> 触发降级(断路器打开) --> 待时间窗口结束 --> 关闭降级 继续使用。
9、Sentinel实现热点参数限流
热点参数流控规则,是一种更细粒度的流控规则,可以将规则设置到具体的参数上
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
/**
* @SentinelResource: 指定资源名称,作用与HystrixCommand大体一致
* blockHandler: 指定降级处理方法,类似于Hystrix的fallbackMethod兜底方法
*/
@GetMapping("/testHotKeyRule")
@SentinelResource(value = "testHotKeyRule", blockHandler = "testHotKeyRule_blockHandler")
public String testHotKeyRule(@RequestParam(value = "username", required = false) String username,
@RequestParam(value = "password", required = false) String password) {
return "[热点限流规则]testHotKeyRule..";
}
public String testHotKeyRule_blockHandler(@RequestParam(value = "username", required = false) String username,
@RequestParam(value = "password", required = false) String password,
BlockException exception) {
return "[热点限流规则-兜底方法]testHotKeyRule_blockHandler..";
}
上面的配置表示:当访问testHotKeyRule资源的时候,针对方法testHotKeyRule的第一个参数【这里对应我们controller中的username参数】的请求,如果一秒内的请求阈值QPS超过1次,那么将会触发兜底方法。
fallback与blockHandler的区别
fallback是服务熔断或者业务逻辑出现异常执行的方法(1.6版本以上)
blockHandler 限流出现错误执行的方法。
10、Sentinel如何保证规则的持久化
1)三种规则管理模式
默认的情况下Sentinel的规则是存放在内存中,如果Sentinel客户端重启后,Sentinel数据规则可能会丢失。
规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:
原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
push模式:控制台将配置规则推送到远程配置中心,例如Nacos。
Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
2)实现push模式
修改order-service服务
修改OrderService,让其监听Nacos中的sentinel规则配置。
具体步骤如下:
引入依赖
在order-service中引入sentinel监听nacos的依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配置nacos地址
在order-service中的application.yml文件配置nacos地址及监听的配置信息:
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848 # nacos地址
dataId: orderservice-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow # 还可以是:degrade、authority、param-flow
修改sentinel-dashboard源码
SentinelDashboard默认不支持nacos的持久化,需要修改源码。
解压sentinel源码包
然后并用IDEA打开这个项目,结构如下:
修改nacos依赖
在sentinel-dashboard源码的pom文件中,nacos的依赖默认的scope是test,只能在测试时使用,这里要去除:
将sentinel-datasource-nacos依赖的scope去掉:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
添加nacos支持
在sentinel-dashboard的test包下,已经编写了对nacos的支持,我们需要将其拷贝到main下。
修改nacos地址
然后,还需要修改测试代码中的NacosConfig类:
修改其中的nacos地址,让其读取application.properties中的配置:
在sentinel-dashboard的application.properties中添加nacos地址配置:
nacos.addr=localhost:8848
配置nacos数据源
另外,还需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2类:
让我们添加的Nacos数据源生效:
修改前端页面
接下来,还要修改前端页面,添加一个支持nacos的菜单。
修改src/main/webapp/resources/app/scripts/directives/sidebar/目录下的sidebar.html文件:
将其中的这部分注释打开:
修改其中的文本:
重新编译、打包项目
运行IDEA中的maven插件,编译和打包修改好的Sentinel-Dashboard:
启动
启动方式跟官方一样:
java -jar sentinel-dashboard.jar
如果要修改nacos地址,需要添加参数:
java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar