SpringCloudAliBaba篇 之 Sentinel:图解分布式系统的流量防卫兵

1、Sentinel是什么

  • Sentinel是阿里巴巴开源的,面向分布式服务架构的高可用防护组件。

  • 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

源码地址:https://github.com/alibaba/Sentinel

官方文档:https://github.com/alibaba/Sentinel/wiki

Sentinel 具有以下特征:

  • 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
  • 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。

阿里云提供了企业级的Sentinel服务,应用高可用服务AHAS

Sentinel和Hystrix对比

SentinelHystrix
隔离策略信号量隔离线程池隔离/信号量隔离
熔断降级策略基于响应时间或失败比率基于失败比率
实时指标实现滑动窗口滑动窗口(基于RxJava)
规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持支持支持
限流基于QPS,支持基于调用关系的限流有限的支持
流量整形支持慢启动,匀速器模式不支持
系统负载保护支持不支持
控制台开箱即用、可配置规则、查看秒级监控、机器发现等不完善
常见框架的适配servlet、spring cloud、dubbo、grpc等servlet、spring cloud netflix

2、sentinel-流控规则初体验(代码层面设置规则)

Sentinel可以简单的分为Sentinel核心库Dashboard。核心库不依赖Dashboard,但是结合Dashboard可以取得最好的效果。

流程规则的定义

重要属性:

Field说明默认值
resource资源名,资源名是限流规则的作用对象
count限流阈值
grade限流阈值类型,QPS 模式(1)或并发线程数模式(0)QPS 模式
limitApp流控针对的调用来源default,代表不区分调用来源
strategy调用关系限流策略:直接、链路、关联根据资源本身(直接)
controlBehavior流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流直接拒绝
clusterMode是否集群限流

通过代码自定义流量控制

1、创建一个springboot项目,并引入sentinel核心库

<!--sentinel核心库-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.4</version>
</dependency>

<!--如果要使用@SentinelResource-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.4</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

编写一个controller

@RestController
@Slf4j
public class HelloController {
    private static final String RESOURCE_NAME = "hello";
    @RequestMapping("/hello")
    public String hello(){
        Entry entry = null;
        try {
            // sentinel针对资源进行限制
            entry = SphU.entry(RESOURCE_NAME);
            // 被保护的业务逻辑
            String str = "hello world";
            log.info("======="+str+"=======");
            return str;
        }catch (BlockException e){
            // 资源访问组织,被限流或被降级,进行响应的处理操作
            log.info("block");
            return "被监控了";
        }catch (Exception ex){
            // 若需要配置降级规则,则需要通过这种方式记录业务异常
            Tracer.traceEntry(ex,entry);
        }finally {
            if(entry != null){
                entry.exit();
            }
        }
        return null;
    }
    /**
     * spring的初始化方法
     *
     **/
    @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);
        // 设置受保护的资源阈值(1秒当中访问一次)
        rule.setCount(1);
        // 加入集合
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

结果描述

浏览器进行访问/hello接口,每隔一秒点击一次,正常返回hello world,如果快速点击,返回被监控了

像这样的规则还有熔断降级规则系统保护规则访问控制规则热点规则查询更改规则等等,代码使用方法与上述雷同。

@SentimeResource:注解引入

作用:改善接口中资源定义和被流控降级后的处理方法

1、添加依赖(上面已经引入,我再展示一遍这个依赖)

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.4</version>
</dependency>

2、配置bean

/**
 * 注解支持的配置Bean
 */
@Bean
public SentinelResourceAspect sentinelResourceAspect(){
    return new SentinelResourceAspect();
}

3、在controller层进行代码实现

@RestController
@Slf4j
public class HelloController {
    private static final String RESOURCE_NAME = "hello";
    /**
     * value: 定义的资源
     * blockHandler: 设置流控降级后的处理方法(默认该方法必须声明在同一个类)
     *   如果不想在同一个类中blockHandLerClass但是方法必须是static静态的
     * fallback : 当接口出现了异常,可以调用的方法
     *    如果不想在同一个类中fallbackClass但是方法必须是static静态的
     * 如果blockHandler和fallback同时指定,则blockHandler优先级更高
     * exceptionsToIgnore 排除哪些异常不处理
     **/
    @RequestMapping("/hello")
    @SentinelResource(value = RESOURCE_NAME,blockHandler = "blockHandlerForHello",fallback = "fallbackHandlerHello",exceptionsToIgnore = {})
    public String hello(String id){
        // int i = 1/0;
        return "hello world";
    }
    /**
     * 注意:
     * 1、一定要public
     * 2、返回值一定要和原方法一直,参数也要包含原函数的参数
     */
    public String blockHandlerForHello(String id , BlockException ex){
        ex.printStackTrace();
        return "流控";
    }
    /**
     * 异常会走这个方法
     */
    public String fallbackHandlerHello(String id , Throwable e){
        e.printStackTrace();
        return "异常处理";
    }

    /**
     * spring的初始化方法
     *
     **/
    @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);
        // 设置受保护的资源阈值(1秒当中访问一次)
        rule.setCount(1);
        // 加入集合
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

3、sentinel控制台部署

根据需要的版本进行下载下载jar包

windows版本启动

下载完成后,在指定目录下面进行java -jar 你的jar包名称

浏览器直接访问即可,localhost:8080,默认账户名和密码都是sentinel

修改端口号、以及账户名和密码

#需以这种方式启动即可
java -Dserver.port=8810 -Dsentinel.dashboard.auth.username=lili -Dsentinel.dashboard.auth.password=211314 -jar D:\a-mysource\sentinel\sentinel-dashboard-1.8.1.jar

linux版本启动

不做过多描述,命令与windows一样,

注意:如果你想要外界可以访问的话,jar包需要后台启动,可以参考这篇文章当xshell关闭时如何保持一个jar包程序在后台运行

并且打开相应端口防火墙,以及服务器的安全组放行。

效果展示:

64

本地集成控制台

<!--整合控制台-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.4</version>
</dependency>

编辑我们的启动配置,在对应位置加入(主要是为了根据ip和端口号找到我们的控制台)

-Dcsp.sentinel.dashboard.server=127.0.0.1:8010

65

启动即可!

注意:刚打开我们的控制台是什么都没有的,随便对一个接口进行访问,再次刷新控制台即可。

66

4、sentinel整合springcloud alibaba

1、添加依赖

<!--sentinel依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2、yml配置

server:
  port: 8005
# 服务名称
spring:
  application:
    name: order-sentinel
  cloud:
    sentinel:
      transport:
        #配置sentinel的地址以及端口号
        dashboard: 127.0.0.1:8010

3、编写一个controller

@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping("/orderList")
    public String getOrder() {
        return "orderList";
    }    
    @GetMapping("/hello")
    public String getHello() {
        return "hello";
    }
}

启动,先对接口进行一个访问,再进入控制台即可

67

5、Sentinel控制台各个部分讲解

5.1、实时监控

我们可以对两个请求快速进行访问几次,然后点击实时监控,可以看到请求的一些信息

68

5.2、簇点链路

  • 用来显示微服务所监控的API

69

我们经过请求,发现两次请求都被添加进来了。并且可为每个请求进行流控降级热点授权

5.3、流控规则

流量控制,其原理是监控应用流量的QPS或并发线程等指标,当达到指定的阈值对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性

应用场景

  • 应对洪峰流量:秒杀,大促,下单,订单回流处理
  • 消息型场景:削峰填谷,冷热启动
  • 付费系统:根据使用流量付费
  • API Gateway:精准控制API流量
  • 探测应用中运行的慢程序块,进行限制

我们在簇点链路对流控规则进行添加操作,先进行示例QPS方式如下

  • QPS 对应阈值: 表示每秒接受几个请求

70

解释:该请求每秒最多2个请求,否则就会被流控

测试:浏览器快速刷新,结果如下

71

结论:成功被流控,这是默认显示,如果想修改默认显示,操作如下

@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping("/hello")
    @SentinelResource(value = "hello",blockHandler = "helloHandler")
    public String getHello() {
        return "hello";
    }
    public String helloHandler(BlockException e) {
        return "被流控了";
    }
}

需要对我们刚才自己添加的资源进行流控

72

再次快速刷新,这个时候就是我们自定义的流控描述了。

再次测试线程数流控

线程数流控是为了保护业务线程池不被慢调用耗尽

  • 线程数对应阈值: 表示每次处理几个线程,其他的线程请求排除在外

实现:添加一个方法

@RequestMapping("/threadHello")
@SentinelResource(value = "threadHello",blockHandler = "helloHandler")
public String threadHello() throws InterruptedException {
    TimeUnit.SECONDS.sleep(5);
    return "hello";
}

73

打开两个浏览器进行访问(模拟两个线程)

74

5.3.1、关联流控模式

当两个资源之间具有争抢或者依赖关系的时候,这两个资源便有了关联,比如对数据库同一个字段的读操作和写操作存在争抢,这个时候,我们就可以把读写操作进行关联,当写的操作过于频繁时,读数据的请求就会被限流。

步骤

1、新建两个接口

@GetMapping("/add")
public String add() {
    return "生成订单";
}
@GetMapping("/get")
public String get() {
    return "查询订单";
}

2、生成订单对查询订单限流,当生成订单的次数大于三,则查询订单被限流。

75

Jmeter工具对/order/add进行每秒四次请求测试,然后浏览器访问/order/get

76

5.3.2、链路流控模式

下图记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一颗调用树

77

解释:有两个入口/order/getUser1,/order/getUser2请求都调用了资源User,Sentinel可以选择对其中一个进行资源限制

步骤

1、定义一个接口和对应的实现类

public interface UserService {
    String getUser();
}
@Service
public class UserServiceImpl implements UserService{
    @Override
    @SentinelResource(value = "getUser",blockHandler = "userHandler")
    public String getUser() {
        return "User";
    }
    public String userHandler(BlockException e){
        return "被链路流控了";
    }
}

2、编写controller层

@Autowired
UserService userService;
@GetMapping("/getUser1")
public String getUser1() {
    return userService.getUser();
}
@GetMapping("/getUser2")
public String getUser2() {
    return userService.getUser();
}

3、对getUser2添加链路流控

78

注意:测试发现功能不生效(高版本不生效)

yml里面加上

spring:
  cloud:
    sentinel:
      web-context-unify: false # 默认将调用链路收敛

再次测试发现

只对order/getUser2进行了流控,而对order/getUser1没有进行流控

5.3.3、预热流控效果

针对激增流量处理

warm up方式,即预热\冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,通过冷启动让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热时间,避免冷系统被压垮。

冷加载因子:codeFactor默认是3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值

79

解释:5s内时间进行预热,直到达到阈值(比如第一秒只能通过三个,第二秒可以通过5个,直到达到阈值)

演示:浏览器快速刷新请求,刚开始会被流控,随着预热,你的刷新速度已经不会被流控了

5.3.4、排队等待流控效果

针对脉冲流量

匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以匀速的速度通过,对应的是漏桶算法

这种方式主要用于处理间隔性突然的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

注意:匀速排队模式暂时不支持QPS>1000的场景

80

解释:每秒接收5个请求,其他请求进行等待,等待时间为5秒

Jmeter工具对/order/orderList进行每秒10次请求测试,间隔时间为5s,循环5次。

然后观察实施监控

81

发现:所有的请求都进行了处理,并且还有空闲时间

5.4、熔断规则

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩,熔断降级作为保护自身的手段,通常在客户端(调用端进行配置)。

保护自身的的手段

  • 并发控制(信号量隔离)
  • 基于慢调用的比例熔断
  • 基于异常的比例熔断

触发熔断后的处理逻辑实例

  • 提供fallback实现(服务降级)
  • 返回错误result
  • 读缓存(DB访问降级)
5.4.1、慢调用比例

慢调用比例:选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值。则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。

1、添加方法

@RequestMapping("/test")
@SentinelResource(value = "test",fallback = "testFallback")
public String test() throws InterruptedException {
    Thread.sleep(101);
    return "正常访问";
}
public String testFallback(Throwable e){
    return "被熔断了";
}

2、添加规则

82

解释:10秒内有大于5个请求的访问,慢调用比例大于0.1就进行熔断。

测试:浏览器进行请求5次以上发现被熔断。

5.4.2、异常比例

1、添加接口

@RequestMapping("/errorTest")
public String errorTest()  {
    int i = 1/0;
    return "正常访问";
}

2、添加规则

83

解释:10秒内有大于5个请求的访问,异常比例大于0.1就进行熔断。

测试:浏览器进行请求5次以上发现被熔断。

5.4.3、异常数

1、添加规则

84

解释:10秒内有大于5个请求的访问,只有有一个异常就进行熔断。

测试:浏览器进行请求5次以上发现被熔断。

5.4.4、整合openFeign降级

在我们服务消费方去调用服务提供方的时候,由于提供方出现的错误,而导致我们的消费方也报错。这个时候就需要我们的sentinel进行整合降级处理。

问题演示

1、服务提供方【加一个1/0的异常】

@RestController
@RequestMapping("/stock")
public class StockController {
    @Value("${server.port}")
    String value;
    @RequestMapping("/stockList")
    public String getStock(){
        int i = 1/0;
        System.out.println("库存");
        return "扣减库存"+value;
    }
}

2、服务消费方

@FeignClient(value = "stock-server",path = "/stock")
public interface StockFeignService {
    @RequestMapping("/stockList")
    public String getStock();
}
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    StockFeignService stockFeignService;
    @GetMapping("/orderList")
    public String getOrder() {
        return stockFeignService.getStock();
    }
}

3、进行调用

85

1、这样就需要我们引入sentinel依赖进行解决

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2、application.yml

#对Feign的支持
feign:
  sentinel:
    enabled: true #添加feign对sentinel的支持

3、编写一个降级的类,去实现你的feign的service接口

@Component
public class StockFeignServiceFallback implements StockFeignService{
    @Override
    public String getStock() {
        return "降级了";
    }
}

4、接口添加fallback,对应你刚才编写的类

86

5、重启访问

87

5.5、热点参数流控

何为热点?热点即经常访问的数据,很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其进行访问限制。

接口编辑

    @RequestMapping("/get/{id}")
    @SentinelResource(value = "getById",blockHandler = "hotBlockHandler")
    public String getById(@PathVariable("id") Integer id){
        return "正常访问";
    }
    public String hotBlockHandler(Integer id ,BlockException e) {
        return "热点异常处理";
    }

添加规则

单机阈值

  • 情况一:假设参数大部分都是热点参数,那单机阈值就要对热点参数进行流控,后续额外对普通参数进行流控
  • 情况二:假设参数大部分是普通参数,那单机阈值就要对普通参数进行流控,后续额外对热点参数进行流控

假设我们的参数是普通参数

88

解释:设置一秒钟可以访问10次

保存完成后可以进行编辑,设置我们的热点参数

89

解释:参数为1的,一秒只能访问两次。

结果:这个时候我们对id为1的进行访问,快速刷新访问就会被热点流控

90

5.6、系统规则

  • 容量评估不到位,某个大流量接口限流配置不合理或没有配置,导致系统崩溃,来不及进行处理
  • Load和cpu彪高,但是不知道什么原因造成的,来不及处理
  • 一个机器挂了导致流量负载到另外的机器上,引起系统雪崩
  • 希望有的兜底防护,即使缺乏容量评估也希望有一定的保护机制

一些指标

  • Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。
  • CPU usage(1.5.0+版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏
  • 平均RT: 当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒
  • 并发线程数:当单台机器上所有入口流量的并发线程达到阈值触发系统保护
  • 入口QPS:当单台机器上所有入口的QPS到达阈值即触发系统保护。

6、Sentinel规则持久化

我们实际开发环境中,肯定不希望我们重启后,这些规则就消失了,这个时候我们就需要持久化。

推模式

生产环境下一班更常用的是push模式的数据源,对于push模式的数据源,如远程配置中心(ZooKeeper,Nacos,Apoll等等),推送的操作不应由Sentinel客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则的正确做法应该是配置中心控制台/Sentinel控制台—>配置中心–>Sentinel数据源–>Sentinel,而不是经过Sentinel数据源推送至配置中心,这样的流程就非常清晰了。

6.1、基于Nacos配置中心控制台实现推送

引入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

nacos中新建一个配置

91

Field说明默认值
resource资源名,资源名是限流规则的作用对象
count限流阈值
grade限流阈值类型,QPS 模式(1)或并发线程数模式(0)QPS 模式
limitApp流控针对的调用来源default,代表不区分调用来源
strategy调用关系限流策略:直接、链路、关联根据资源本身(直接)
controlBehavior流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流直接拒绝
clusterMode是否集群限流

yml配置

server:
  port: 8005
# 服务名称
spring:
  application:
    name: order-sentinel
  cloud:
    sentinel:
      transport:
        #配置sentinel的地址以及端口号
        dashboard: 127.0.0.1:8010
      web-context-unify: false # 默认将调用链路收敛
      datasource:
        hello-rule: #可以自定义
          nacos:
            #配置的名字
            data-id: order-sentinel-hello-rule
            server-addr: 101.34.254.160:8847
            data-type: json
            rule-type: flow


重启项目:

92

流控规则成功进来了,我们进行快速刷新接口,测试流控效果

93

流控效果也正常显示。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jamie Chyi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值