服务结合SkyWalking链路追踪参考:skywalking监控服务调用链路
sentinel github wiki:如何使用

随着微服务的流行,服务调用的稳定性变得越来越重要。Sentinel 以“流量”为切入点,在流量控制、熔断降级和负载保护等多个领域发挥作用,以保障服务的可靠性。

Sentinel 具有以下特点:

  • 丰富的场景支持:Sentinel 已经支持阿里巴巴双 11 购物节的关键场景超过 10 年,例如秒杀(即控制突发流量,使其在系统容量的可接受范围内)、消息负载转移以及不可靠下游应用的熔断。
  • 全面的实时监控:Sentinel 提供实时监控能力。您可以以秒级精度查看服务器的监控数据,甚至可以查看拥有不到 500 个节点的集群的整体运行状态。
  • 广泛的开源生态系统:Sentinel 提供开箱即用的模块,可以轻松与 Spring Cloud、Dubbo 和 gRPC 等其他开源框架/库集成。要使用 Sentinel,您只需引入相关依赖并进行一些简单的配置。
  • 健全的 SPI 扩展:Sentinel 提供易于使用且健全的 SPI 扩展接口。您可以通过 SPI 扩展快速定制逻辑,例如定义自己的规则管理,或者适配特定的数据源。

如何使用Sentinel

如果您想在您的项目中使用 Sentinel,请使用具有 com.alibaba.cloud group ID和 spring-cloud-starter-alibaba-sentinel artifact ID的启动器。

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

下面是一个简单的代码示例:

@SpringBootApplication
public class Application {

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

}

@RestController
public class TestController {

    @GetMapping(value = "/hello")
    @SentinelResource("hello")
    public String hello() {
        return "Hello Sentinel";
    }

}

@SentinelResource 注解用于标识一个资源是否被限流或降级。在上述示例中,注解的 hello 属性指的是资源名称。

@SentinelResource 还提供了诸如 blockHandlerblockHandlerClassfallback 等属性,用于标识限流或降级操作。更多详情,请参考 Sentinel 注解支持

上述示例均用于 WebServlet 环境。Sentinel 当前支持 WebFlux,需要与 spring-boot-starter-webflux 依赖项配合使用,以触发 Sentinel 启动器中的 WebFlux 相关自动化配置。

Sentinel 仪表板

Sentinel 仪表板是一个轻量级控制台,提供诸如机器发现、单机资源监控、集群资源数据概览以及规则管理等功能。这些配置的数据存储在服务的内存中,当服务重启之后规则会消失。要使用这些功能,您只需要完成几个步骤。

注意:集群的统计概览仅支持少于 500 个节点的集群,并且存在大约 1 到 2 秒的延迟。

可以通过github下载dashboard的jar包,启动命令:

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

从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。可以参考 鉴权模块文档 配置用户名和密码。

Sentinel实战

1. 添加依赖,并配置yaml

添加maven依赖:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

在application.yml文件中添加dashboard的配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8888
        port: 8719

spring.cloud.sentinel.transport.port 中指定的端口号将在应用程序的相应服务器上启动一个 HTTP 服务器,该服务器将与 Sentinel 仪表板进行交互。例如,如果在 Sentinel 仪表板中添加了一个限流规则,那么规则数据将被推送到 HTTP 服务器并由其接收,随后该服务器会将规则注册到 Sentinel 中。

2. 启动dashboard

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

3. 启动应用
controller代码编写:

@SpringBootApplication
@RestController
@RequestMapping("/stl")
public class SentinelApp {
    public static void main(String[] args) {
        SpringApplication.run(SentinelApp.class, args);
    }
    @GetMapping("/ping")
    public String ping() {
        return "pong";
    }
}

应用运行在8080端口,并暴露了一个get /ping端点。

4. 查看dashboard

通过postman发送请求,并观察dashboard的情况。只有在发送请求之后,才会出现对应的服务菜单。

关于簇点链路

我们查看上面的簇点链路页签,可以看到系统自动为请求创建了一个请求路径的资源

这是因为所有访问的 Web URL 被自动统计为了 Sentinel 的资源,可以针对单个 URL 维度进行流控。若希望区分不同 HTTP Method,可以将 HTTP_METHOD_SPECIFY 这个 init parameter 设为 true,给每个 URL 资源加上前缀,比如 GET:/foo

异常处理

我们为其添加一个流控规则,将阈值设置为1:

然后请求查看被流控的效果:

这是sentinel默认的被流控后返回的错误信息。在开发中我们如果想自定义请求被流控后的异常信息,根据我们定义sentinel资源的不同,有以下四种方式:学习地址

针对于web接口的方式我们可以自定义BlockExceptionHandler实现并注入spring容器:

/**
 * sentinel webmvc block handler
 * <p/>
 * see: {@link com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration#sentinelWebMvcConfig()}
 */
@Component
public class ErrorUnifyHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
        PrintWriter out = response.getWriter();
        out.print("被sentinel限流了");
        out.flush();
        out.close();
    }
}

这样程序启动之后,再次被限流可以看到错误信息就是我们自定义的异常了。

动态规则源

Sentinel 目前支持以下数据源扩展:

拉模式拓展
实现拉模式的数据源最简单的方式是继承 AutoRefreshDataSource 抽象类,然后实现 readSource() 方法,在该方法里从指定数据源读取字符串格式的配置数据。比如 基于文件的数据源

推模式拓展
实现推模式的数据源最简单的方式是继承 AbstractDataSource 抽象类,在其构造方法中添加监听器,并实现 readSource() 从指定数据源读取字符串格式的配置数据。比如 基于 Nacos 的数据源

Nacos动态规则源

下面实战下nacos动态规则源的配置,nacos采用的则是推模式,在自动配置数据源的时候会同时注册nacos的监听器,当我们通过nacos管理界面修改配置之后,服务会收到对应的监听事件,从而获取到最新的规则配置。

1. 我们首先在nacos中添加对应的配置:

2. 在application.yml文件中添加对应的配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8888
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            data-id: sentinel-config.json
            group-id: DEFAULT_GROUP
            namespace: 9bf28ee1-daf9-4932-a453-a18de35a8be8
            data-type: json
            rule-type: flow
            username: nacos
            password: nacos

3. 添加maven依赖:

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

4. 之后启动项目即可,不需要额外的编写代码逻辑。我们在yml文件中的配置对应的是SentinelProperties类。通过DataSourcePropertiesConfiguration类也可以看到sentinel支持的动态规则源的集合,

并且每个配置方式中会有对应的factorybean类与之对应,用于bean的实例化。

使用spring boot的环境下,项目会自动配置SentinelDataSourceHandler bean,在这个bean的实例构造完成的时候,通过生命周期方法org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated,解析配置的datasource并逐一进行datasource bean的注册。

限流规则

流控规则

流控规则的流控模式有三种:直接,关联,链路。这里先来看看关联流控的效果,主要是要清楚两个资源谁影响谁:

这样配置规则的情况下:当资源二的QPS达到2的时候,对资源一的请求会被限流;可以统一理解为资源名是谁就是要流控谁。

热点参数规则

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

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

首先需要添加热点参数的依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-parameter-flow-control</artifactId>
</dependency>

我们定义一个包含参数的请求GetMapping:

    @GetMapping("/hp")
    @SentinelResource("hot-parameter")
    public String hotParameter(String p1, String p2) {
        return "hot-parameter: " + p1 + ", " + p2;
    }

在dashboard控制台中添加热点规则。

规则的含义是:资源hot-parameter对应的方法参数中,对第二个参数进行流量控制,2s内最多2个请求;例外值是“tom”,如果参数值是“tom”,则2s内的限流为10。

注意:目前 Sentinel 自带的 adapter 仅 Dubbo 方法埋点带了热点参数,其它适配模块(如 Web)默认不支持热点规则(也就是对于RequestMapping的请求路径自动生成的资源名不支持),可通过自定义埋点方式指定新的资源名并传入希望的参数。注意自定义埋点的资源名不要和适配模块生成的资源名重复,否则会导致重复统计。

工作流程SlotChain

先来看看什么是SlotChain,放出一张官网上面的图:

SlotChain是伴随我们的资源一同创建的,当使用Sphu.entry的时候会创建资源对应的Entry对象,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责。当执行到FlowSlot节点的时候,会根据前面的slot节点统计的信息和当前资源配置的流控规则进行检查,如果被限流则抛出异常。

当所有的Slot判断完之后,才会开始执行我们的代码逻辑,也就是会执行Sphu.entry的下一行代码。

程序的入口则是在SphU类的方法上,从经常见的Sphu.entry(resourceName)这个方法入手。当调用entry方法的时候并且传递一个资源名,该资源就代表着后续的调用行为,我们对资源名进行流控就是要对该资源所代表的执行逻辑进行流控:资源名==一段调用逻辑

查看github wiki中的说明已经明确介绍了各个slot的作用,这里记录一下初始化SlotChain的流程。
我们从方法开始说起:

    public static Entry entry(String name) throws BlockException {
        return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);
    }

在方法体中调用了Env的sph静态字段的entry方法,里面有两部分:

  1. 实例化sph静态字段;
  2. 静态初始化块进行初始化;
public class Env {

    public static final Sph sph = new CtSph();

    static {
        // If init fails, the process will exit.
        InitExecutor.doInit();
    }

}

这里静态初始化块中的部分比较简单,提一嘴即可:作用就是使用spi机制加载InitFunc接口的实现类,将它们按照order进行排序后循环遍历调用init方法。
官方的一个使用场景说明是:在动态规则拓展的时候需要将sentinel的规则存放在其他地方进行动态配置,此时可以使用InitFunc的方式,创建一个接口的实现类,在init方法中自己手动注册动态数据源。

SlotChain的初始化

接下来重点看的则是sph静态属性的实例化,在调用Sphu的方法(包括entry方法)的时候都会代理给这个属性去调用。entry()方法有很多重载,这里跟踪的是entry(String name)这个方法的执行,顺着方法点进方法里面一直到entryWithPriority方法,在这个方法中处理主要的逻辑步骤如下:

1. 初始化sentinel的上下文对象Context;

2. 使用SlotChainBuilder创建ProcessorSlotChain,并与resourcename进行映射保存在map中;
使用SlotChainBuilderbuild方法创建的ProcessorSlotChain对象,我们来看一下build方法是如何进行构建的:

@Override
public ProcessorSlotChain build() {
    ProcessorSlotChain chain = new DefaultProcessorSlotChain();

// 通过SPI机制读取ProcessorSlot的实现
    List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();

    for (ProcessorSlot slot : sortedSlotList) {
        if (!(slot instanceof AbstractLinkedProcessorSlot)) {
            RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
            continue;
        }
// 将获取到的所有实现构造成链结构
        chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
    }

    return chain;
}

因为这里的SlotChainBuilder是通过spi机制获取到的,目前sentinel只有一个实现:DefaultSlotChainBuilder,构造出的ProcessorSlotChainDefaultProcessorSlotChainAbstractLinkedProcessorSlot类的是一个链式的结构,有一个first属性作为链的哨兵节点;next属性链接着下一个要执行的slot。

如果我们自定义ProcessorSlotChain的实现,这里sentinel的要求是必须继承自AbstractLinkedProcessorSlot类才能放入到slot chain中进行链式调用;

3. 将resource,context和chain统一封装为Entry对象,用于方法的返回值。

4. 调用SlotChain的entry方法,开始执行链逻辑;

下面列出了ProcessorSlot中的预置slot:

com.alibaba.csp.sentinel.slotchain.ProcessorSlot

# Sentinel default ProcessorSlots
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
com.alibaba.csp.sentinel.slots.logger.LogSlot
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
com.alibaba.csp.sentinel.slots.system.SystemSlot
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot

预置的8种Slot的职责

NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;

热点参数限流的实现方式就是拓展了ProcessorSlot接口,提供了ParamFlowSlot实现。

Leave a Reply

Your email address will not be published. Required fields are marked *

目录