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脚本实现)
- 修改pom 添加redis依赖
- 添加reids key 的解析器即key-resolver 解析类
- 调整配置文件
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 {@Beanpublic KeyResolver pathKeyResolver(){return new KeyResolver() {@Overridepublic 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.1pool: 6379database: 0cloud: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/1filters: #配置路由过滤器 http://127.0.0.1:8080/product-service/payment/get/1 -> http://127.0.0.1:8001/payment/get/1- name: RequestRateLimiterargs:#使用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 {@Beanpublic 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的实现限流方案中,有两种限流方式:
- 基于 route-id 的ruote维度:即在application.yml 文件中的spring.cloud.gateway.routes[0].id配置项
- 基于自定义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 : 调用总数*/@PostConstructpublic void initGatewayRules(){Set<GatewayFlowRule> rules = new HashSet<>();// 按照 rules-id配置项值 整体设置限流rules.add(new GatewayFlowRule("product-service").setIntervalSec(1).setCount(1));GatewayRuleManager.loadRules(rules);}/*** 自定义限流处理器*/@PostConstructpublic void initBlockHanlers(){BlockRequestHandler blockhandler = new BlockRequestHandler() {@Overridepublic 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.1pool: 6379database: 0cloud: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/1filters: #配置路由过滤器 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/1discovery:locator:enabled: true #开启根据服务名称自动转发lower-case-service-id: true #微服务名称已小写形式呈现#eureka 注册中心
eureka:client:register-with-eureka: truefetch-registry: trueservice-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 : 调用总数*/@PostConstructpublic 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);}/*** 自定义限流处理器*/@PostConstructpublic void initBlockHanlers(){BlockRequestHandler blockhandler = new BlockRequestHandler() {@Overridepublic 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/***/@PostConstructprivate 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 方法中对不同的分组设定了不同的限流规则。单位时间和 阈值的指定。
SpringCloud 微服务网关Gateway常用限流算法以及简单实现相关推荐
- SpringCloud 微服务网关Gateway 动态路由配置
概述:在上一章节<SpringCloud 微服务网关Gateway介绍及简单路由配置>中我们讲述了Gateway的最简单的路由配置方式.但是其中比较明显的问题就是我们在配置路由服务的地址时 ...
- SpringCloud—— 微服务网关GateWay
目录 1.GateWay网关概述 1.1.什么是GateWay? 1.2.为什么要使用微服务网关? 1.3.Zuul与GateWay网关的区别? 2.快速入门 2.1.创建项目 2.2.配置yml文件 ...
- SpringCloud 微服务网关Gateway介绍及简单路由配置
概述:什么是微服务网关?为了解决用户客户端在调用微服务系统中的多个消费者工程接口时,需要维护非常多的消费者应用接口地址等信息,以及可能存在不同应用见的调用跨域等问题,微服务网关组件随即出现.网关作为用 ...
- SpringCloud微服务组件:Sentinel限流熔断
点击关注公众号,实用技术文章及时了解 前言 什么是雪崩问题? 微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况. 解决雪崩问题的常见方式有四种: 超时处理:设定超时时间,请 ...
- Spring Cloud之(十八)微服务网关GateWay
十八.微服务网关GateWay Zuul 1.x 是一个基于阻塞 IO 的 API Gateway 以及 Servlet:直到 2018 年 5 月,Zuul 2.x(基于Netty,也是非阻塞的,支 ...
- 微服务网关GateWay 过滤+路由+限流
文章目录 1 微服务网关概述 2 微服务网关微服务搭建 3 微服务网关跨域 4 微服务网关过滤器 5 微服务网关限流 5.1 思路分析 5.2 令牌桶算法 5.3 网关限流代码实现 1 微服务网关概述 ...
- Spring-Cloud 微服务网关Zuul、ZuulFilter过滤器和限流
微服务网关 一. Zuul网关 1. 创建工程 并导入依赖 2. application.yml 配置文件 3. 启动类添加注解 4. 依次启动服务 5. 进入浏览器访问测试 二. ZuulFilte ...
- 微服务之熔断、限流、降级 三板斧
系列服务器开发 文章目录 系列服务器开发 前言 一.背景 二.熔断 三. 限流 四. 降级 五.三种措施的差异 总结 前言 Spring Cloud全家桶是提供的一整套微服务开源解决方案,包括服务注册 ...
- golang bufio.newscanner如何超时跳出_Golang微服务的熔断与限流
(给Go开发大全加星标) 来源:Che Dan https://medium.com/@dche423/micro-in-action-7-cn-ce75d5847ef4 [导读]熔断和限流机制对于大 ...
- 微服务网关Gateway
使用场景 不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会多次请求不同的微服务,增加了客户端 ...
最新文章
- linux jdk配置环境变量
- 第二次做HDOJ 1051
- 学业水平考试b能上985吗_211 和985 的大学是不是要求学业水平考试全部是A
- Windows 2012 - Dynamic Access Control 浅析
- 电力电子、电机控制系统的建模和仿真_清华团队研发,首款国产电力电子仿真软件来啦~已捐赠哈工大、海工大、清华使用!...
- csv文件python是怎么输入的,python怎么读取和写入csv文件
- 程维谈智慧交通:我们赶上好时代 走出了自己路
- Docker : Error response from daemon: Get https://docker.elastic.co/v2/: net/http: TLS handshake time
- leetcode64. 最小路径和
- 设计模式(外观模式)
- Parallels Toolbox for mac(PD工具箱合集)
- shell脚本中变量的赋值
- JavaScript计算器(加减乘除完善)
- 移动端分享链接给微信好友
- 管理信息系统期末测试题
- 如何让我的世界变成别人的世界(国际版)——HMCL启动器之如何同时用到mod和光影
- 基于百度paddle的快递面单三段码识别
- NS2中GOD的使用
- uni-app学习之旅(二)uni-app开发规范
- Edge浏览器安装插件报错:显示出现错误 Download interrupted
热门文章
- ubuntu系统Firefox浏览器B站视频无法播放
- Windows XP IIS 500内部错误 解决方案(HTTP 500内部服务器错误)
- 对大一c语言学习的感想
- 1987年,国际C语言混乱代码大赛
- 仿QQ音乐下载歌曲头部导航
- 机器学习资源备份,转载自大神https://zhuanlan.zhihu.com/p/26876504
- 时区提示:Local time zone must be set--see zic manual page 2018的解决办法
- 短网址还原的Bookmarklet
- 学java有前途吗?学会之后有什么好处?
- MAC 网速问题 变慢 的来看看 经验