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中,大家自己去看看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM