分布式系统遇到的问题
服务雪崩
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应
导致服务不可用的原因:
- 激增流量
- 不稳定的服务依赖
容错机制
常见的容错机制:
- 超时机制: 在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以一直资源耗尽的问题
- 服务限流:
- 隔离: 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回有好的提示),而不是无休止的等待或者看到系统崩溃
- 服务熔断: 远程服务不稳定或网络抖动时暂时关闭,叫做服务熔断。
- 服务降级: 有服务熔断,必然要有服务降级。所谓的降级,就是当某个服务熔断之后,服务将不在被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。例如:(备用接口/缓存/mock数据)。这样做,虽然服务水平下降,但好歹可用,会比直接挂掉强,当然这也要看适合的业务场景。
sentinel介绍:分布式系统的流量防卫兵
是阿里巴巴开源的,面向分布式服务架构的高可用防护组件
随着微服务的流行,服务和服务之间稳定性变得越累越重要。Sentinel是面向分布式服务架构的流量控制组件。主要以流量为切入点,从限流、流量整形、熔断降级、系统负载均衡博阿虎、热点防护等多个维度来帮助开发者保障微服务的稳定性
源码地址:https://github.com/alibaba/Sentinel
官方文档: https://github.com/alibaba/Sentinel/wiki/介绍
Sentinel具有以下特征:
- 丰富的应用场景
- 完备的实时监控
- 广泛的开源生态
- 完善的SPI扩展点
快速开始:
在官方的文档中,定义的Sentinel进行资源保护的几个步骤:
- 定义资源
- 定义规则
- 检验规则是否生效
需要引入的资源
<!--引入web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入sentinel-core的核心库-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
流控规则初体验
流控规则代码实现
package com.tuling.nacosconfig.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* @Author:Mr Wang
* @QQ:1046065349
* @CreateTime: 2021/10/31 18:39
* @Descirption:
*/
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
/**
* 进行流量的控制
*/
@RequestMapping("/hello")
public String hello(){
Entry entity = null;
try {
//sentinel针对资源进行限制
//资源的名称一般会跟接口的地址保存一直
entity = SphU.entry(RESOURCE_NAME);
//被保护的业务逻辑
String str = "hello world";
log.info("======"+str+"=====");
return str;
} catch (BlockException e) {
e.printStackTrace();
//资源访问组织,被限流或者被降级
log.info("block");
return "被流控了";
}catch (Exception ex){
//若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex,entity);
}finally {
if(entity != null){
entity.exit();
}
}
return null;
}
/**
* 在Bean初始化之前会通过@PostConstruct注解先初始化此方法
* 定义一些流规则
*/
@PostConstruct
private static void initFlowRules(){
//流控规则
List<FlowRule> rules = new ArrayList<>();
//流控
FlowRule rule = new FlowRule();
//设置受保护的资源,为哪个资源进行流控
rule.setResource(RESOURCE_NAME);
//设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置受保护资源的阈值
//Set linit QPS to 20
rule.setCount(1);
rules.add(rule);
//加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
@SentineIResource
@SentineIResource
:此注解可以改善接口中资源定义和流控、降级后的处理方法
添加依赖
<!--如果要使用SentinelResoutce的注解,需要添加此依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
使用方法:
- 配置一个Bean
SentinelResourceAspect
属性:
- value:定义资源,资源的名称对应的为接口的名称
- blockHandler:设置流控降级后的处理方法,默认改方法必须声明在同接口的类中
- blockHandlerClasse:可以将流控降级处理后的方法放在别的类中。这里降级的处理方法需要定义为静态的
流控降级方法的说明:
- 此方法必须是一个public
- 返回值一定要和源方法保持一致
- 参数也要包含源方法的参数,顺序要保持一致,可以在参数的最后增加一个BlockEcxeption。可以通过异常区分是什么规则
降级规则初体验
控制台部署
帮助文档
可以从官网下载最新版本的控制台jar包,使用java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
启动。
-Dserver.port=8080
:用于指定Sentinel的控制台端口为8080
用户可以通过如下参数进行配置
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为 sentinel-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel-Dserver.servlet.session.timeout=7200
于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟
整合后的处理命令java -Dserver.port=8852 -Dsentinel.dashboard.auth.username=nacos -Dsentinel.dashboard.auth.password=nacos -Dserver.servlet.session.timeout=7200 -jar sentinel-dashboard-1.8.1.jar
客户端接入控制台
客户端需要引入Transport模块来与Sentinel控制台进行通信,可以在pom文件中引入jar包
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>x.y.z</version>
</dependency>
引入控制台后的配置
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口(默认是 8719)
从 1.6.3 版本开始,控制台支持网关流控规则管理。您需要在接入端添加 -Dcsp.sentinel.app.type=1 启动参数以将您的服务标记为 API Gateway,在接入控制台时您的服务会自动注册为网关类型,然后您即可在控制台配置网关规则和 API 分组
整合Springcloud alibaba
引入依赖
<!--加入阿里巴巴sentinel启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改yml文件,配置控制台的地址
# 给对应的服务起一个名称(nacos会将改名称当作服务名称)
spring:
application:
name: order-service
cloud:
sentinel:
transport:
dashboard: 111.229.181.158:8852
如果在实时监控中看不到信息,可以看下机器列表中的ip是否文本机的ip,可以修改yml配置文件增加client-ip: localhost
尽量设置客户端和控制台在同一个机器上,或者两台机器能够互通
QPS-流控规则
流量控制: 其原理是监控应用流量的QPS或并发线程等指标.当达到指定的阈值时对流量进行控制,以避免被顺势的流量高峰冲垮。从而保障应用的高可用性。
- 应对洪峰流量:秒杀,大促,下单,订单回流操作
- 消息型场景:削峰填谷 冷热启动
- 付费系统:根据使用流量付费
- API Gateway:精准控制API流量
- 任何应用:探测应用中运行的慢程序块进行限制
最普适的场景
- Provider端控制脉冲流量
- 针对不同的调用来源进行流控
- Web接口流控
如何配置规则
- 梳理核心接口
- 通过事前压测评估核心接口容量,配置QPS阈值
若是想使用自定义的流控返回信息需要使用注解@SentinelResource
进行开发配置
@SentinelResource(value = "add",blockHandler = "flowBlockHandler")
@RequestMapping("/add")
public String add(){
System.out.println("下单成功!");
//使用远程工具进行调用
// String message = restTemplate.getForObject("http://localhost:8011/stock/reduce", String.class);
//使用nacos注册服务中心发现调用订单系统
//String message = restTemplate.getForObject("http://stock-service/stock/reduce", String.class);
String message = stockFeignService.reduce();
System.out.println(message);
return "Hello World"+message;
}
public String flowBlockHandler(BlockException ex){
return "被流控了....";
}
并发线程数-流控规则
并发数控制用于保护业务线程池不被慢调用耗尽。例如:当应用所依赖的下游应用由于某种原因导致服务不稳定,影响延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用情况,业内有使用隔离反感的,比如通过不同业务逻辑使用不同线程池来隔离业务自身之际爱你的资源争抢。这种隔离反感虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的overhead比较大,特别是对低延迟时调用有比较大的影响。Sentinel并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离,并发数控制通常在调用端进行配置。
BlockException统一异常处理
Spring mvc接口资源限流入口在HandlerInterceptor
的实现类AbstractSentinelInterceptor
的preHandle方法中,对异常的处理是BlockException
的实现类sentinel1.7.1
引入了sentinel-spring-webmvc-adaper.jar
自定义BlockExceptionHandler的实现类统一处理BlockException
关联流控模式
流控模式
基于调用关系的流量控制,调用关系包括调用方,被调用方;一个方法可能会调用其他方法,形成一个调用链路的层次关系
直接: 资源调用达到设置的阈值后直接被流控抛出异常
关联:
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段读操作和写操作存在争抢,读的速度过高会影响写的速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量,可使用关联限流来避免具有关联关系的资源之间过渡的争抢,举例来说,readdb和wrtie_db这两个资源非别代表数据库的读写,我们可以给read_db设置限流规则来达到写优先的目的:设置strategy为RuleConstant.STRATEGY_RELATE同时设置refResource为write_db。这样当写库操作过于频繁的时候,读数据请求会被限流。
链路_ 根据调用链路入口限流
链路流控模式
高版本在测试链路时候不会生效,需要在配置文件中配置spring.cloud.sentinel.web-contet-unfix:false
流控效果介绍
快速失败:
Warm Up:
排队等待:
预热流控管理
即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬时把系统压垮,通过冷启动,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮.
冷加载因子:codeFactor默认是3,即请求QPS从thresold/3 开始,经预热时逐渐升至设置的QPS阈值.
通常冷启动的过程系统允许通过的QPS曲线如下图所示
排队等待
匀速排队(脉冲流量)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,该方式的作用如下图:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒处于空闲状态,我们希望系统能够在接下来空闲的时间处理请求,而不是第一秒直接拒绝多余的请求
熔断降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用。避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用段)进行配置。
熔断降级与隔离,通常在consumer端组合配置
- 并发控制(信号量隔离)
- 基于慢调用比例熔断
- 基于异常比例熔断
触发熔断后的处理逻辑
- 提供fallback实现(服务降级)
- 返回错误result
- 读缓存(DB访问降级)
熔断降级字段
熔断策略
- 慢调用比例
选择以慢调用比例作为阈值,需要设置允许的慢调用TR(即最大的响应时间),请求的影响时间大于该值则统计为慢调用。当统计时长内请求的数目大雨设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断,经过熔断时长后熔断起会进入探测恢复状态(HALF-PEN状态),若接下来的一个请求响应时间小雨设置的慢调用的RT则结束熔断,若大于设置的慢调用TR,则会在侧熔断
- 异常比例
- 异常数
整合openfeign降级
引入依赖:spring-cloud-starter-alibaba-sentinel
,spring-cloud-starter-openfeign
修改application.yml文件对feign支持
feign:
sentinel:
enable: true #添加fegin对sentinel的支持
添加openFeign的接口
添加openfeign的fallback实现类
此类需要实现上一步骤中添加的feign的接口
热点参数流控(热点识别流控)
何为热点:热点即为经常访问的数据。很多时候我们希望统计某个热点数据中心访问频次最高的数据,并对其访问进行限制。
实现原理: 热点淘汰策略(LRU)+Token Bucket流控
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源进行调用进行限流。热点参数限流可以看出是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
单机阈值: 假设大部分值都是热点参数,那单机阈值主要针对热点参数进行流控,后续额外针对普通参数值进行流控。
系统保护规则
规则持久化
1. Sentinel持久化模式
推送模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
原始模式 | API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource ) |
简单,无任何依赖 | 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境 |
Pull 模式 | 扩展写数据源(WritableDataSource ), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等 |
简单,无任何依赖;规则持久化 | 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。 |
Push 模式 | 扩展读数据源(ReadableDataSource ),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。 |
规则持久化;一致性;快速 | 引入第三方依赖 |
基于Nacos配置中心控制台实现推送
官方demo:sentinel-demo-nacos-datasource
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactid>sentinel-datasource-nacos</artifactid>
</dependency>