(一)、sentinel介绍

1、sentinel是什么?

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
源码地址:https://github.com/alibaba/Sentinel
官方文档:https://github.com/alibaba/Sentinel/wiki

2、Sentinel具有以下特征:

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

Sentinel和Hystrix对比

traefik 微服务 微服务 sentinel_spring cloud alibaba

3、sentinel能解决什么问题

【大流量请求】:在秒杀和大促开始前,如果准备不充分,瞬间大量请求会造成服务提供者的不可用。
【硬件故障】:可能为硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问。
【缓存击穿】:一般发生在缓存应用重启, 缓存失效时高并发,所有缓存被清空时,以及短时间内大量缓存失效时。大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用。

在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时, 会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。

服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应

4、重要概念

资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

工作主流程
在 Sentinel 里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个 Entry 对象。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 SphU API 显式创建。Entry 创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如:
NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;

详细的介绍请参考sentinel官方文档:sentinel中文文档

(二)、sentinel的使用

一、定义资源和规则

1、引入依赖

<!-- sentinel核心包-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>${sentinel.versioin}</version>
        </dependency>

2、定义规则
规则的种类:Sentinel 支持以下几种规则:流量控制规则熔断降级规则系统保护规则来源访问控制规则热点参数规则
以流控规则为例,在资源保护前先定义好流控规则,所以使用到 @PostConstruct注解,在该bean对象实例化之前将定义的流控规则加载完成。

/**
     * 设置流控规则
     */
    @PostConstruct
    public static void initFlowRules() {
        List<FlowRule> flowRules = new ArrayList<>();
        FlowRule flowRule = new FlowRule();
        // 设置受保护的资源
        flowRule.setResource(RESOURCE_NAME);
        // 设置流控规则 QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        // Set limit QPS to 20.
        flowRule.setCount(1);
        flowRules.add(flowRule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(flowRules);
    }

3、编写资源保护的代码

@RestController
@Slf4j
public class HelloController {
    private static final String RESOURCE_NAME = "hello";

    @RequestMapping(value = "/hello")
    public String hello() {
        Entry entry = null;
        try {
            entry = SphU.entry(RESOURCE_NAME);
            // 被保护的业务逻辑
            String str = "hello world";
            log.info("====="+str);
            return str;
        } catch (BlockException ex) {
            // 资源访问阻止,被限流或被降级
            //进行相应的处理操作
            log.info("block!");
        } catch (Exception ex) {
            // 若需要配置降级规则,需要通过这种方式记录业务异常
            Tracer.traceEntry(ex, entry);
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
        return null;
    }

    /**
     * 设置流控规则
     */
    @PostConstruct
    public static void initFlowRules() {
        List<FlowRule> flowRules = new ArrayList<>();
        FlowRule flowRule = new FlowRule();
        // 设置受保护的资源
        flowRule.setResource(RESOURCE_NAME);
        // 设置流控规则 QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        // Set limit QPS to 20.
        flowRule.setCount(1);
        flowRules.add(flowRule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(flowRules);
    }
}

资源保护的核心代码块:

// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
  // 被保护的业务逻辑
  // do something here...
} catch (BlockException ex) {
  // 资源访问阻止,被限流或被降级
  // 在此处进行相应的处理操作
}

测试效果:

traefik 微服务 微服务 sentinel_限流_02


其他规则定义和资源保护的详细介绍可以参考官方文档如何使用

以上方式定义资源的缺点很明显

  • 业务侵入性很强,定义资源保护需要在controller中写入非业务代码.
  • 规则配置不灵活,若需要添加新的保护规则,还需要手动添加

二、@SentinelResource注解+Sentinel dashboard控制台

Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理。
同时支持通过Sentinel控制台定义规则,可以简化开发。

@SentinelResource 注解用来标识资源是否被限流、降级。

1、使用该注解需要引入依赖:

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

如果是spring项目需要配置切面支持(springboot项目会自动配置,不用关注)

@Configuration
public class SentinelAspectConfiguration {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

2、使用@SentinelResource定义资源

// 原本的业务方法.
@SentinelResource(value = "findOrderByUserId",
				blockHandler = "handleException", // 指定触发限流等规则时执行的方法名
				fallback = "fallback" // 指定fallback降级执行的方法名
				//blockHandlerClass = ExceptionUtil.class, // 指定blockHandler函数的所在类,如果在当前类可以忽略
				//fallbackClass = ExceptionUtil.class,  指定fallback函数所在类,如果在当前类可以忽略
				  )
@RequestMapping(value = "/user/findOrderByUserId/{id}")
public User findOrderByUserId(@PathVariable("id") Integer id) {
    throw new RuntimeException("getUserById command failed");
}

// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User handleException(@PathVariable("id") Integer id, BlockException ex) {
    return new User("admin");
}

// fallback 函数,原方法调用被降级的时候调用
public User fallback(@PathVariable("id") Integer id, Throwable e) {
	return new User("admin1"); 
}

使用@SentinelResource需要注意:

配置了@SentinelResource注解的方法都视作资源,可以配置在@RequestMapping("/user/findOrderByUserId/{id}")修饰的接口层,也可以配置在service层,如果配置在了接口上,则sentinel会产生两套资源链路,一个是@RequestMapping中的value属性的"/user/findOrderByUserId/{id}"链路,一个是@SentinelResource(“findOrderByUserId”)配置的"findOrderByUserId"链路

(1)如果这两个链路都配置了规则的话,默认会先执行接口产生的链路;

(2)如果没有为接口配置规则,就会执行@SentinelResource的资源链路。

traefik 微服务 微服务 sentinel_微服务_03

3、编写ExceptionUtil,注意如果指定了fallback函数或者blockHandler的class类,该类中的方法必须是static方法

public class ExceptionUtil {

	public static R handleException(Integer id, BlockException e){
        log.error("===被限流啦===");
        return new User("admin");
    }
    
    public static  fallback(Integer id,Throwable e){
        log.error("===被异常降级啦===");
        return new User("admin1");
    }
}

4、客户端接入Sentinel dashboard控制台

  • 客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信。
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.0</version>
</dependency>
  • 配置启动参数

启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口(默认是 8719)。
除了修改 JVM 参数,也可以通过配置文件取得同样的效果。更详细的信息可以参考 启动配置项

  • 启动 Sentinel 控制台
    您可以从 release 页面 下载最新版本的控制台 jar 包。
    下载控制台 jar 包并在本地启动:可以参见 此处文档
#启动控制台命令
java -jar sentinel-dashboard-1.8.0.jar

用户可以通过如下参数进行配置:
-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 分钟;
访问http://localhost:8080/#/login ,默认用户名密码: sentinel/sentinel

  • 触发客户端初始化
    Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;

三、Spring Cloud Alibaba整合Sentinel

1.引入依赖

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

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

2.添加yml配置,为微服务设置sentinel控制台地址
添加Sentinel后,需要暴露/actuator/sentinel端点,而Springboot默认是没有暴露该端点的,所以需要设置,测试http://localhost:8800/actuator/sentinel

server:
  port: 8800

spring:
  application:
    name: sentinel-demo
  cloud:
    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: 127.0.0.1:8080
        # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
        # port: 8719
    
#暴露actuator端点   
management:
  endpoints:
    web:
      exposure:
        include: '*'

3.在sentinel控制台中设置流控规则

资源名: 接口的API

针对来源: 默认是default,当多个微服务都调用这个资源时,可以配置微服务名来对指定的微服务设置阈值

阈值类型: 分为QPS和线程数 假设阈值为10

QPS类型: 只得是每秒访问接口的次数>10就进行限流

线程数: 为接受请求该资源分配的线程数>10就进行限流

traefik 微服务 微服务 sentinel_User_04


测试: 因为QPS是1,所以1秒内多次访问会出现如下情形:

traefik 微服务 微服务 sentinel_限流_05


微服务和Sentinel Dashboard通信原理

Sentinel控制台与微服务端之间,实现了一套服务发现机制,集成了Sentinel的微服务都会将元数据传递给Sentinel控制台,架构图如下所示:

traefik 微服务 微服务 sentinel_限流_06