Spring Cloud Alibaba Sentinel 用法总结

常用的限流策略:

  1. Niginx添加限流模块限制平均访问速度
  2. 通过数据库连接池,线程池大小限制
  3. 通过Guava包Ratelimiter限制接口访问速度
  4. Tcp通信协议中流量整形

常见限流算法:

1.计数器算法

在指定周期内限制访问次数,进入下一个时间周期次数清0.

这种算法可以用在短信发送频次限制上,比如限制一个用户一分钟之内触发短信发送的次数。

可以借助 redis incr 命令实现

缺点:

  • 临界问题:单位周期内,某一个时间点达到峰值,导致后续单位时间内无法访问。

2.滑动窗口法

在固定窗口中分割出多个小窗口,分别在每个小时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的所有小时间窗口总的计数即可

解决了固定窗口中的临界问题

Sentinel就是才用的滑动窗口算法

3.令牌桶限流算法

令牌通是网络流量整形和速率限制中最常使用的一种算法,每一个请求都需要从桶中获得一个令牌,如果没有获得令牌,则触发限流策略。

  • 请求速度大于令牌生成速度:令牌被用完后,后续请求被限流。
  • 请求速度等于令牌生成速度:流量处于平稳状态。
  • 请求速度小于令牌生成速度:并发数不高,正常被处理。

令牌通能够处理突发流量,也就是在短时间内新增的流量系统能够正常处理,这是令牌通的特性。

利用 Guava concurrent 包下 的 RateLimiter 限流工具类可以实现令牌桶算法

 

 

4.漏桶限流算法

主要作用是控制数据注入网络的速度,平滑网络上的突发流量。

维护一个固定容量的桶,这个桶会按照指定的速度漏水。

请求到达系统就类似于向桶中加水,如果这个桶满了,就忽略后面来的请求,直到这个桶可以存放多余的水。

漏桶算法可以将系统处理能力维持在一个比较平稳的水平,缺点就是瞬时流量过来后,会拒绝后续的请求,类似队列满了以后拒绝。

 

Sentinel

分为两部分:

  1. 核心库(Java客户端):不依赖任何框架/库。能够运行与java运行时环境,同时对Dubbo,SpringCloud等框架有较好支持、
  2. 控制台(Dashboard):基于Spring boot开发,打包后直接运行,不需要额外的容器

Sentinel实现限流

使用方式:

  1. 抛出异常的方式定义资源
  2. 返回布尔值方式定义资源
  3. 注解方式
  4. 异步调用

官方使用手册

规则种类:

  1. 流量控制规则
  2. 熔断降级规则
  3. 系统保护规则
  4. 访问控制规则
  5. 热点规则
  6. 查询更改规则
  7. 定制自己的持久化规则

官方使用手册

基于并发数和QPS的流量控制

Sentinel流量控制统计有2中,通过grade属性控制

  • 并发线程数(FLOW_GRADE_THREAD)
  • QPS(FLOW_GRADE_QPS)

并发线程数:

如果A服务调用B服务,B不稳当或者延迟,那么A的吞吐量会下降,占用更多的线程,且线程阻塞一致未释放,会导致线程池耗尽。

解决方式:通过不同业务逻辑使用不同线程池来隔离业务自身资源争抢问题,但是会造成线程数量过多带来的上下文切换问题。

Sentinel并发线程数限流,就是统计当前请求的上下文线程数量,如果超出阈值,则后续请求会被拒绝。

 

Qps:

一台服务器每秒能够响应的查询次数,当QPS达到限流的阈值时,就会触发限流策略

当Qps超过阈值时,会触发限流行为,通过controlBehavior来设置,包含:

  • 直接拒绝 (RuleConstant.CONTROL_BEHAVIOR_DEFAULT)
  • Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)
  • 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
  • 冷启动+匀速排队(RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER)

 

调用关系流量策略

调用关系包括调用方和被调用方,一个方法又可能会调用其他方法,形成一个调用链。

调用关系流量策略,就是根据不同的调用维度来出发流量控制。

  • 根据调用方限流
  • 根据调用链路入口限流
  • 据有关系的资源流量控制

调用方限流:

根据请求来源进行流量控制,需要设置limitApp

  • default:表示不区分调用者,任何调用者都会进行限流统计。
  • {some_origin_name}:设置特定的调用者,只有来自这个调用者的请求才会进行流量统计和控制。
  • other:标识针对除{some_origin_name}外的其他调用者进行流量控制

根据调用链路入口限流:

一个被限流保护的方法,可能来自不同的调用链,如资源nodeA,入口Entrance1 和 Entrance2 都调用了资源nodeA,如果志云与某个入口来进行流量统计,那么设置Entrance1入口,的调用才会统计请求次数,一定程度有点像 调用方限流。

关联流量控制:

两个资源之间存在争抢时,就说明两个资源存在关联。造成相互影响执行效率问题,所以关联流量控制(流控)就是限制其中一个资源的执行流量。

 

Sentinel实现服务熔断

与限流类似,限流才用的是FlowRule,而熔断才用的是DegradeRule

private static void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule(KEY)
        .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
        // Max allowed response time
        .setCount(50)
        // Retry timeout (in second)
        .setTimeWindow(10)
        // Circuit breaker opens when slow request ratio > 60%
        .setSlowRatioThreshold(0.6)
        .setMinRequestAmount(100)
        .setStatIntervalMs(20000);
    rules.add(rule);

    DegradeRuleManager.loadRules(rules);
    System.out.println("Degrade rule loaded: " + rules);
}

Sentinel三种熔断策略

  • 平均响应时间(RuleConstant.DEGRADE_GRADE_RT):如果1s内持续进入5个请求,对应的平均响应时间都超过了阈值(count,单位 ms) ,那么在接下来的时间窗口(timeWindow , 单位为s)内,对这个方法的调用都会自动熔断,抛出 DegradeException。

Sentinel 默认统计的RT上限是4900ms,如果超出此阈值都会算4900ms,可以通过启动参数来配置。

  • 异常比例(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO):如果每秒资源数>=minRequestAmount(默认值为5),并且每秒的异常总数占总通过量的比例超过阈值count (count 的取值范围是 【0.0 ,1.0】 代表 0 ~ 100%,则资源进入降级状态,在接下来的timeWindow之内,对这个方法的调用都会触发熔断。

 

  • 异常数(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT):当资源最近一分钟的异常数目超过阈值之后,会触发熔断。需要注意的是,如果timeWindow小于60s,则结束熔断状态后仍然可能再进入熔断状态。


Spring Cloud 集成 Sentinel

pom文件引入依赖

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

通过@SentinelResource 注解配置限流保护资源

@RestController
public class HelloController {

    @SentinelResource(value = "hello",blockHandler = "blockHandlerHello")
    @GetMapping("/say")
    public String hello(){
        return "hello ,Mic";
    }
    public String blockHandlerHello(BlockException e){
        return "被限流了";
    }
}
  • 在引入Sentinel starter pom 依赖以后,默认情况下,会对所有的HTTP请求限流埋点。
  • 如果想对特定方法进行限流或者降级,加@SentinalResource注解来实现即可
  • 可以通过Sphu.entry() 方法进行配置

手动配置流控规则,可以利用Sentinel 的 InitFunc SPI 扩展接口实现

public class FlowRuleInitFunc implements InitFunc{
    @Override
    public void init() throws Exception {
        List<FlowRule> rules=new ArrayList<>();
        FlowRule rule=new FlowRule();
        rule.setCount(1);
        rule.setResource("hello");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

SPI是扩展点机制,如果需要被Sentinel加载,需要在resource目录下创建META-INF/service/com.alibaba.csp.sentinel.init.InitFunc

在此文件中指定 FlowRuleInitFunc  类的路径。

最终效果如下:

 

基于Dashboard来实现流控配置

 

 

自定义URL限流异常

在默认情况下 URL 出发限流后会直接返回。

如果希望出发限流后返回结果形式,可以通过自定义限流异常来处理,实现CustomUrlBlockHandler 并且重写 blocked方法:

public class CustomUrlBlockHandler implements UrlBlockHandler{
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
        httpServletResponse.setHeader("Content-Type", "application/json;charset=UTF-8");
        String message = "{\"code\":999,\"msg\":\"访问人数过多\"}";
        httpServletResponse.getWriter().write(message);
    }
}

当触发限流之后,希望直接跳转到一个限流页面:

spring.cloud.sentinel.servlet.block-page={url}

 

URL资源清洗

Sentinel 中Http 服务限流 默认是有 Sentinel-Web-Servlet 中的 CommonFilter 实现的。

假设只针对 clean 请求做流控处理,而不是针对每一个不同的{id} 进行流控。

@RestController
public class UrlCleanController {

    @GetMapping("/clean/{id}")
    public String dash(@PathVariable("id")int id){
        return "Hello clean";
    }
}

 

可以通过UrlCleaner接口来实现资源清洗,将clean/id 归集到 clean/*

@Service
public class CustomerUrlCleaner implements UrlCleaner{
    @Override
    public String clean(String originUrl) {
        if(StringUtils.isEmpty(originUrl)){
            return originUrl;
        }
        if(originUrl.startsWith("/clean/")){
            return "/clean/*";
        }
        return originUrl;
    }
}

 

Sentinel 集成 Nacos 实现流控

Sentinel Dashboard 所配置的流控规则,都是保存在内存中的,一旦应用重启,这些规则都会被清除。

Sentinel提供了动态数据源支持:Consul,ZooKeepr,Redis,Nacos,Apollo,etcd 等数据源的扩展。

<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
   <version>1.7.0</version>
</dependency>
spring:
  application:
    name: spring-cloud-sentinel-dynamic
  cloud:
    sentinel:
      transport:
        dashboard: 192.168.216.128:7777
      datasource:
        - nacos:
            server-addr: 192.168.216.128:8848
            data-id: ${spring.application.name}-sentinel-flow
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: flow
server:
  port: 8888

datasource 目前支持redis,apollo ,zk,file,nacos 

data-type 配置项表示 Converter 类型,Spring Cloud Alibaba Sentinel 默认提供两种内置的值,分别是 json 和 xml (不填默认是json)。 如果不想使用内置的 json 或 xml 这两种 Converter,可以填写 custom 表示自定义 Converter,然后再配置 converter-class 配置项,该配置项需要写类的全路径名(比如 spring.cloud.sentinel.datasource.ds1.file.converter-class=com.alibaba.cloud.examples.JsonFlowRuleListConverter)。

rule-type 配置表示该数据源中的规则属于哪种类型的规则(flowdegradeauthoritysystemparam-flowgw-flowgw-api-group)。

@RestController
public class DynamicController {

    @GetMapping("/dynamic")
    public String dynamic(){
        return "Hello Dynamic Rule";
    }
}

修改接口流控规则目前就有两个入口:

  1. Dashboard
  2. Nacos

在Dashboard上修改规则,不能同步到Nacos上,目前Sentinel Dashboard上不支持此功能。

但是再Nacos上修改规则,危险系数很高,没有专门的UI管理,以修改参数的形式容易引发问题。

 

Sentinel Dashboard 集成 Nacos 实现规则同步

com.alibaba.csp.sentinel.dashboard.controller.v2包下有一个 FlowControllerV2 类,提供流控规则的CRUD,和V1版本不同的是,他可以实现指定数据源的规则拉取和发布。

@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {

    private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);

    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    @Autowired
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleNacosPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
  • DynamicRuleProvider 动态规则拉取
  • DynamicRulePublisher 动态规则发布

 

修改源码步骤:

1. 注释 <scope>

!-- for Nacos rule publisher sample -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
   <!-- <scope>test</scope>-->
</dependency>

2.修改sentinel-dashboard\src\main\webapp\resources\app\scripts\directives\sidebar\sidebar.html

修改之后会调用 FlowControllerV2 中的接口

<li ui-sref-active="active" ng-if="!entry.isGateway">
 <!-- <a ui-sref="dashboard.flowV1({app: entry.app})">-->
  <a ui-sref="dashboard.flow({app: entry.app})">
    <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>
</li>

3.在com.alibaba.csp.sentinel.dashboard.rule 包中创建一个 nacos 包,用来加载外部化配置

@ConfigurationProperties(prefix="sentinel.nacos",ignoreUnknownFields = true)
public class NacosPropertiesConfiguration {

    private String serverAddr;
    private String dataId;
    private String groupId = "DEFAULT_GROUP";
    private String namespace;

4.创建一个Nacos配置类

@EnableConfigurationProperties(NacosPropertiesConfiguration.class)
@Configuration
public class NacosConfiguration {

    @Bean
    public Converter<List<FlowRuleEntity>,String> flowRuleEntityEncoder(){
        return JSON::toJSONString;
    }

    @Bean
    public Converter<String,List<FlowRuleEntity>> flowRuleEntityDecoder(){
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    }

    @Bean
    public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws NacosException {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
        properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
        return ConfigFactory.createConfigService(properties);
    }
}

5.创建一个常量类 NacosConstans,分别表示 GROUP_ID 和 DATA_ID 的后缀

public class NacosConstants {
    public static final String DATA_ID_POSTFIX = "-sentinel-flow";
    public static final String GROUP_ID = "DEFAULT_GROUP";
}

6.实现动态从Nacos 配置中心获取流控规则

@Service
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>>{
    private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosProvider.class);

    @Autowired
    private NacosPropertiesConfiguration nacosConfigProperties;

    @Autowired
    private ConfigService configService;

    @Autowired
    private Converter<String, List<FlowRuleEntity>> converter;

    @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
        String dataID=new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
        String rules = configService.getConfig(dataID, nacosConfigProperties.getGroupId(), 3000);
        logger.info("pull FlowRule from Nacos Config:{}",rules);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}

7.创建一个流控规则发布类,在Dashboard修改完后,推送到Nacos中

@Service
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {

    @Autowired
    private NacosPropertiesConfiguration nacosPropertiesConfiguration;

    @Autowired
    private ConfigService configService;
    @Autowired
    private Converter<List<FlowRuleEntity>, String> converter;

    @Override
    public void publish(String appName, List<FlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(appName, "appName cannot be empty");
        if (rules == null) {
            return;
        }
        String dataID=new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
        configService.publishConfig(dataID, nacosPropertiesConfiguration.getGroupId(), converter.convert(rules));
    }
}

8.修改FlowControllerV2类,将新配置的两个类注入进来

@Autowired······
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

9.application.properties 中添加 Nacos 服务端配置信息。

sentinel.nacos.serverAddr=localhost:8848
sentinel.nacos.namespace=
sentinel.nacos.group-id=DEFAULT_GROUP

10.重新打包,启动服务

 

Sentinel 热点限流

 

针对访问频次非常高的数据进行限流,比如针对一段时间内频繁访问的用户IP地址进行限流,或者针对频繁访问的某个用户进行限流。

热点限流在一下场景中使用较多:

  • 服务网关层:防止网络爬虫和恶意攻击,一种方法是限制爬虫的IP地址,客户端IP地址就是一种热点参数。
  • 写数据的服务:业务系统提供写数据服务,数据会写入数据库之类的存储。存储系统的底层会加锁写磁盘上的文件,部分存储系统会将某一类数据写入同一个文件。如果底层写同一文件,会出现抢占锁的情况,导致出现大量的超时和失败。出现这种情况时一般有两种解决方法:修改存储设计,对热点参数限流

Sentinel 通过 LRU 策略结合滑动窗口机制来实现热点参数的统计,LRU 策略可以统计单位时间内最常访问的热点数据,滑动窗口机制可以协助统计每个参数的QPS。

限流依赖包:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-parameter-flow-control</artifactId>
    <version>${project.version}</version>
</dependency>

热点参数限流使用

设置热点参数限流埋点 通过  ParamFlowRuleManager.loadRules 方法加载热点参数规则。

热点参数是通过ParamFlowRule来配置的,大部分属性和FlowRule类似。

官方从参考文档

@PostConstruct
public void initParamRule(){

    ParamFlowRule rule=new ParamFlowRule(resourceName);
    rule.setParamIdx(0);
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(1);
    ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
@GetMapping("/hello")
public String sayHello(@PathParam("id")String id,@PathParam("name")String name){
    Entry entry=null;
    try {
        entry=SphU.entry(resourceName, EntryType.IN,1,id);
        return "access success";
    } catch (BlockException e) {
        e.printStackTrace();
        return "block";
    }finally {
        if(entry!=null) {
            entry.exit();
        }
    }
}

http://localhost:8888/hello?id=1

当传入id时,进行流控

http://localhost:8888/hello 不传id时,不进行流控

 

@SentinelResource 热点参数限流

@SentinelResource
@GetMapping("/hello")
public String sayHello(@PathParam("id")String id){
    return "access success";
}

当注解所配置的方法上有参数是,Sentinel会把这些参数传入 Sphu.enty(res,args) 。会把id这个参数作为热点参数进行限流。

默认情况下,当用户访问这个接口时就会触发热点限流规则的验证。

 

 

硬编码限流

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.7.1</version>
</dependency>
public class Test {
    public static void main(String[] args) {
        initFlowRules();
        while (true) {
            doSomething();
        }
    }


    private static void doSomething(){
        try( Entry entry = SphU.entry("doSomething")) {
            /*您的业务逻辑 - 开始*/
            System.out.println("hello world doSomething" + System.currentTimeMillis());
            /*您的业务逻辑 - 结束*/
        } catch (BlockException e1) {
            /*流控逻辑处理 - 开始*/
           // System.out.println("block!");
            /*流控逻辑处理 - 结束*/
        } /*finally {
            if (entry != null) {
                entry.exit();
            }
        }*/
    }




    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("doSomething");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 20.
        rule.setCount(20);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

${USER_HOME}\logs\csp\{包名-类名}-metrics.log

  • passQps:代表通过的请求数
  • blockQps:代表被阻止的请求数
  • successQps:代表成功执行完成的请求个数
  • rt:代表平均响应市场
  • occupiedPassQps:代表优先通过的请求
  • concurrency:代表并发量
  • classsification:代表组员类型

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值