SpringCloud 微服务网关Gateway常用限流算法以及简单实现

概述:在SpringCloud中我们之前在使用Hystrix 以及 sentinel 中都可以对服务调用进行限流控制,在SpringCloud体系中通过网关也可以对整个系统架构进行限流处理。本章将介绍一些通过微服务网关实现限流的一些常见算法以及其在SpringCloud Gateway 网关下的实现方式。

一、常见的限流算法

1,计数器算法

计数器算法为最简答的限流算法,其实现原理是为维护一个单位时间内的计数器。在单位时间内,开始计数器为0,每次通过一个请求计数器+1。如果单位时间内 计数器的数量大于了预先设定的阈值,则在此刻到单位时间的最后一刻范围内的请求都将被拒绝。单位时间结束计数器归零,重新开始计数。

 

2,漏桶算法

漏桶算法实际为一个容器请求队列,关键要素为 桶大小(队列大小),流出速率(出队速率)。即无论请求并发多高,如果桶内的队列满了,多余进来的请求都将被舍弃。由于桶的流出速率固定,所以可以保证限流后的请求并发数可以固定在一个范围内。

3,令牌桶算法

令牌桶算法为漏桶算法的一种改进。漏桶算法能够控制调用服务的速率,而令牌桶算法不仅能控制调用服务的速率,还能在短时间内允许一个超并发的调用。其实现原理为,存在一个令牌桶,并且有一个持续不断地产生令牌的机制,比如每秒产生100个令牌。桶存在一个固定大小,比如300。当桶中的令牌满了的时候,多余的令牌将被舍弃。

当请求过来时必须先从桶中获取一个令牌,桶内令牌数减一,获取到令牌的请求将被放行。桶中令牌被用光时,没有获取到令牌的请求将进行等待或者拒绝。这样在短期的时间内该算法将允许大于100,小于等于300的并发。如果持续有大于100的并发请求经过网关,在消耗完桶内令牌后,则最大通过网关的qps为产生令牌的速率,及 qps=100。

 

二、限流算法的简单实现方式

1,使用Filter 实现令牌桶算法(RequestRateLimiter局部过滤器,需要借助redis+lua脚本实现

  1. 修改pom 添加redis依赖
  2. 添加reids key 的解析器即key-resolver 解析类
  3. 调整配置文件

pom.xml 需要添加redis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

解析类src/main/java/com/xiaohui/gateway/config/KeyResolverConfiguration.java

package com.xiaohui.gateway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class KeyResolverConfiguration {

    @Bean
    public KeyResolver pathKeyResolver(){
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getPath().toString());
            }
        };
    }
}

application.properties 中主要部分

spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: 127.0.0.1
    pool: 6379
    database: 0
  cloud:
    gateway:
      routes:
      #配置路由: 路由id,路由到微服务的uri,断言(判断条件)
        - id: product-service #保持唯一
          #uri: http://127.0.0.1:8001 #目标为服务地址
          uri: lb://cloud-payment-service # lb:// 根据服务名称从注册中心获取请求地址路径
          predicates:
            #- Path=/payment/** #路由条件 path 路由匹配条件
            - Path=/product-service/** #给服务名称前加上一个固定的应用分类路径 将该路径转发到 http://127.0.0.1:8001/payment/get/1
          filters: #配置路由过滤器  http://127.0.0.1:8080/product-service/payment/get/1 -> http://127.0.0.1:8001/payment/get/1
          - name: RequestRateLimiter
            args:
              #使用SpEL从容器中获取对象
              key-resolver: '#{@pathKeyResolver}'
              #桶令牌每秒产生平均速率
              redis-rate-limiter.replenishRate: 1
              #令牌桶的上限
              redis-rate-limiter.burstCapacity: 2
          - RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写的过滤器,在yml中$写为 $\

如果需要进行根据判断参数(userId)进行设置限流则其解析类可以调整为如下示例(修改配置文件中key-resolver 项为 paramKeyResolver):

package com.xiaohui.gateway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class KeyResolverConfiguration {

    @Bean
    public KeyResolver paramKeyResolver(){
        return exchange -> Mono.just(
                exchange.getRequest().getQueryParams().getFirst("userId")
        );
    }
}

效果:每秒钟一次请求http://127.0.0.1:8080/product-service/payment/get/1?userId=11 没问题,如果连续每秒钟多次请求改地址,则部分请求会出现返回429错误码。

2,使用Sentinel 实现限流

在Gateway + Sentinel的实现限流方案中,有两种限流方式:

  1. 基于 route-id 的ruote维度:即在application.yml 文件中的spring.cloud.gateway.routes[0].id配置项
  2. 基于自定义API维度:通过 ApiDefinition 定义不同的分组,进行分组限流控制

使用Gateway+Sentinel的形式 都需要添加Sentinel的依赖,依赖坐标如下:

        <!-- 使用sentinel对gateway进行限流实现 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
            <version>1.6.3</version>
        </dependency>

实现方式一:基于route-id的形式

该种配置方式将对配置的路由id中的微服务中全部接口进行限流控制。无法进行单独的限流控制。

package com.xiaohui.gateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolverProvider,
                                ServerCodecConfigurer serverCodecConfigurer){
        this.viewResolvers = viewResolverProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 配置限流的异常处理器
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
        return new SentinelGatewayBlockExceptionHandler(this.viewResolvers,this.serverCodecConfigurer);
    }

    /**
     * 配置限流过滤器
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGlobalFilter(){
        return new SentinelGatewayFilter();
    }

    /**
     * product-service: rules-id配置项值
     * setIntervalSec : 单位时间
     * setCount       : 调用总数
     */
    @PostConstruct
    public void initGatewayRules(){
        Set<GatewayFlowRule> rules = new HashSet<>();
        // 按照 rules-id配置项值 整体设置限流
        rules.add(new GatewayFlowRule("product-service").setIntervalSec(1).setCount(1));
        GatewayRuleManager.loadRules(rules);
    }


    /**
     * 自定义限流处理器
     */
    @PostConstruct
    public void initBlockHanlers(){
        BlockRequestHandler blockhandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap();
                map.put("code","001");
                map.put("msg","网关限流拦截返回...");
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockhandler);
    }

}

对应的application.yml 如下:

server:
  port: 8080
spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: 127.0.0.1
    pool: 6379
    database: 0
  cloud:
    gateway:
      routes:
      #配置路由: 路由id,路由到微服务的uri,断言(判断条件)
        - id: product-service #保持唯一
          #uri: http://127.0.0.1:8001 #目标为服务地址
          uri: lb://cloud-payment-service # lb:// 根据服务名称从注册中心获取请求地址路径
          predicates:
            #- Path=/payment/** #路由条件 path 路由匹配条件
            - Path=/product-service/** #给服务名称前加上一个固定的应用分类路径 将该路径转发到 http://127.0.0.1:8001/payment/get/1
          filters: #配置路由过滤器  http://127.0.0.1:8080/product-service/payment/get/1 -> http://127.0.0.1:8001/payment/get/1
          - RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写的过滤器,在yml中$写为 $\

      # 配置自动根据微服务名称进行路由转发 http://127.0.0.1:8080/cloud-payment-service/payment/get/1
      discovery:
        locator:
          enabled: true #开启根据服务名称自动转发
          lower-case-service-id: true #微服务名称已小写形式呈现

#eureka 注册中心
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka1.com:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip进行注册

该配置类中在initGatewayRules 方法中定义了需要限流的服务id product-service  对应的微服务  uri: lb://cloud-payment-service. initBlockHanlers方法定义了限流时对限流接口的返回信息封装。

实现方式二:基于自定义API维度

配置文件同实现方式一的,配置类如下:

package com.xiaohui.gateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolverProvider,
                                ServerCodecConfigurer serverCodecConfigurer){
        this.viewResolvers = viewResolverProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 配置限流的异常处理器
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
        return new SentinelGatewayBlockExceptionHandler(this.viewResolvers,this.serverCodecConfigurer);
    }

    /**
     * 配置限流过滤器
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGlobalFilter(){
        return new SentinelGatewayFilter();
    }

    /**
     * product-service: rules-id配置项值
     * setIntervalSec : 单位时间
     * setCount       : 调用总数
     */
    @PostConstruct
    public void initGatewayRules(){
        Set<GatewayFlowRule> rules = new HashSet<>();
        // 按照 rules-id配置项值 整体设置限流
//        rules.add(new GatewayFlowRule("product-service").setIntervalSec(1).setCount(1));
        //分组限流
        rules.add(new GatewayFlowRule("product_api").setIntervalSec(1).setCount(1));
        rules.add(new GatewayFlowRule("order_api").setIntervalSec(3).setCount(1));
        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 自定义限流处理器
     */
    @PostConstruct
    public void initBlockHanlers(){
        BlockRequestHandler blockhandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap();
                map.put("code","001");
                map.put("msg","网关限流拦截返回...");
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockhandler);
    }

    /**
     * 自定义限流分组
     * 1,定义分组
     * 2,对个组设置匹配路径 如:/product-service/**
     */
    @PostConstruct
    private void initCustomizedApis(){
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("product_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                    add(new ApiPathPredicateItem().setPattern("/product-service/payment/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    add(new ApiPathPredicateItem().setPattern("/product-service/payment2/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        ApiDefinition api2 = new ApiDefinition("order_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>(){{
                    add(new ApiPathPredicateItem().setPattern("/product-service/order/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }
}

在 initCustomizedApis 方法中,定义了两个分组 "product_api"、"order_api",并分别配置了不同的匹配路径。

在initGatewayRules 方法中对不同的分组设定了不同的限流规则。单位时间和 阈值的指定。

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值