Spring Cloud 限流熔断

限流的核心

高并发三把斧:限流、降级、缓存

  • 限流:限流是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理,程序中的限流就是限制请求,请求包括客户端发起的请求,应用内服务间的请求,很多平台都有限流,例如我们调用第三方的api的时候,对方都会限制每天最多只能调用几次之类的,当请求量超过系统的最大容量后,访问延迟就会增加,超过峰值的流量会拖累整个系统,出现宕机。因此,需要提前流量控制,对于超过峰值的流量,可以直接拒绝掉或者选择随机拒绝。限流结合业务自定义配置,优先保证核心服务的正常响应,非核心服务可直接关闭。
  • 降级:降级不是为了防止故障的发生,而是当故障发生后,怎么减小故障所造成的损失。比如,系统正常时提供的服务能力是100%,出现系统故障后,我们有措施能让系统服务能力不直接降到了0,而是还能提供部分(比如50%)的服务能力。场景:依赖的服务耗时增加、边缘业务降级
  • 缓存:缓存的目的是提升系统访问速度和增大系统处理容量

限流方案

1. 前端限流

浏览器、app、小程序

  • JS提交将按钮置灰 ;
  • 验证码、小游戏、问题回答

2. 接入层限流:LVS

对地区限流;对IP限流
接入层指的是请求流量的入口,我们可以在这里做很多控制,比如:负载均衡,缓存,限流等。

3. 代理层限流

IP限流、用户限流、硬件设备限流(Android设备号)、连接数限流、其他限流策略
nginx中针对限流有两个模块可以处理:

  • ngx_http_limit_req_module;连接数限流模块
  • ngx_http_limit_conn_module;请求限流模块

Nginx 常用于服务器反向代理,达到实现负载均衡和保护后端的应用服务器的目的。Nginx 主要通过限制访问频率和并发连接数两种方式达到限制目的,Nginx 配置文件支持丰富的配置命令,比如下面一种配置示例:
比如 limit_req_zone 的命令含义是对限制的对象(如 URL 地址、服务器地址和客户端 IP 地址等)设置最大访问速率,zone 部分定义了共享内存区的名称和大小。limit_conn 可以对指定的 IP 甚至是所在服务主机限制并发连接数量。

4. 服务层限流

Tomcat、springboot等
Server.xml:maxConnect(Http连接数限流)、maxThreads(线程数限流)

5. 接口层限流(API)、业务方法限流

线程数限流、计数器、Sentinel、Hystrix

Hystrix与Sentinel对比

比较项SentinelHystrix说明
隔离策略信号量隔离(并发线程数限流)(模拟信号量)线程池隔 离/信号 量隔离Sentinel不创建线程依赖tomcat或jetty容器的 线程池,存在的问题就是运行容器的线程数量 限制了sentinel设置值的上限可能设置不准。 比如tomcat线程池为10,sentinel设置100是 没有意义的,同时隔离性不好 hystrix使用自己 创建的线程池,隔离性会更好
熔断降级策 略基于响应时 间、异常比 率、异常数基于异常 比率快速失败的本质功能
实时统计实现滑动窗口(LeapArray)滑动窗口 (基于 RxJava)
动态规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
注解支持支持
限流基于 QPS,支持基于调用关系的限流有限的支持(并发线程数或信号量大小)快速失败的本质功能
流量整形支持预热模式、匀速器模式、预热排队模式不支持(排队)
系统自适应保护支持(仅对linux/unix生效)不支持设置一个服务器最大允许处理量的阈值
控制台提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等简单的监控查看接近实时数据控制台是非常有竞争力的功能,因为能集中配置限制数据更方便,但是展示数据和实时性没有hystrix直观。
配置持久化ZooKeeper,Apollo,Nacos、本地文件Git/svn/本地文件Sentinel客户端采用直接链接持久化存储,应用客户端引用了更多的依赖,同样的存储链接可能有多个配置
动态配置支持支持
黑白名单支持支持
springcloud集成非常高Spring boot使用hystrix集成度更高
整体优势集中配置设置及监控+更细的控制规则漂亮的界面+接近实时的统计结果docker容器化部署之后sentinel可能更会发挥作用

基于Hystrix微服务限流

引入maven依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

修改application.properties

加上feign.hystrix.enabled=true ,启用hystrix

spring.application.name=service-order
server.port=9001
feign.hystrix.enabled=true
spring.cloud.nacos.discovery.server-addr=localhost:8848

修改启动类

加上@EnableCircuitBreaker,启用断路器

加上@EnableFeignClients,扫描@FeignClient注解

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableFeignClients
public class ServiceOrderHystrixApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceOrderHystrixApplication.class, args);
}
}

使用Hystrix线程池限流

@RequestMapping(value = "/testQPS1", method = RequestMethod.GET)
    @ResponseBody
    @HystrixCommand(commandProperties = {
/*线程池隔离*/@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "THREAD"),
/*线程超时时间*/@HystrixProperty(name =HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value = "3000"),
/*设置超时的时候是否中断线程,默认为true*/
               @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT, value = "false")
            })
    public String testQPS1() {
        try {
            System.out.println(Thread.currentThread().getName() + ":testQPS1 before sleep 5s....");
            Thread.sleep(1000 * 5);
            System.out.println(Thread.currentThread().getName() + ":testQPS1 after sleep 5s....");
        } catch (InterruptedException e) {
//e.printStackTrace();
        }
        return "ok";
    }

使用Hystrix信号量限流

@RequestMapping(value = "/testQPS2", method = RequestMethod.GET)
    @ResponseBody
    @HystrixCommand(fallbackMethod = "flowError",
            commandProperties = {
/*信号量隔离*/@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
/*线程超时时间*/ @HystrixProperty(name =HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value = "3000"),
/*最大并发数*/ @HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="3")
            })
    public String testQPS2() {
        try {
            System.out.println(Thread.currentThread().getName() + ":testQPS2 before sleep 5s....");
            Thread.sleep(1000 * 2);
            System.out.println(Thread.currentThread().getName() + ":testQPS2 after sleep 5s....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ok";
    }

使用FeignClient+fallback进行服务降级

  1. 新增FeignStorageService和FeignStorageServiceFallback
@FeignClient(value = "service-storage", fallback =
FeignStorageServiceFallback.class)
public interface FeignStorageService {
@RequestMapping("/storage")
String storage();
}
@Service
public class FeignStorageServiceFallback implements FeignStorageService {
@Override
public String storage() {
System.out.println("当前访问人数过多,请稍后再试!");
return "当前访问人数过多,请稍后再试!";
}
}
  1. 测试feign调用
@RequestMapping(value = "/orderByFeign", method = RequestMethod.GET)
public String orderByFeign(String params) {
String result = feignStorageService.storage();
return result;
}

基于Sentinel实战微服务限流

Sentinel 环境搭建

下载地址 : https://github.com/alibaba/Sentinel/releases

启动jar服务命令:

java -Dserver.port=10001 -Dcsp.sentinel.dashboard.server=localhost:10001 -jar sentinel-dashboard-1.7.1.jar

浏览器打开:http://127.0.0.1:10001 ,账号和密码都是sentinel

Springboot 整合 Sentinel

Springboot 整合 Sentinel有2种方式:

  1. 使用纯代码或者注解在工程中进行配置
  2. Sentinel 控制台 + 注解进行配置
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.14</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacosdiscovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

配置文件application.properties
spring.application.name=service-order
server.port=6001
spring.cloud.nacos.discovery.server-addr=localhost:8848
feign.sentinel.enabled=true
#禁用Sentinel 的赖加载
spring.cloud.sentinel.eager=true
# 配置Sentinel
spring.cloud.sentinel.transport.dashboard=localhost:10001
ribbon.ReadTimeout=3000
ribbon.ConnectTimeout=3000
logging.level.com.alibaba.nacos=ERROR
logging.level.com.netflix.loadbalancer=ERROR
logging.level.com.netflix.config=ERROR
management.endpoints.web.exposure.include=*
基于代码实现限流
resource:资源名,即限流规则的作用对象
limitApp:流控针对的调用来源,若为default则不区分调用来源
grade:限流阈值类型,即QPS还是并发线程数,0代表线程数,1代表QPS
count:限流阈值
strategy:调用关系限流策略
controlBehavior:流量控制效果(快速失败,Warm Up,排队等待)
clusterMode:是否集群
配置限流规则
public class SentinelController {
//限流规则名称,与作用的接口名称一致
private static final String LIMIT_KEY_1 = "testQPS1";
private static final String LIMIT_KEY_2 = "testQPS2";
//@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
@PostConstruct
public void initFlowQpsRule() {
//设置testQPS1接口限流规则
List<FlowRule> flowRules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
//设置限流规则名称
rule1.setResource(LIMIT_KEY_1);
//设置QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置QPS为1
rule1.setCount(1);
rule1.setLimitApp("default");
flowRules.add(rule1);
//设置testQPS2限流规则
FlowRule rule2 = new FlowRule();
//设置限流规则名称
rule2.setResource(LIMIT_KEY_2);
//设置QPS限流
rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置QPS为2
rule2.setCount(2);
rule2.setLimitApp("default");
flowRules.add(rule2);
FlowRuleManager.loadRules(flowRules);
System.out.println("-------------------配置限流规则成功--------------------
---");
}
}
新增限流接口testQPS1
@GetMapping("/testQPS1")
public String testQPS1() {
Entry entry = null;
try {
entry = SphU.entry(LIMIT_KEY_1);
} catch (BlockException e) { // 限流后进入此异常
//e.printStackTrace();
System.out.println("当前访问人数过多,请稍后再试!");
return "当前访问人数过多,请稍后再试!";
} finally {
// SphU.entry与entry.exit()成对出现,否则会导致调用链记录异常
if (entry != null) {
	entry.exit();
	}
}
System.out.println("testQPS1 success");
return "testQPS1 success";
}

基于注解实现限流
// value的值即为限流配置的key
@SentinelResource(value = LIMIT_KEY_2, blockHandler = "testBlockHandler")
@GetMapping("/testQPS2")
public String testQPS2() {
System.out.println("testQPS2 success");
return "testQPS2 success";
}
public String testBlockHandler(BlockException e) {
System.out.println("当前访问人数过多,请稍后再试!");
return "当前访问人数过多,请稍后再试!";
}
流控参数
  • 资源名:唯一名称,默认是请求路径,可自定义(后续讲解)。
  • 针对来源:指定对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制。

在这里插入图片描述

sentinel共有三种流控模式,分别是:

  • 直接(默认):接口达到限流条件时,开启限流。
  • 关联:当关联的资源达到限流条件时,开启限流 (适合做应用让步)。

直接流控模式

直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。前面的两个案例都是默认直接流控。

关联流控模式

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。

配置流控效果

  1. 快速失败(默认): 直接失败,抛出异常,不做任何额外的处理,是最简单的效果
  2. Warm Up:它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景,底层采用令牌桶算法实现
  3. 排队等待:让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。底层采用漏桶算法实现
降级参数

在这里插入图片描述

  1. RT(平均响应时间)

    平均响应时间 ( DEGRADE_GRADE_RT ):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值( count ,以 ms 为单位),那么在接下的时间窗口( DegradeRule 中的 timeWindow ,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException )。注意 Sentinel默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

在这里插入图片描述

上图表示 需要1s持续进入5个请求,并且 平均响应时间大于 阈值,才会触发降级,打开断路器,等时间窗口结束,再关闭降级

  1. 异常比例

    异常比例 ( DEGRADE_GRADE_EXCEPTION_RATIO ):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值( DegradeRule 中的 count )之后,资源进入降级状态,即在接下的时间窗口( DegradeRule 中的 timeWindow ,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0] ,代表 0% - 100%。

C:\Users\daixn\AppData\Local\Temp\1607265480544.png

上图表示 QPS>=5 且异常比例超过阈值,触发降级,打开断路器,等时间窗口结束,再关闭降级

  1. 异常数

    异常数 ( DEGRADE_GRADE_EXCEPTION_COUNT ):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

    异常数是按分钟来统计的,所以时间窗口必须大于等于60s

    在这里插入图片描述

    上图就是表示,在一分钟内统计异常数超过阈值了,然后出发降级,打开断路器,等时间长款结束,关闭降级

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

numun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值