• Sentinel限流示例:编码和注解限流


    一、Sentinel 是什么?

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

    Sentinel 具有以下特征:

    • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

    • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

    • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

    • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

    Sentinel 的主要特性:

    Sentinel 的开源生态:

    二、如何使用Sentinel

    我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用Sentinel 来进行资源保护,主要分为两个步骤:

    •     定义资源
    •     定义规则

    先把可能需要保护的资源定义好,之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。
    对于主流的框架,我们提供适配,只需要按照适配中的说明配置,Sentinel 就会默认定义提供的服务,方法等为资源。

    三、示例

    3.1、Sentinel编码限流

    先来简单的体验下 Sentinel 吧,在你的Maven项目中增加 Sentinel 的依赖:

        <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-core</artifactId>
        <version>1.4.0</version>
        </dependency>

    Sentinel中需要限流的称之为资源,对资源进行处理,下面来看最简单的一段代码:

    package com.example.demo.service;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    import com.alibaba.csp.sentinel.Entry;
    import com.alibaba.csp.sentinel.SphU;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.alibaba.csp.sentinel.slots.block.RuleConstant;
    import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
    import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
    
    public class LimitByCoding {
        public static void main(String[] args) throws InterruptedException {
            initFlowRules();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000; i++) {
                /* 您的业务逻辑 - 开始 */
                System.out.println("hello world");
            }
            long endTime = System.currentTimeMillis();
            System.out.println(endTime - startTime);
            long startTime2 = System.currentTimeMillis();
            for (int i = 0; i < 1000; i++) {
                Entry entry = null;
                try {
                    entry = SphU.entry("HelloWorld");
                    /* 您的业务逻辑 - 开始 */
                    System.out.println("hello world");
                    /* 您的业务逻辑 - 结束 */
                } catch (BlockException e1) {
                    /* 流控逻辑处理 - 开始 */
                    System.out.println("block!");
                    /* 流控逻辑处理 - 结束 */
                } finally {
                    if (entry != null) {
                        entry.exit();
                    }
                }
            }
            long endTime2 = System.currentTimeMillis();
            System.out.println(endTime2 - startTime2);
        }
    
        private static void initFlowRules() {
            List<FlowRule> rules = new ArrayList<>();
            FlowRule rule = new FlowRule();
            rule.setResource("HelloWorld");
            rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
            // Set limit QPS to 20.
            rule.setCount(20);
            rules.add(rule);
            FlowRuleManager.loadRules(rules);
        }
    }

    第一行中初始化限流的规则,创建了一个资源叫 HelloWorld,设置了这个资源的QPS 为 20。

    结果:

    上面的例子中次数为1000次,只输出一句话,不做任何限制,执行完成的时间大概在9毫秒左右。

    下面加上限流的逻辑,执行完成的时间基本上就超过91毫秒了,可见限流起了作用。

    在业务开始前使用SphU.entry();方法标识开始,结束使用entry.exit();,如果触发了流控逻辑就会抛出BlockException异常让用户自行处理。

    代码运行之后,我们可以在日志 ~/logs/csp/${appName}-metrics.log.xxx 里看到下面的输出:

        |--timestamp---|------date time--|-resource-|p |block|s  |e|rt
    
        1529998904000|2018-06-26 15:41:44|HelloWorld|20|0    |20|0|0
    
        1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728
    
        1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0
    
        1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0
    
        1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0
    
        1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0
    • p:通过的请求,

    • block:被阻止的请求

    • s:成功执行完成的请求个数

    • e:用户自定义的异常

    • rt:平均响应时长。

    3.2、Sentinel注解限流

    这种做法不好的地方在于每个需要限制的地方都得写代码,从 0.1.1 版本开始,Sentinel 提供了 @SentinelResource 注解的方式,非常方便。

    要使用注解来保护资源需要引入下面的Maven依赖:

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

     完整的pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.19.RELEASE</version>
            <relativePath /> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.dxz</groupId>
        <artifactId>LimitDemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>LimitDemo</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-annotation-aspectj</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>4.3.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>4.3.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.3.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>4.3.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-expression</artifactId>
                <version>4.3.17.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
                <version>1.7.10</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.2</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>

    引入之后我们需要配置SentinelResourceAspect切面让其生效,因为是通过SentinelResourceAspect切面来实现的,我这边以Spring Boot中使用进行配置示列:

    package com.example.demo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
    
    @Configuration
    public class AopConfiguration
    {
        @Bean
        public SentinelResourceAspect sentinelResourceAspect()
        {
            return new SentinelResourceAspect();
        }
    
    }

    然后在需要限制的方法上加SentinelResource注解即可:

    package com.example.demo.service;
    
    import org.springframework.stereotype.Component;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.example.demo.ExceptionUtil;
    
    @Component
    public class BuzService {
        
        @SentinelResource(value = "get", blockHandler = "exceptionHandler")
        public String get(String id) {
            return "http://cxytiandi.com";
        }
    
        @SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class })
        public String get2() {
            return "http://cxytiandi.com";
        }
    
        public String exceptionHandler(String id, BlockException e) {
            e.printStackTrace();
            return "错误发生在" + id;
        }
    }

    SentinelResource:value

    表示资源名,必填项

    SentinelResource:blockHandler

    处理 BlockException 的方法名,可选项。若未配置,则将 BlockException 直接抛出。

    • blockHandler 函数访问范围需要是 public

    • 返回类型需要与原方法相匹配

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

    • blockHandler 函数默认需要和原方法在同一个类中

    如果你不想让异常处理方法跟业务方法在同一个类中,可以使用 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

    业务方法:

        @SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class })
        public String get2() {
            return "http://cxytiandi.com";
        }

    异常处理类:

    package com.example.demo;
    
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    
    public final class ExceptionUtil {
        public static String handleException(BlockException ex) {
            System.err.println("错误发生: " + ex.getClass().getCanonicalName());
            return "error";
        }
    
    }

    如何测试?

    我们可以在Spring Boot的启动类中定义规则,然后快速访问接口,就可以看出效果啦,或者用压力测试工具ab等。

    package com.example.demo;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import com.alibaba.csp.sentinel.slots.block.RuleConstant;
    import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
    import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
    
    @SpringBootApplication
    public class LimitDemoApplication {
    
        public static void main(String[] args) {
            initFlowRules();
            SpringApplication.run(LimitDemoApplication.class, args);
        }
    
        private static void initFlowRules() {
            List<FlowRule> rules = new ArrayList<>();
            FlowRule rule = new FlowRule();
            rule.setResource("get");
            rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
            rule.setCount(1);
            rules.add(rule);
    
            rule = new FlowRule();
            rule.setResource("get2");
            rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
            rule.setCount(1);
            rules.add(rule);
    
            FlowRuleManager.loadRules(rules);
        }
    }

    结果:

    源码分析

    只需要配置了SentinelResourceAspect就可以使用注解,我们来简单的看下SentinelResourceAspect的源码

        @Aspect
        public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
      @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
        public void sentinelResourceAnnotationPointcut() {
    
        }
    
         
        @Around("sentinelResourceAnnotationPointcut()")
        public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
    
        // 获取当前访问的方法
        Method originMethod = resolveMethod(pjp);
        // 获取方法上的SentinelResource注解
        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
        // Should not go through here.
        throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
    
        // 获取资源名
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        Entry entry = null;
        try {
        entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
        Object result = pjp.proceed();
        return result;
        } catch (BlockException ex) {
        // 处理被限制的异常,回调事先配置的异常处理方法
        return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {
        Tracer.trace(ex);
        throw ex;
        } finally {
        if (entry != null) {
        entry.exit();
        }
        }
        }
        }

    上面是整个切面的代码,对所有加了SentinelResource注解的方法进去切入。细节代码在AbstractSentinelAspectSupport中,大家自己去看看。

  • 相关阅读:
    s3c2440的2440init.s的分析(转载)
    sql server错误状态列表(转载)
    vector类、迭代器、bitset类(转载)
    VC++以ADO方式连接SQL SERVER
    WinCE流设备驱动简介及GPIO驱动的实现(转载)
    .obj, .lib, .dll, .exe的关系(转载)
    利用DynamicExpression.ParseLambda实现Eval
    Matching Balanced Constructs with .NET Regular Expressions
    VB.NET and C# Comparison【转】
    ReportViewer一个简单例子
  • 原文地址:https://www.cnblogs.com/duanxz/p/5280636.html
Copyright © 2020-2023  润新知