高并发访问场景如果没有保护机制,所有的流量都进入服务器,会造成服务器宕机导致整个系统不可用,需要采取一定的系统保护策略,例如服务降级、限流和熔断等。
(说起熔断就想起股票熔断,WDNMD!!!)

服务限流的作用及实现

限制并发访问数或者显式一个时间窗口内允许出列请求数量来保护系统,达到限制数量则对当前请求进行处理采取对应的拒绝策略(跳转到错误页面、进入排队系统等)
实际开发中,限流体现在:

  • Nginx层添加限流模块限制平均访问速度
  • 设置数据连接池、线程池大小限制总并发数
  • Guava提供的Ratelimiter限制接口访问速度
  • TCP通信协议中的流量整形

计数器算法

在指定周期内累加访问次数,当访问次数达到设定的阈值时,触发限流策略,当即努人下一个时间周期时进行访问次数清零。例如短信发送的频次限制
image.png

  • 临界问题
    可能会出现间隔不到时间周期,但超过限制的次数
    image.png

滑动窗口算法

  • 目的
    解决计数器算法的临界问题
  • 原理
    在固定窗口中分别割出多个小时间窗口,在每个小时间窗口中记录访问次数,然后根据时间间窗口往前滑动并删除过期的小时间窗口。只需要统计滑动窗口范围内所有小窗口总计计数即可
  • 使用
    Sentinel采用该方法实现限流
    TCP网络通信协议也采用了滑动窗口算法来解决网络拥塞的情况

令牌桶限流算法

令牌桶是网络流量整形和速率限制最常使用的算法
image.png

  • 原理
    系统会以一个恒定速度忘固定容量的令牌桶中放入令牌,对于每一个请求,都需要从令牌桶中获得一个令牌,如果没有获得令牌则触发限流策略。
  • 例子
    若令牌生成速度每秒10个,则QPS=10,在请求获取令牌的时候存在:
    1.请求速度>令牌生成速度,令牌会被取完,后续请求被限流
    2.请求速度=令牌生成速度,流量处于平衡状态
    3.请求速度<令牌生成速度,系统并发数不高,请求被正常处理
  • 特性
    令牌桶的容量是固定的,请求速度小于令牌生成速度时,令牌桶被填满,所以能够处理突发流量

漏桶限流算法

漏桶限流算法和令牌同限流算法实现原理差不大多,区别在于漏桶无法处理短时间内的突发流量,是一种恒定速度的限流算法

  • 作用
    控制数据注入网络的速度,平滑网络上的突发流量
  • 原理
    在漏桶算法内部同样维护一个容器,以恒定速度出水,与消息中间件的思想类似

服务熔断与降级

熔断

微服务拆分粒度较细,请求链路长的高并发场景中,某个服务因为网络延迟或者请求超时等原因不可用会导致当前请求阻塞,导致请求堆积导致出现雪崩效应

  • 作用
    为防止整个系统出现雪崩效应,暂时将出现故障的接口隔离出来,断绝与外部接口的联系,熔断触发后,一段时间内该服务调用者的请求会直接失败,直到目标服务恢复正常

降级

  • 服务降级参考指标
    1.平均相应时间
    对应时刻的平均响应时间超过该阈值,接下来在一个固定时间窗口内,对这个方法的访问都会自动熔断
    2.异常比例
    某个方法每秒调用所获得的异常总数的比例超过设定的阈值时
    2.异常数量
    方法在指定窗口内获得的异常数量超过阈值时

分布式限流框架Sentinel

官网地址: https://sentinelguard.io/zh-cn/
官方文档: https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
image.png
Sentinel是面向分布式服务架构的轻量级流量控制组件,以流量为切入点,从限流、流量整形、服务降级、系统负载保护等多个维护保障微服务的稳定性

Sentinel的特性

  • 丰富的应用场景
  • 实时监控
  • 开原生态支持:开箱即用
  • SPI扩展点支持:定制化限流规则
    50505538-2c484880-0aaf-11e9-9ffc-cbaaef20be2b

Sentinel的组成

  • 核心库(Java 客户端)
    不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo、Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)
    基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel Dashboard的部署

Sentinel Dashboard是Sentinel轻量级开源控制台,支持机器发现、健康情况管理、监控(单机和集群)、规则管理和推送的功能

java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

-Dserver.port:指定Sentinel控制台访问端口,默认8080
-Dcsp.sentinel.dashboard.server:指定Sentinel控制台的IP地址和端口,目的是为了把自己的限流数据暴露到监控平台
-Dproject.name:设置项目名称
image.png

  • 登录
    localhost:7777
    账号密码默认sentinel、sentinel
    image.png

Sentinel的基本应用

Sentinel实现限流的基本步骤

  • 定义资源
    资源即限流保护的最基本元素,例如一个远程方法
  • 定义限流规则
  • 检验规则是否生效

Sentinel实现限流

  • 引入依赖

  • Demo类


/**
 * @Author jtao
 * @Date 2021/3/4 20:52
 * @Description
 */

public class DemoTest {
    public static void main(String[] args) {
        initFlowRules();
        while (true) {
            doSomething();
        }
    }

    private static void doSomething() {

            // 1.5.0 版本开始可以直接利用 try-with-resources 特性
            try (Entry entry = SphU.entry("doSomething")) {
                // 被保护的逻辑
                System.out.println("hello world"+System.currentTimeMillis());
            } catch (BlockException ex) {
                // 处理被流控的逻辑
                System.out.println("blocked!");

        }
    }

    private static void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("doSomething");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(20);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

代码说明:
1.SphU.entry("doSomething")定义一个资源来实现流控的逻辑,请求进入doSomething方法时,需要进行限流判断,如果跑出BlockException则触发限流
2.setResource设置保护的资源
3.setGrade限制阈值类型,QPS或并发线程数模式
4.setCount限流阈值

  • 启动main方法运行结果
    image.png
  • 查看sentinel日志输出
    image.png
    官方的日志输出说明
    image.png
    可以看出每秒接受请求20次和预定一样,被拒绝的请求高达几十万次QPS!!!

资源定义的方式

通过抛出异常的方式来定义一个资源,当资源被限流后,抛出BlockException异常。需要捕获异常进行限流后的逻辑处理

  • 方式一
 try (Entry entry = SphU.entry("resourceName")) {
            // 被保护的逻辑
            System.out.println("hello world" + System.currentTimeMillis());
        } catch (BlockException e) {
            // 处理被流控的逻辑

        }

resourceName可以定义方法名称、接口名称或者其他的唯一标识

  • 方式二
    可以通过返回boolean值方式定义资源
if (SphO.entry("resource")) {
            try {
                //被保护的逻辑
            } finally {
                SphO.exit();
            }
        } else {
            //资源访问被限制
        }

该方式资源使用完后要调用SphO.exit();,否则会导致调用链记录异常,抛出ErrorEntryFreeException异常

  • 方式三:使用@SentinelResource注解
    image.png

Sentinel资源保护规则

支持流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则、热点参数规则

  • 限流规则使用
    1.FlowRule定义限流规则
    2.FlowRuleManager加载FlowRule定义的限流规则

常用限流参数代码如下:

private void initFlowQPSRule(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setCount(20);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //是否需要针对调用来源进行限流,默认为default,不区分调用来源
        rule.setLimitApp("default");
        //调用关系限流策略:直接、链路、关联
        rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
        //流控行为:直接拒绝、排队等待、慢启动、默认直接拒绝
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        //是否集群限流,默认否
        rule.setClusterMode(false);
        //more
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

基于并发树和QPS的流量控制

image.png
Sentinel流量控制统计有2种类型,通过grade属性设置

  • RuleConstant.FLOW_GRADE_THREAD:并发线程数
  • RuleConstant.FLOW_GRADE_QPS:QPS

并发线程数

保护业务线程不被耗尽,统计当前请求的上下文线程数量,如果超出阈值,新的请求会被拒绝

QPS

QPS表示每秒查询数,一台服务器每秒能够响应的查询次数。当QPS达到限流的阈值时,会出发限流策略

QPS流量控制行为

QPS超过阈值时,触发流量控制行为,通过controlBehavior设置
image.png
image.png

  • 直接拒绝:RuleConstant.CONTROL_BEHAVIOR_DEFAULT
  • 预热(冷启动)Warm UP:RuleConstant.CONTROL_BEHAVIOR_WARM_UP
  • 匀速排队:RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
  • 冷启动+匀速排队:RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER

直接拒绝

默认流量控制方式
请求流量超出阈值时,直接抛出FlowException

Warm UP

当流量突然增大时,系统从空闲状态切换到繁忙状态,可能会将系统压垮。
希望请求处理的数量逐步递增,并在一个预期时间之后达到允许处理请求的最大值时,Warm UP就是以这为目的
系统并不是直接将QPS拉到最大值,而是在一定时间内逐步增加阈值,中间这段时间就是一个系统逐步预热的过程

匀速排队

严格控制请求通过的间隔时间,相当于漏桶限流算法
可以处理间隔性突发流量

调用关系流量策略

调用链:一个被调用的方法可能优惠调用其他方法,根据不同的调用维度来触发流量控制

  • 调用方限流
  • 调用链路入口限流
  • 具有关系的资源流量控制(关联流量控制)

调用方限流

image.png
image.png
根据请求来源进行流量控制,设置limitApp属性设置来源信息

  • default:LIMIT_APP_DEFAULT,不区分调用者,任何访问调用者的请求都会进行限流统计
  • :设置特定的调用者,只有来自这个调用者的请求才会进行流量统计和控制
  • other:LIMIT_APP_OTHER,除了之外的其他调用者进行流量控制
    统一资源可以配置多条规则,如果limitApp设置不一样,则按照生效顺序为
    、other、default

调用链路入口限流

一个被限流保护的方法,可能来自不同的调用链路,Sentinel允许只根据某个入口来进行流量统计。
类似于调用方限流

关联流量控制

当两个资源之间存在依赖关系或者资源争抢时,即该两个资源存在关联。
关联流量控制就是限制其中一个资源的执行流量不至操作于频繁而影响另外一个资源的自行效率
62410811-cd871680-b61d-11e9-9df7-3ee41c618644

Sentinel实现服务熔断

image.png
熔断操作配置和限流配置类似,只是配置的规则和拦截的异常不同

熔断规则配置属性

image.png

熔断策略

image.png

Spring Cloud集成Sentinel实践

Spring Cloud Alibaba 默认为 Sentinel 整合了 Servlet、RestTemplate、FeignClient 和 Spring WebFlux。Sentinel 在 Spring Cloud 生态中,不仅补全了 Hystrix 在 Servlet 和 RestTemplate 这一块的空白,而且还完全兼容了 Hystrix 在 FeignClient 中限流降级的用法,并且支持运行时灵活地配置和调整限流降级规则。

开源框架适配

  • 云原生微服务体系
    Spring Boot/Spring Cloud
    Quarkus
  • Web 适配
    Web Servlet
    Spring Web
    Spring WebFlux
    JAX-RS (Java EE)
  • RPC 适配
    Apache Dubbo
    gRPC
    Feign
    SOFARPC
  • HTTP client 适配
    Apache HttpClient
    OkHttp
  • Reactive 适配
    Reactor
  • API Gateway 适配
    Spring Cloud Gateway
    Netflix Zuul 1.x
    Netflix Zuul 2.x
  • Apache RocketMQ

Sentinel接入Spring Cloud

  • 项目依赖
 <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>
  • 资源使用类
/**
 * @Author jtao
 * @Date 2021/3/5 14:55
 * @Description
 */
@RestController
public class HelloController {
    @SentinelResource(value = "hello")
    @GetMapping("/say")
    public String hello() {
        return "hello,world!"+System.currentTimeMillis();
    }
  • 配置策略类
/**
 * @Author jtao
 * @Date 2021/3/5 15:01
 * @Description
 */

public class FlowRuleInitFunc implements InitFunc {
    @Override
    public void init() throws Exception {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("hello");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

需要在META-INF\services\com.alibaba.csp.sentinel.init.InitFunc目录下放入扩展点的全路径名
image.png
以下是源码配置文件地址说明
image.png
image.png

  • Spring Boot启动类
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
  • 启动项目 配置的QPS为1秒,访问地址得到结果和预期一样
    image.png
  • @SentinelResource注解降级策略的其他参数官网也有说明
    image.png

基于Sentinel Dashboard来实现流控配置

//TODO


这个家伙很懒,啥也没有留下😋