Sentinel 阿里开源流量防护组件

一、前言

Sentinel 是面向分布式服务架构的高可用流量防护组件

Sentinel 具有以下特性:

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

二、Sentinel基本概念

资源

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

规则

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

下面通过一个Demo来熟悉一下这两个概念

 private static final String sourceName = "s1";
    public static void main(String[] args) {
        initFlowRules();
        while (true) {
            Entry entry = null;
            try {
                //被保护的资源
                entry = SphU.entry(sourceName);
                //执行业务逻辑
                System.out.println("hello world");
            } catch (BlockException e) {
                // 资源访问阻止,被限流或被降级都会触发BlockException
                // 在此处进行相应的处理操作
                e.printStackTrace();
                System.out.println("block exception");
            }finally {
                if(entry != null) {
                    entry.exit();
                }
            }
        }
    }

    //硬编码规则
    private static void initFlowRules(){
        //该资源每秒最多只能通过 20 个请求。
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource(sourceName);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(20);
        rules.add(rule);
        //加载流控规则
        FlowRuleManager.loadRules(rules);
    }

 Demo 运行之后,我们可以在日志 ~/logs/csp/${appName}-metrics.log.xxx 里看到下面的输出:(~表示当前用户根目录)

|--timestamp-----|------date time----------|-resource-|p|block|s|e|rt

1655778763000|2022-06-21 10:32:43|s1|20|48|20|0|1

1655778764000|2022-06-21 10:32:44|s1|20|36641|20|0|0

1655778765000|2022-06-21 10:32:45|s1|20|80100|20|0|0

1655778766000|2022-06-21 10:32:46|s1|20|144822|20|0|0

其中 resource代表资源名, p 代表通过的请求, block 代表被阻止的请求, s 代表成功执行完成的请求个数, e 代表用户自定义的异常, rt 代表平均响应时长。

上面方式为手动修改规则(硬编码方式)一般仅用于测试和演示,生产上一般通过动态规则源的方式来动态管理规则,也就是在控制台为api设置规则,下面尝试一下这种方法


三、动态规则源 

(1)下载sentinel控制台jar包

下载地址

(2)因为Sentinel是一个springboot项目直接通过java -jar启动

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

(3)访问sentinel控制台(localhost:8080) 默认用户名和密码都是 sentinel 

(4)客户端接入sentinel控制台

相关依赖

 <!-- 依赖web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- sentinel springboot-starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2021.1</version>
        </dependency>
        <!-- sentinel 核心依赖-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>1.8.0</version>
        </dependency>

配置控制台信息 

server:
  port: 8081
spring:
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8080 #指定sentinel dashboard web 地址

 其中 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。

(5)官方测试代码部分

@RestController
public class SentinelController {

    @Autowired
    SentinelService sentinelService;

    @GetMapping("/foo")
    public String apiFoo(@RequestParam(required = false) Long t) throws Exception {
        if (t == null) {
            t = System.currentTimeMillis();
        }
        sentinelService.test();
        return sentinelService.hello(t);
    }

    @GetMapping("/baz/{name}")
    public String apiBaz(@PathVariable("name") String name) {
        return sentinelService.helloAnother(name);
    }
}

@Service
public class SentinelService {

    private static final String sourceName = "s1";

    @SentinelResource(value = "test", blockHandlerClass = {ExceptionUtil.class})
    public void test() {
        System.out.println("Test");
    }

    @SentinelResource(value = "hello", fallback = "helloFallback")
    public String hello(long s) {
        if (s < 0) {
            throw new IllegalArgumentException("invalid arg");
        }
        return String.format("Hello at %d", s);
    }

    @SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback",
            exceptionsToIgnore = {IllegalStateException.class})
    public String helloAnother(String name) {
        if (name == null || "bad".equals(name)) {
            throw new IllegalArgumentException("oops");
        }
        if ("foo".equals(name)) {
            throw new IllegalStateException("oops");
        }
        return "Hello, " + name;
    }

    public String helloFallback(long s, Throwable ex) {
        // Do some log here.
        ex.printStackTrace();
        return "Oops, error occurred at " + s;
    }

    public String defaultFallback() {
        System.out.println("Go to default fallback");
        return "default_fallback";
    }
}

public class ExceptionUtil {
    public static void handleException(BlockException ex) {
        System.out.println("Oops: " + ex.getClass().getCanonicalName());
    }
}

其中核心注解 @SentinelResource,通过该注解即可定义一个资源

@SentinelResource 注解包含以下属性

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型(资源调用的流量类型,入口流量还是出口流量),可选项(默认为 EntryType.OUT
  • blockHandler:用来处理异常为BlockException及其子类异常函数的名称
  • blockHandlerClass:用来处理异常为BlockException及其子类异常默认情况下, blockHandler与原始方法位于同一类中。 但是,如果某些方法共享相同的签名并打算设置相同的块处理程序,则用户可以设置存在块处理程序的类。 请注意,块处理程序方法必须是静态的。

blockHandler和blockHandlerClass要求:

1、方法必须为public

2、返回值类型必须与原函数返回值类型一致;

3、参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException

4、blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析

  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理
  • fallbackClass:同上
  • defaultFallback:默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑,若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效

fallbackfallbackClass和defaultFallback要求:

1、方法必须为public
2、返回值类型必须与原函数返回值类型一致;
3、方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
4、fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析

  • exceptionsToIgnore:用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
  • resourceType:资源的分类(类型)用于标识资源的分类,0-通用、1-WEB、2-RPC、3-GATEWAY、4-SQL,在 ResourceTypeConstants 类里有定义。

(6)启动客户端,触发客户端初始化,Sentinel 会在客户端首次调用的时候进行初始化(所以需要先触发一次/foo接口),开始向控制台发送心跳包。

进入控制台,稍等几秒,就会看到我们的客户端已经注册进来

(7) 配置流控规则 

当我们访问/foo接口,当qps > 3就会触发FlowException,而FlowException异常是继承BlockException,然后控制台会看到 


可以发现上面整个流程都没有依赖数据库,那配置的规则数存到哪里的呢?答案就是:内存中 ,这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。

Sentinel提供了以下三种模式,其中Pull模式和Push模式可以保证规则持久化,具体如何做留到后文展示

推送模式说明优点缺点
原始模式API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource简单,无任何依赖不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等简单,无任何依赖;规则持久化不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。规则持久化;一致性;快速引入第三方依赖

参考链接:新手指南 · alibaba/Sentinel Wiki · GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值