springcloud-限流(gateway、sentinel)

springcloud-限流

限流算法

  1. 计数器

    每个单位时间能通过的请求数固定,超过阈值直接拒绝。

    通过维护一个单位时间内的计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被绝,直到单位时间已经过去,再将计数器重置为零。

  2. 漏桶算法

    维持一个队列,所有请求先进队列,然后从队列取出请求的速率是固定。保护请求

    漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。

    漏桶算法需要通过两个变量进行控制:一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)(从队列取出请求)

  3. 令牌桶算法

    按一定额定的速率产生令牌,存入令牌桶,桶有最大容量(应该为微服务最大承载);服务过来时需要请求到一个令牌才可以进入服务执行;服务里就可以保持基本不会超过承载值。【保护服务】

    令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

一、基于springcloud-gateway的Filter限流

SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。

  1. redis 和 redis依赖

    • redis

      下载redis,启动redis-server服务端,启动redis-cli客户端

    • 依赖

      springcloud项目需要引入redis的reactive依赖

      <dependency>
      <groupId>org.springframework.cloudgroupId>
          <artifactId>spring-cloud-starter-actuatorartifactId>
      dependency>
      	<dependency>  		 <groupId>org.springframework.bootgroupId>
            <artifatId>spring-boot-starter-data-redis-reactiveartifactId>
      dependency>
      
  2. 修改网关的aplication.yml

    spring:
      application:
        name: api-gateway #指定服务名
      cloud:
        gateway:
          routes:
          - id: order-service
          uri: lb://shop-service-order
          filters:
          - RewritePath=/order-service/(?>.*), /$\{segment}
          - name: RequestRateLimiter
            args:
              # 使用SpEL从容器中获取对象pathKeyResolver,根据这个对象来进行限流
              key-resolver: '#{@pathKeyResolver}'
              # 令牌桶每秒填充平均速率
              redis-rate-limiter.replenishRate: 1
              # 令牌桶的总容量
              redis-rate-limiter.burstCapacity: 3
      redis:
        host: localhost
        port: 6379
    
  3. 配置redis中key的解析器KeySesolver(包含上面配置的key-resolver对象pathKeyResolver)

    @Configuration
    public class KeyResolverConfiguration {
    
    	/**
    	 * 编写基于请求路径的限流规则
    	 *  //abc
    	 */
    	//@Bean
    	public KeyResolver pathKeyResolver() {
    		//自定义的KeyResolver
    		return new KeyResolver() {
    			/**
    			 * ServerWebExchange :
    			 *      上下文参数
    			 */
    			@Override
    			public Mono<String> resolve(ServerWebExchange exchange) {
    				return Mono.just( exchange.getRequest().getPath().toString());
    			}
    		};
    	}
    
    	/**
    	 * 基于请求参数的限流
    	 *
    	 *  请求 abc ? userId=1
    	 */
    	@Bean
    	public KeyResolver userKeyResolver() {
    #        基于请求参数userId的限流
    		return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId")
    			
    //基于请求ip的限流                                     //exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 
    		);
    	}
    }
    
    

    通过reids的MONITOR可以监听redis的执行过程。这时候Redis中会有对应的数据。

    大括号中就是我们的限流Key,这边是IP,本地的就是localhost
    timestamp:存储的是当前时间的秒数,也就是System.currentTimeMillis() / 1000或者
    Instant.now().getEpochSecond()
    tokens:存储的是当前这秒钟的对应的可用的令牌数量


二、基于Sentinel的限流

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。

从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:

  • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
  • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组.

Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑:

  • GatewayFlowRule :网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
  • ApiDefinition :用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api ,请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
  1. 导入Sentinel 的响应依赖
<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
    <version>1.6.3version>
dependency>
  1. 编写配置类
/**
 * sentinel限流的配置
 */
//@Configuration
public class GatewayConfiguration {

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

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

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

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

	/**
	 * 配置初始化的限流参数
	 *  用于指定资源的限流规则.
	 *      1.资源名称 (路由id)
	 *      2.配置统计时间
	 *      3.配置限流阈值
	 */
	@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
//		rules.add(new GatewayFlowRule("product-service")
//				.setCount(1)
//				.setIntervalSec(1)
//		);
		rules.add(new GatewayFlowRule("product_api")
			.setCount(1).setIntervalSec(1)
		);


		GatewayRuleManager.loadRules(rules);
	}

	/**
	 * 自定义API限流分组
	 *      1.定义分组
	 *      2.对小组配置限流规则
	 */
	@PostConstruct
	private void initCustomizedApis() {
		Set<ApiDefinition> definitions = new HashSet<>();
        //限流小组1 api1
		ApiDefinition api1 = new ApiDefinition("product_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/product-service/product/**").
     //已/product-service/product/开都的所有url			
  setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
				}});
         //限流小组2 api2
		ApiDefinition api2 = new ApiDefinition("order_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/order-service/order")); //完全匹配/order-service/order 的url
				}});
        //将小组通知给sentinel
		definitions.add(api1);
		definitions.add(api2);
	
 GatewayApiDefinitionManager.loadApiDefinitions(definitions);
        //然后在initGatewayRules方法中rule.add添加限流规则
	}

	/**
	 * 自定义限流处理器
	 */
	@PostConstruct
	public void initBlockHandlers() {
		BlockRequestHandler blockHandler = new BlockRequestHandler() {
			public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
				Map map = new HashMap();
				map.put("code",001);
				map.put("message","不好意思,限流啦");
				return ServerResponse.status(HttpStatus.OK)
						.contentType(MediaType.APPLICATION_JSON_UTF8)
						.body(BodyInserters.fromObject(map));
			}
		};
		GatewayCallbackManager.setBlockHandler(blockHandler);
	}
}

基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
@PostConstruct定义初始化的加载方法,用于指定资源的限流规则。这里资源的名称为orderservice,统计时间是1秒内,限流阈值是1。表示每秒只能访问一个请求。

如果我们只想对某个路由的参数做限流,那么可以使用参数限流方式:

rules.add(new GatewayFlowRule("order-service")
    .setCount(1)
    .setIntervalSec(1)
    .setParamItem(new GatewayParamFlowItem()
 .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id"))
);
//通过指定PARAM_PARSE_STRATEGY_URL_PARAM表示从url中获取参数,setFieldName指定参数名称

Sentinel 自定义异常提示

当触发限流后页面显示的是Blocked by Sentinel: FlowException。为了展示更加友好的限流提示,Sentinel支持自定义异常处理。

您可以在 GatewayCallbackManager 注册回调进行定制:setBlockHandler :注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为BlockRequestHandler 。默认实现为 DefaultBlockRequestHandler ,当被限流时会返回类似于下面的错误信息: Blocked by Sentinel: FlowException 。

@PostConstruct
public void initBlockHandlers() {
    BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
    public Mono<ServerResponse> handleRequest(ServerWebExchange
    serverWebExchange, Throwable throwable) {
    Map map = new HashMap<>();
    map.put("code", 001);
    map.put("message", "对不起,接口限流了");
    return ServerResponse.status(HttpStatus.OK).
    contentType(MediaType.APPLICATION_JSON_UTF8).
    body(BodyInserters.fromObject(map));
    }
    };
    GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}



项目源码:https://gitee.com/hxmkd/spring-cloud/tree/master/study_springcloud8_gateway

你可能感兴趣的:(springcloud,springcloud,gateway,sentinel,网关)