- 服务保护的基本概念
- 服务降级
在高并发情况下,防止用户一直等待,使用服务降级方式,直接走本地falback的方法,返回一个友好的提示。
- 服务限流
服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现。
- 服务熔断
熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级的falback方法返回一个友好提示,服务熔断和服务降级一起使用
- 服务的雪崩效应
服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口, 就会导致其他服务不可用
- 服务的隔离的机制
一 线程池隔离
每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
二 信号量隔离
设置每个服务接口处理线程数的阈值,超过该阈值会拒绝请求。
- Sentinel介绍
分布式系统的流量防卫兵,随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。
github介绍: https://github.com/alibaba/Sentinel/wiki
- Sentinel 具有以下特征:
丰富的应用场景:
Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。完备的实时监控:
Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。广泛的开源生态:
Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。完善的 SPI 扩展点:
Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。 - Sentinel 与hytrix区别
# Sentinel Hystrix 隔离策略 信号量隔离 线程池隔离/信号量隔离 熔断降级策略 基于响应时间或失败比率 基于失败比率 实时指标实现 滑动窗口 滑动窗口(基于 RxJava) 规则配置 支持多种数据源 支持多种数据源 扩展性 多个扩展点 插件的形式 基于注解的支持 支持 支持 限流 基于 QPS,支持基于调用关系的限流 不支持 流量整形 支持慢启动、匀速器模式 不支持 系统负载保护 支持 不支持 控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善 常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC Servlet、Spring Cloud Netflix
- Sentinel 环境搭建
- 下载jar包
https://github.com/alibaba/Sentinel/releases - 启动
java -jar sentinel-dashboard-1.7.1.jar
- 测试
访问: http://127.0.0.1:8080 默认账号/密码: sentinel/sentinel
此时是没有服务
- 安装成功,接下来项目整合
- 项目整合
- 创建boot项目
pom.xml引入依赖:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.2.RELEASE</version> </dependency> <!-- Sentinel服务保护--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel</artifactId> <version>0.2.2.RELEASE</version> </dependency> </dependencies>
application.yml配置:spring: application: ###服务的名称 name: pitch-order cloud: nacos: discovery: ###nacos注册地址 server-addr: 127.0.0.1:8848 sentinel: transport: ### sentinel连接地址 dashboard: 127.0.0.1:8080 eager: true server: port: 8001 tomcat: ### 最大线程数 max-threads: 20
- 测试类
mySentinelName: 定义流控规则的资源名,如果没有指定默认未@GetMapping的value值// mySentinelName:流控规则资源则名称(自定义) mySentinelException: 超过阈值执行的方法 @SentinelResource(value="mySentinelName", blockHandler = "mySentinelException" ) @GetMapping("/my-sentinel") public String mySentinel(){ return "hello mySentinel"; } public String mySentinelException(BlockException e) { e.printStackTrace(); return "访问次数过多,请稍后重试!!!"; }
mySentinelException: 定义客户端访问超过阈值的执行方法
注意: @SentinelResource也可以不用加 - 启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * Hello world! * */ @SpringBootApplication @EnableFeignClients public class AppOrder { public static void main( String[] args ){ SpringApplication.run(AppOrder.class); } }
- 测试
测试请求地址: http://127.0.0.1:8001/my-sentinel
查询sentinel控制台,此时pitch-order就存在了 - 配置隔离规则
选择流控规则-->新增流控规则
资源名: 为代码中@SentinelResource注解的value值,如果方法中没有加@SentinelResource则默认为@GetMapping的value值。
QPS&&单机阈值:表示该接口每秒只能访问5次,超过访问次数则执行mySentinelException方法
线程数&&单机阈值:表示该接口最多只能有5个线程处理请求,多余的请求则执行mySentinelException方法 - 访问测试
http://127.0.0.1:8001/my-sentinel
- success
- 介绍
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
- 降级策略
平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
- 测试代码
@SentinelResource(value="myTimeOutName", blockHandler = "mySentinelException" ) @GetMapping("/my-timeout") public String myTimeout(){ try { Thread.sleep(300); } catch (Exception e) { e.printStackTrace(); } return "success"; } public String mySentinelException(BlockException e) { e.printStackTrace(); return "超时了!!!"; }
- 配置RT降级规则
登录sentinel,选择'降级规则'-->‘新增降级规则’
RT&&10毫秒&&时间窗口5秒 表示: 该接口的方法如果平均响应时间超过10毫秒,则在5秒内都不会执行该接口,直接服务降级提示方法
- 测试
频繁刷新: http://127.0.0.1:8001/my-timeout
- 其他降级规则
异常比例:
表示规定时间(每秒)内访问的异常超过配置的比例,则在规定的时间窗口不能再访问。
异常数:
表示1分钟内访问接口的异常数超过配置的值,则在规定的时间窗口不能再访问。
- Sentinel持久化
默认的情况下Sentinel的规则是存放在内存中,如果Sentinel客户端重启后,Sentinel数据规则可能会丢失。
Sentinel持久化机制支持四种持久化的机制。
1. 本地文件
2. 携程阿波罗
3. Nacos
4. Zookeeper
- 项目配置
pom.xml添加依赖
application.yml配置如下:<!--sentinel 持久化整合nacos --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.5.2</version> </dependency>
spring: application: ###服务的名称 name: pitch-order cloud: nacos: discovery: ###nacos注册地址 server-addr: 127.0.0.1:8848 sentinel: transport: ### sentinel连接地址 dashboard: 127.0.0.1:8080 eager: true ### sentinel通过nacos实现持久化配置 datasource: ds: nacos: ### nacos连接地址 server-addr: localhost:8848 ## nacos连接的分组 group-id: DEFAULT_GROUP ###路由存储规则 rule-type: flow ### 读取nacos配置文件的 data-id 须对应 data-id: pitch-order-sentinel ### 读取培训文件类型为json data-type: json server: port: 8001
- 代码
// test流控规则资源则名称(自定义) mySentinelException: 超过阈值执行的方法 @SentinelResource(value="mySentinelName", blockHandler = "mySentinelException" ) @GetMapping("/my-sentinel") public String mySentinel(){ return "hello mySentinel"; }
- 启动nacos
登录nacos-->'配置列表'-->'+'
Data ID: 需要与application.yml的data-id一致[ { "resource": "mySentinelName", "limitApp": "default", "grade": 1, "count": 5, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
resource:资源名,为代码中@SentinelResource注解的value值
limitApp:流控针对的调用来源,若为 default 则不区分调用来源
grade:限流阈值类型(QPS 或并发线程数);0: 并发数量来限流,1:QPS来进行流量控制
count:限流阈值
strategy:调用关系限流策略
controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
clusterMode:是否为集群模式
选择 '发布' - 测试
http://127.0.0.1:8001/my-sentinel
- 基于网关整合sentinel
文档 : https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81
- 创建网关项目
pom.xml依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.0.0.RELEASE</version> </dependency> <!-- nacos整合服务注册与发现--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.2.RELEASE</version> </dependency> <!-- gateway整合sentinel--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.6.0</version> </dependency> </dependencies>
application.yml配置server: port: 80 ####服务网关名称 spring: application: name: pitch-gateway cloud: gateway: discovery: locator: ####允许从注册中心获取地址 enabled: true routes: ###路由id 自定义唯一 - id: pitch ####pitch-member为会员服务的名称 uri: lb://pitch-order/ filters: - StripPrefix=1 ###路由匹配规则 predicates: - Path=/order/** nacos: discovery: ###将网关服务注册到nacos server-addr: 127.0.0.1:8848 enabled: true
- 核心配置类
异常处理handlerimport com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; import java.util.Collections; import java.util.List; /** * @author xiaobo * @Description GatewayConfiguration * @createTime 2020-04-01 21:41 */ @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; } // 配置sentinel超过阈值的异常处理 @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { JsonSentinelGatewayBlockExceptionHandler jsonSentinelGatewayBlockExceptionHandler = new JsonSentinelGatewayBlockExceptionHandler(null, null); return jsonSentinelGatewayBlockExceptionHandler; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } }
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.List; public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler { public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { } @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { ServerHttpResponse serverHttpResponse = exchange.getResponse(); serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); byte[] datas = "{\"code\":403,\"msg\":\"访问次数过多,请稍后重试!!!\"}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas); return serverHttpResponse.writeWith(Mono.just(buffer)); } }
- 加载限流规则
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Set; @Component public class SentinelApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { // 项目启动是加载 initGatewayRules(); } /** * 配置限流规则 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); // pitch : 对应application.yml的路由策略(routes -id) rules.add(new GatewayFlowRule("pitch") // 限流阈值 .setCount(1) // 统计时间窗口,单位是秒,默认是 1 秒 .setIntervalSec(1) ); GatewayRuleManager.loadRules(rules); } }
- 启动测试
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AppGateway { public static void main( String[] args ) { SpringApplication.run(AppGateway.class); } }
- 测试
浏览器多次访问