SpringCloudGateway源码(四)限流组件

news2024/11/21 0:23:22

前言

如果不使用Alibaba Sentinel的网关流控规则,

是否可以选择使用SpringCloudGateway基于Redis的限流组件?

基于这个问题,笔者想了解一下scg自带限流组件的实现原理。

一、使用案例

1、pom

注意要加入redis-reactive依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
复制代码

2、KeyResolver

实现一个KeyResolver解析限流资源key,比如针对某个请求路径、针对某个请求路径+用户等。

@Component
public class ExampleKeyResolver implements KeyResolver {
	@Override
	public Mono<String> resolve(ServerWebExchange exchange) {
		String uri = exchange.getRequest().getURI().getPath();
		HttpHeaders headers = exchange.getRequest().getHeaders();
		List<String> keys = headers.get("client_id");
		if (CollectionUtils.isEmpty(keys)) {
			return Mono.just(uri);
		}
		return Mono.just(uri + "_" + keys.get(0));
	}
}
复制代码

3、路由配置

这里通过编码方式创建一个RouteLocator配置路由。

注:也可以通过RouteDefinitionLocator配置,也可以通过yml配置。

@Bean
public RouteLocator rateLimitRouteLocator(RouteLocatorBuilder builder, ExampleKeyResolver exampleKeyResolver) {
    return builder.routes()
            // curl --header "client_id:aacc" -v localhost:8080/ratelimiter
            .route("test_rate_limit", predicateSpec -> predicateSpec
                    .path("/ratelimiter") // PathRoutePredicate匹配路径
                    .filters(
                            // RedisRateLimiter
                            gatewayFilterSpec -> gatewayFilterSpec.requestRateLimiter().rateLimiter(RedisRateLimiter.class, config -> {
                                config.setReplenishRate(1); // 令牌填充速率
                                config.setBurstCapacity(2); // 桶容量
                                config.setRequestedTokens(1); // 每次请求消耗令牌数量
                            }).configure(config -> {
                                // key解析器
                                config.setKeyResolver(exampleKeyResolver);
                            }))
                    .uri("https://www.aliyun.com")) // 转发uri
            .build();
}
复制代码

二、原理

1、自动配置

在引入redis-reactive后,开启GatewayRedisAutoConfiguration自动配置。

1)RedisScript:限流lua脚本,脚本位于META-INF/scripts/request_rate_limiter.lua;

2)RedisRateLimiter:基于redis的RateLimiter实现,底层依赖限流lua脚本;

GatewayAutoConfiguration默认提供了一个KeyResolver的实现PrincipalNameKeyResolver,基于java.security.Principal#getName获取限流资源key,在案例中我们使用ExampleKeyResolver替换了默认实现。

GatewayAutoConfiguration在RateLimiter和KeyResolver存在的情况下,注入RequestRateLimiterGatewayFilterFactory限流过滤器工厂,用于创建限流过滤器。

2、RequestRateLimiterGatewayFilterFactory

成员变量:

1)defaultRateLimiter:默认全局RateLimiter,如果针对route路由没有定制,默认是RedisRateLimiter;

2)defaultKeyResolver:默认全局KeyResolver,如果针对route路由没有定制,默认是PrincipalNameKeyResolver;

3)denyEmptyKey:是否拒绝KeyResolver解析为空key的请求,默认为true;

4)emptyKeyStatusCode:如果拒绝空key,返回http状态码,默认403Forbidden;

在ioc容器启动阶段(不同路由配置方式,加载Route时机不同),加载Route需要加载所有Route下的GatewayFilter,RequestRateLimiterGatewayFilterFactory#apply返回一个GatewayFilter。

我们重点看运行时的这个GatewayFilter的逻辑。

总体上分为两步,第一步KeyResolver解析key,第二步RateLimiter限流判断。

KeyResolver通过本次请求解析出需要限流的资源标识,比如针对uri限流,针对uri+用户限流等。

如果KeyResolver解析key为空,默认会拒绝客户端访问,返回403。

这个行为可以全局设置spring.cloud.gateway.filter.request-rate-limiter.denyEmptyKey=false修改;

也可以通过编码方式或配置文件方式针对单路由修改,比如:

KeyResolver解析出resourceKey后,代入RateLimiter的isAllowed判断,是否允许请求通过。

如果不允许通过,默认返回429状态码。

3、RedisRateLimiter

RateLimiter是允许用户自定义实现的,只需要实现isAllowed方法,看一下方法定义。

入参:

routeId=路由id,id=KeyResolver解析的resourceKey。

出参:

allowed=是否允许通过,tokensRemaining=剩余token数量,headers=加入响应头的参数。

这里我们着重分析scg提供的基于Redis的限流器RedisRateLimiter。

RedisRateLimiter核心逻辑都在lua脚本中,我们先搞清楚lua脚本的上下文逻辑。

每个资源涉及两个key:

1)request_rate_limiter.{resourceKey}.tokens:资源的令牌数量;

2)request_rate_limiter.{resourceKey}.timestamp:一个时间戳,代表上次经过rateLimiter的时间(其实是上次填充令牌桶的时间);

对resourceKey外边加了花括号,是因为如果redisTemplate底层使用官方redis-cluster,需要使用hashtag将两个key路由到同一个slot上。

lua脚本的args参数有四个:

1)replenishRate:令牌每秒填充速率;

2)burstCapacity:令牌桶最大容量;

3)Instant.now().getEpochSecond():当前时间戳;

4)requestedTokens:每次请求消耗令牌数量,认为是1即可;

script出参有两个:

1)第一个allowed:1-通过,0-不通过

2)第二个tokensLeft:剩余令牌数量

如果执行lua脚本出错,比如redis挂了,script出参降级为(1,-1),即通过。

接下来重点分析一下lua脚本:META-INF/scripts/request_rate_limiter.lua。

首先计算填满空桶的用时fill_time=桶容量/填充速率=burstCapacity/replenishRate。

此外计算一个生存时间ttl=填桶用时*2向下取整。

获取last_tokens剩余token数量,默认为桶容量。

获取last_refreshed上次令牌桶填充时间,默认为0。

计算计划令牌数量filled_tokens=未填充时间长度delta*填充速率rate+剩余token数量last_tokens,最大不超过桶总容量capacity。

如果计划令牌数量大于等于1,则allowed=true,allowed_num=1,允许通过,最终令牌数量new_tokens=计划令牌数量-1。

如果计划令牌数量小于1,则allowed=false,allowed_num=0,不允许通过,最终令牌数量new_tokens=计划令牌数量。

最后,设置两个key的value并设置ttl,返回allow_num是否通过和new_tokens最终令牌数量。

这里判断ttl>0还有个隐含逻辑,如果用户配置replenishRate:burstCapacity超过2,则这两个key根本不会存入redis,按照lua脚本逻辑每次令牌桶都是满的,请求会被直接放行

由于对lua也不懂,仅仅是凭借变量名和方法名来揣测了这个逻辑,如果要验证这个猜想,可以通过redis-cli的monitor命令监控redis客户端命令。

比如按照21填充速率+10桶容量配置:

客户端没有发送setex:

但是按照20填充速率+10桶容量配置,客户端就发送了setex,且ttl=1:

三、和Sentinel对比

Sentinel可以支持很多流量防护规则,这里仅针对网关流控规则。

Sentinel的网关限流规则适配了热点参数规则,相关文章之前分享过,源码见ParamFlowChecker#passDefaultLocalCheck。

动态更新

spc默认情况下不支持路由在运行时更新,需要做二次开发。

不过一般情况下都会对RouteLocator和RouteDefinitionLocator做一些二次开发,满足路由动态更新,限流规则顺便也能给一起做了,并不是什么难事。

Sentinel提供Dashboard支持运行时对流控配置做增删改查,开箱即用。

资源key解析

Sentinel支持多种资源key解析方式,开箱即用,比如ip、host等等,但是不支持比如spi扩展。

虽然scg不支持这么多开箱即用的key解析方式,但是可以根据业务定制逻辑,只需要实现KeyResolver即可。

阈值类型/流控效果

Sentinel阈值类型支持QPS和并发线程数,scg自带的RateLimiter仅支持QPS。

Sentinel在阈值类型为QPS的基础上,还支持设置流控效果,默认令牌桶算法快速失败,也支持漏桶算法允许排队。

scg仅支持令牌桶算法。

单机流控or集群流控

Sentinel针对SpringCloudGateway提供了网关流控规则,底层适配了热点参数流控规则,是进程级别的单机流控。Sentinel仅针对普通的流控规则提供了集群模式。

scg借助Redis实现了集群流控(令牌桶在jvm内存还是在外部集中存储的区别)。

如果KeyResolver做特殊实现,也可以支持单机流控。举个例子,在KeyResolver中加入类变量instanceId作为进程唯一标识。

但是这样做也不需要用RedisRateLimiter了,自己把lua脚本翻成java代码(比如guava的RateLimiter)实现一个RateLimiter即可。

桶容量/填充速率/时间窗口

桶容量:

Sentinel,桶容量=QPS+burstSize,其中QPS一般用户都会配置,burstSize默认是0。

scg,桶容量=burstCapacity。

填充速率:

Sentinel,填充速率=QPS。

scg,填充速率=replenishRate。

时间窗口:

Sentinel可以自由配置,默认是1秒,前一秒剩余的令牌,不会给后一秒用。

scg,时间窗口取决于ttl,而ttl=math.floor(桶容量/填充速率 * 2),只要key没有过期,桶里剩余的令牌都可以持续使用。

总结

本章分析了SpringCloudGateway自带的限流组件。

scg通过RequestRateLimiterGatewayFilterFactory#apply为每个Route路由创建一个限流过滤器GatewayFilter。

限流过滤器主要包含两个可扩展组件:

1)KeyResolver:解析资源key,比如基于uri限流、基于uri+用户id限流等,提供默认实现PrincipalNameKeyResolver,基于java.security.Principal#getName获取限流资源key;

2)RateLimiter:限流器,根据资源key判断请求是否能通过限流校验,默认提供基于Redis的实现RedisRateLimiter;

与Sentinel相比(仅针对网关流控规则),scg限流组件短板是:

1)无法在运行时动态更新,需要二次开发,但是一般用scg都有定制,这点可以忽略;

2)资源key解析多半需要用户自己实现;

3)仅支持基于QPS限流,且流控效果仅支持令牌桶算法;

4)由于底层是热点参数规则,sentinel支持针对不同参数,配置不同的限流规则,比如:user_id=1,qps=1;user_id=2,qps=2,其他user_id,qps=10,而scg只能针对user_id统一配置流控阈值;

优势是:

1)限流器RateLimiter可扩展,借助scg自带的RedisRateLimiter可实现集群流控,也可以二次开发借助三方限流器实现单机流控;

2)资源key解析可通过KeyResolver扩展;

3)可以不用引入sentinel三方组件,而redis比较常用没什么成本;

4)相较于Sentinel,scg的限流器更容易理解,简单明了;

5)RedisRateLimiter+KeyResolver可移植到需要集群限流的地方,比如网关之下的业务应用,具备通用性;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/111938.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OA系统遇到的问题

目录 一、开始时间与结束时间之差 二、弹出层的大小以及位置设置 2.1、高度设置body-style 2.2、位置设置dialogStyle 三、vue2安装引入Ant Design Vue 四、按钮控制盒子的显示与隐藏 五、表单生成器思想 5.1、点击左侧控件库生成中间的控件元素 5.2、点击中间的控件&…

Flink-状态编程的基本概念

文章目录Flink 中的状态1.1 有状态算子1.2 状态的管理1.3 状态的分类&#x1f48e;&#x1f48e;&#x1f48e;&#x1f48e;&#x1f48e; 更多资源链接&#xff0c;欢迎访问作者gitee仓库&#xff1a;https://gitee.com/fanggaolei/learning-notes-warehouse/tree/master Fli…

springcloud-gateway简介

目录 1. gateway简介 1.1 是什么 1.2 作用 1.3 主要特征 1.4 与zuul的主要区别 1.5 主要组件 1.6 架构图 2. 开发示例 2.1 创建一个gateway模块 2.2 与nacos结合使用 2.2.1 默认规则 2.2.2 通过配置文件配置路由 2.2.3 动态路由 1. gateway简介 1.1 是什么 SpringC…

一文带你深入理解【Java基础】· 网络编程(上)

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Kali Linux中安装IDLE的方法

1 IDLE简介 IDLE是Integrated Development and Learning Enviroment即集成开发和学习环境的简称&#xff0c;是Python的集成开发环境。在Kali Linux中&#xff0c;可以通过IDLE进行Python编程。 2 Kali Linux中安装IDLE 2.1 查看Kali Linux中是否安装IDLE 在Kali Linux终端…

WEB1.0起源:全球首个网站info.cern.ch

伯纳斯李&#xff08;图&#xff09;1990年创立第一个网站。 info.cern.ch是世上第一个网站&#xff0c;提供有关万维网的资料。 info.cern.ch这个网站依然运作如常。 英国科学家蒂姆伯纳斯-李 (Tim Berners-Lee) 于 1989 年在 CERN 工作期间发明了万维网 (WWW)。Web 最初的构思…

基于Vue+SpringBoot智慧校园疫情防控系统(PC端、手机端)--附源码

介绍 智慧校园疫情防控系统——PC 手机端 多端并行 项目源码下载&#xff1a;https://download.csdn.net/download/DeepLearning_/87340321 软件架构 手机端信息系统——日常健康信息填报系统&#xff08;前端手机端 文件夹&#xff09;电脑端智疫图 —— 数据可视化界面 &…

一种新的语义分割思路

这两天看到一篇挺有意思的论文&#xff0c;虽然不是语义分割方面的但是挺有意思的&#xff0c;因此在这里跟大家分享一下&#xff0c;这个也是一种语义分割的思路和方法。 Paper&#xff1a;Fully-Convolutional Siamese Networks for Object Tracking. SiamFC是深度学习目标…

【深入浅出 Yarn 架构与实现】4-2 RM 管理 Application Master

上一篇文章对 ResourceManager 整体架构和功能进行了讲述。本篇将对 RM 中管理 Application Master 的部分进行深入的讲解。 下面将会介绍 RM 与 AM 整体通信执行流程&#xff0c;并对 RM 中涉及的对应服务进行具体讲解。 为了更好的学习本篇知识&#xff0c;建议先熟悉以下知识…

股票量化分析工具QTYX使用攻略——实盘交易信号监控(更新2.5.7)

搭建自己的量化系统如果要长期在市场中立于不败之地&#xff01;必须要形成一套自己的交易系统。如何学会搭建自己的量化交易系统&#xff1f;边学习边实战&#xff0c;在实战中学习才是最有效地方式。于是我们分享一个即可以用于学习&#xff0c;也可以用于实战炒股分析的量化…

3天学会撰写软件发明专利——5.专利法律常识

“无意中发现了一个巨牛的人工智能教程&#xff0c;忍不住分享一下给大家。教程不仅是零基础&#xff0c;通俗易懂&#xff0c;而且非常风趣幽默&#xff0c;像看小说一样&#xff01;觉得太牛了&#xff0c;所以分享给大家。点这里可以跳转到教程。”。 1)假冒专利行为及其法…

Activemq的Broker

目录 一、broker是什么 二、启动broker时指定配置文件 三、嵌入式Broker &#xff08;一&#xff09;Pom.xml &#xff08;二&#xff09;Broker实例 &#xff08;三&#xff09;验证 一、broker是什么 相当于一个ActiveMQ服务器实例。说白了&#xff0c;Broker其实就是…

YK-L1刷机

文章目录1.测试是否能够连接到路由器2.刷breed3.Padavan firmware编译4.烧板5.验证杂文1.1内核模块编写&#xff08;使用insmod方式&#xff09;1.2内核模块编写&#xff08;跟随内核一起编译&#xff09;参考资料1.测试是否能够连接到路由器 插上网线和电脑相连&#xff0c;网…

计算机网络基础——一文详解IPv4与子网划分

IPv4地址概述 在因特网中&#xff0c;为了实现计算机之间的相互通信&#xff0c;通常需要为每台计算机分配一个IP地址。在互联网的发展过程中主要有两个版本的互联网协议&#xff0c;分别是IPv4(Internet Protocol version4)和IPv6 (Internet Protocol version 6) IPv4的IP地址…

SpringBoot:模块探究之spring-boot-starters

Spring Boot Starters 是一组方便的依赖描述符&#xff0c;您可以将它们包含在您的应用程序中。您可以获得所需的所有 Spring 和相关技术的一站式服务&#xff0c;而无需搜索示例代码和复制粘贴大量依赖项描述符。 例如&#xff0c;如果想使用 Spring 和 JPA 进行数据库访问&am…

SpringBoot整合Redis实现几种自定义数据序列化存储方式

JDK自带序列化方式 在Java中RedisTemplete提供了统一的API来操作Redis&#xff0c;比如插入一条String类型的数据&#xff0c;我可以用 redisTemplate.opsForValue().set("name", "美羊羊"); SpringDataRedis可以接收任何类型的对象并将其转成Redis可以处…

小布助手,身入大千世界

在2018年—2019年&#xff0c;AI智能助手一度火热&#xff0c;成了科技行业的全新风口。智能音箱与手机中&#xff0c;我们能看到各种各样的智能助手横空出世&#xff0c;一度成为产品标配。但随着时间缓缓冲刷&#xff0c;就像所有科技风口一样&#xff0c;有的AI智能助手随着…

一种非侵入式幂等性的Java实现

今天我们来谈谈什么是幂等性&#xff1f; 引用百度百科的解析如下&#xff1a; 幂等&#xff08;idempotent、idempotence&#xff09;是一个数学与计算机学概念&#xff0c;常见于抽象代数中。 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同…

3ds Max:标准几何体

三维软件中一般有许多非常复杂的命令&#xff0c;能够完成非常复杂的图形运算&#xff0c;但其实许多绚丽的图形也是由最基本的几何体构成&#xff0c;许多复杂的命令也是基本的运算程序的集合&#xff0c;就像是砖块&#xff0c;构成了复杂的大厦。任何一个几何体&#xff0c;…

【QGIS入门实战精品教程】3.4:QGIS创建、连接、打包GeoPackage数据库及数据入库案例详解

GeoPackage(以下简称gpkg),内部使用SQLite实现的一种单文件、与操作系统无关的地理数据库。在QGIS中可以很方便的实现GeoPackage的创建与连接等操作。 文章目录 一、QGIS创建GeoPackage1. 创建数据库2. 数据入库二、矢量数据打包为GeoPackage1. 加载shp文件2. 使用QGIS打包图…