-
2022-04-04 15:27:46
内核版本:2.6.38
此函数的功能是:限制在一定时间间隔内的打印次数。
printk_ratelimit()
include/linux/printk.h
#define printk_ratelimit() __printk_ratelimit(__func__)
__printk_ratelimit(__func__)
kernel/printk.c
/* * printk rate limiting, lifted from the networking subsystem. * * This enforces a rate limit: not more than 10 kernel messages * every 5s to make a denial-of-service attack impossible. */ // 就是5秒内最多打印10次 DEFINE_RATELIMIT_STATE(printk_ratelimit_state, 5 * HZ, 10); int __printk_ratelimit(const char *func) { return ___ratelimit(&printk_ratelimit_state, func); } EXPORT_SYMBOL(__printk_ratelimit);
关于 DEFINE_RATELIMIT_STATE,定义在:./include/linux/ratelimit.h
#define DEFINE_RATELIMIT_STATE(name, interval_init, burst_init) \ \ struct ratelimit_state name = { \ .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ .interval = interval_init, \ .burst = burst_init, \ }
struct ratelimit_state 数据类型
struct ratelimit_state { raw_spinlock_t lock; /* protect the state */ int interval; int burst; int printed; int missed; unsigned long begin; unsigned long flags; };
___ratelimit
lib/ratelimit.c
/* * __ratelimit - rate limiting * @rs: ratelimit_state data * @func: name of calling function * * This enforces a rate limit: not more than @rs->burst callbacks * in every @rs->interval * * RETURNS: * 0 means callbacks will be suppressed. * 1 means go ahead and do it. */ int ___ratelimit(struct ratelimit_state *rs, const char *func) { unsigned long flags; int ret; if (!rs->interval) return 1; /* * If we contend on this state's lock then almost * by definition we are too busy to print a message, * in addition to the one that will be printed by * the entity that is holding the lock already: */ if (!spin_trylock_irqsave(&rs->lock, flags)) return 0; if (!rs->begin) rs->begin = jiffies; if (time_is_before_jiffies(rs->begin + rs->interval)) { if (rs->missed) printk(KERN_WARNING "%s: %d callbacks suppressed\n", func, rs->missed); rs->begin = 0; rs->printed = 0; rs->missed = 0; } if (rs->burst && rs->burst > rs->printed) { rs->printed++; ret = 1; } else { rs->missed++; ret = 0; } spin_unlock_irqrestore(&rs->lock, flags); return ret; } EXPORT_SYMBOL(___ratelimit);
更多相关内容 -
ratelimit
2021-03-21 23:33:01限速 -
Go-ratelimit使用Redis来Ratelimit限制你的方法
2019-08-14 02:43:03ratelimit 使用Redis来Ratelimit限制你的方法 -
koa2-ratelimit:Koa2 ES6的限速中间件。 用于限制对API和/或端点的重复请求,例如密码重置
2021-02-04 05:58:26Koajs 2速率限制(Bruteforce) 限速中间件Koa2与async await 。... 注意:此模块基于并适用于... const RateLimit = require ( 'koa2-ratelimit' ) . RateLimit ; const limiter = RateLimit . middleware ( { interva -
RateLimit-使用guava来做接口限流代码示例
2020-08-28 05:20:39主要介绍了RateLimit-使用guava来做接口限流代码示例,具有一定借鉴价值,需要的朋友可以参考下 -
asgi-ratelimit:ASGI中间件以进行速率限制
2021-05-01 02:00:10ASGI RateLimit 限制用户访问频率。 基于ASGI。 100%覆盖率。 高性能。 支持常规匹配。 可定制的。 安装 # Only install pip install asgi-ratelimit # Use redis pip install asgi-ratelimit[redis] # Use jwt ... -
ratelimit-service:简单的限速路由服务
2021-05-20 21:48:18$ git clone https://github.com/cloudfoundry-samples/ratelimit-service.git 以下示例使用的是 您想要速率限制的应用程序在上运行,并在运行时上运行。 安装 部署速率限制器应用 $ cd ratel -
ratelimit-令牌桶ratelimiter-Rust开发
2021-05-27 20:12:03ratelimit-锈率限制的令牌桶速率限制器提供了一个令牌桶速率限制器,该令牌桶速率限制器可以由单个线程使用,或者跨t ratelimit共享。锈率限制的令牌桶速率限制器提供了一个令牌桶速率限制器,该令牌桶速率限制器... -
ratelimit:一个用于 go 的速率限制库
2021-07-02 02:19:55速率限制器 跟踪操作是否超过某个阈值的通用方法,以 #/秒为单位。 该库是通用的,但明显的例子是限制用户可以发出的请求数量... tracker * ratelimit. Tracker } // whenever an action is taken that you want to l -
ratelimit:Golang阻止漏斗率限制实施
2021-05-07 02:34:29限速器 该软件包提供了泄漏桶速率... rl := ratelimit . New ( 100 ) // per second prev := time . Now () for i := 0 ; i < 10 ; i ++ { now := rl . Take () fmt . Println ( i , now . Sub ( prev )) pr -
harmonyos2-koa-ratelimit-lru:lru-cache支持的速率限制器中间件
2021-07-01 15:13:45koa-ratelimit-lru 由 lru-cache 支持的用于 koa@2 的速率限制器中间件 Koa2 中间件使用async/await ,因此您必须使用--harmony_async_await或使用babel在 Node.js >= 7.0.0 中运行 安装 $ npm install koa - ... -
Go-ratelimit基于令牌桶算法和漏桶算法来实现的限速限流Golang实现
2019-08-14 03:39:03ratelimit 基于令牌桶算法和漏桶算法来实现的限速限流,Golang实现 -
koa-simple-ratelimit:Koa.js v2 Web框架的简单速率限制器
2021-02-03 17:34:47koa-simple-ratelimit Koa v2的速率限制器中间件。 与在于不依赖于并使用redis ttl(生存时间)来处理剩余的到期时间。 这将在redis中仅创建一个条目,而不是node-ratelimiter所创建的三个条目。 安装 npm install ... -
connect-ratelimit:连接中间件以限制每个 iphostname 对 node.js 服务器的请求
2021-07-12 08:46:43var limiter = require ( 'connect-ratelimit' ) ; app = connect ( ) . use ( limiter ( { whitelist : [ '127.0.0.1' ] , blacklist : [ 'example.com' ] } ) ) . use ( function ( req , res ) { res . ... -
caddy-ratelimit:Caddy 2的HTTP速率限制模块
2021-04-01 15:37:20达到指定的速率限制后,可以拒绝请求。 请注意,该模块仍未完成,可能存在错误。 请尝试一下并提交错误报告,谢谢! 特征 多个限速区 滑动窗算法 可扩展的环形缓冲区实现 ...Goroutines:1(清理旧缓冲区) ... -
FireflySoft.RateLimit 使用
2022-05-30 08:48:01FireflySoft.RateLimit 使用限流处理器
使用FireflySoft.RateLimit首先需要创建一个限流处理器RateLimitProcessor的实例,然后用它来过滤每一个请求。创建RateLimitProcessor的实例,至少需要提供两个信息:被限流的请求类型、限流算法和规则。其它还有限流计数持久化方式、限流错误信息,如果不提供会使用默认值。这里先不展开,后边会详细介绍它们。创建RateLimitProcessor需要采用构造器模式,如果你使用ASP.NET Core应该很熟悉,如果没什么印象打开Program.cs就可以看到了。为了提高效率,这个限流处理器一般会统一放在某个过滤器中,这个过滤器在ASP.NET中可以是一个消息处理器,在ASP.NET Core中一般就是一个中间件。为了方便集成,FireflySoft.RateLimit已经提供了一个基于ASP.NET的消息处理器和一个基于ASP.NET Core的中间件,通过Github和Nuget都可以很方便的找到他们,Github上还提供了演示程序.
被限流请求类型
被限流请求类型就是需要被限制的业务请求的类型,在ASP.NET中他可能是HttpRequestMessage,在ASP.NET Core中它可能是HttpContext,在其它业务中它可能是你自定义的一个类型XXXRequest。被限流请求类型很重要,因为不同的业务可能使用不同的请求类型,而限流处理程序需要从当前请求中提取或关联到限流目标,明确请求类型才能确定提取方法,否则对请求进行限制将无所适从
限流算法和规则
FireflySoft.RateLimit提供了四种常见的限流算法:固定窗口、滑动窗口、漏桶、令牌桶。固定窗口相对最简单,无论是算法的时间复杂度还是分布式环境实现难度都比较有优势,如果需求是在较短的时间内进行限制,比如每秒限制多少次,使用这种算法是最合适的。但是实际场景中请求在时间分布中可能不太均匀,时多时少,根据需求的不同,可能需要选择其它三种限流算法,这里不做说明了,网上已经有很多的场景选择说明。 初始化限流算法还需要提供对应的限流规则,因为不同的算法往往需要不同的参数,这里很难对不同的算法提供完全统一的限流规则,不过这些规则确实是有共同设置项的,比如限流锁定时间、目标提取方法、是否应用限流处理的判断方法等。 以上文【使用方法】中的限流代码为例,做一些介绍:
-
FixedWindowAlgorithm是此限流组件提供的固定窗口限流算法。
-
FixedWindowRateLimitRule是此限流组件提供的固定窗口限流规则类型;
-
HttpContext是业务中需要限制的请求类型,这里是Http请求上下文类型;
-
StatWindow是固定窗口的大小,是一个时间跨度;
-
LimitNumber是限流值,在StatWindow时间内请求数超过它就会触发限流;
-
LockSeconds是触发限流后的锁定时间,此时间内请求都会被阻止,不需要锁定时可以不设置;
-
ExtractTarget传递一个方法用于从请求中提取限流目标,这里是用户的每一个请求路径;
-
CheckRuleMatching传递一个方法用于检查当前请求是否需要限流检查,这里return true代表所有请求都要经过限流检查。
这里有两个比较有意思的设置:
ExtractTarget
和CheckRuleMatching
,他们共同作用,让用户可以完全自由的定制自己限流的目标和条件,不固定是IP、ClientId或者Url。其它算法规则中每个设置项的含义可以通过其注释了解到。 如果这几个算法还不能满足要求,可以通过实现IRateLimitAlgorithm来定义一个新的算法。计数持久化方式
FireflySoft.RateLimit中的限流计数目前支持保存在内存或者Redis中,也可以通过实现IRateLimitStorage来定义一个新的存储器。 对于只需要部署一份的程序,绝大部分情况下使用内存就够了;但是如果限流的时间窗口比较长,比如1小时限制300次,重启就会丢失计数,这可能是个风险,此时使用Redis会比较合适。 对于需要部署多份的程序,如果请求是基本均匀的,并且在每个部署之间的分配也是均匀的,那么使用内存存储器也未尝不可,将总的限流数平均分配在每一个部署中,只要有一个部署触发限流,其它部署很大几率上也会触发限流。但是对于需要部署多份的情况,采用更持久的Redis方式才是稳妥的。 相比内存访问,Redis访问需要网络交互,这会造成一定的性能损失,访问量很大时也会产生拥堵,不过也可以将请求分散到多个Redis的方式进行缓解;同时分布式环境下数据一致性的实现难度更大,即使使用Redis,比如限流处理中必然会涉及的各种时间,不同节点之间的时间不可能绝对一致,越短的时间窗口协调难度越大。因此使用滑动窗口、漏桶、令牌桶等分布式实现难度较大的算法时,需要注意时间单位的设置.
限流错误信息
限流错误信息是触发限流时限流检查结果中附带的信息,目前每个限流处理器允许设置一个统一的RateLimitError,方便业务侧进行统一处理。默认限流错误Code是429,Message为null。你也可以不使用这个定义的值,根据当前限流目标和限流规则构造自己的错误信息
限流锁定
限流锁定是触发限流时的惩罚性处理。FireflySoft.RateLimit通过设置限流规则中的LockSeconds,定义触发限流后的锁定时间,此时间内的请求都会被认定触发限流规则,而不被允许通过;如果不需要锁定忽略这个设置就可以了
如上图所示,时间单位是1秒,阈值是3。
-
第1秒3个请求,不会触发限流;
-
第2秒1个请求,不会触发限流;
-
第3秒4个请求,这一秒的前3个请求正常处理,第4个请求触发限流,会被拒绝处理。
-
后续第4秒、第5秒不会触发限流,所有请求正常处理。
Core、AspNet、Middleware 3种
Github 源码地址
GitHub - bosima/FireflySoft.RateLimit: It is a rate limiting library based on .Net standard.
Project Descriptioin FireflySoft.RateLmit.Core FireflySoft.RateLmit的算法、规则、持久化和其它核心代码。 FireflySoft.RateLimit.AspNet 为基于 .NET Framework的ASP.NET提供的限流组件。 FireflySoft.RateLimit.AspNetCore ASP.NET Core限流中间件。 FireflySoft.RateLimit.Core.UnitTest Unit test for FireflySoft.RateLimit.Core. FireflySoft.RateLimit.Core.BenchmarkTest Benchmark test for FireflySoft.RateLimit.Core. samples/console FireflySoft.RateLmit.Core sample program. samples/aspnet FireflySoft.RateLimit.AspNet sample program. samples/aspnetcore FireflySoft.RateLimit.AspNetCore sample program. samples/aspnetcore6 FireflySoft.RateLimit.AspNetCore with .NET6 sample program. ASP.NET Core 使用方法:
1、Install Nuget Package Package Manager: Install-Package FireflySoft.RateLimit.AspNetCore Or .NET CLI: dotnet add package FireflySoft.RateLimit.AspNetCore
Use Middleware 使用方法:
public void ConfigureServices(IServiceCollection services) { ...
services.AddRateLimit(new InProcessFixedWindowAlgorithm( new[] { new FixedWindowRule() { ExtractTarget = context => { return (context as HttpContext).Request.Path.Value; }, CheckRuleMatching = context => { return true; }, Name="default limit rule", LimitNumber=30, StatWindow=TimeSpan.FromSeconds(1) } }) ); ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ...
app.UseRateLimit(); ...
}
ASP.NET
1、Install Nuget Package:
Install-Package FireflySoft.RateLimit.AspNet
2、Register MessageHandler
Open Global.asax.cs, the following code adds the rate limit message handle:
protected void Application_Start() { ...
GlobalConfiguration.Configuration.MessageHandlers.Add( new RateLimitHandler( new Core.InProcessAlgorithm.InProcessFixedWindowAlgorithm( new[] { new FixedWindowRule() { ExtractTarget = context => { return (context as HttpRequestMessage).RequestUri.AbsolutePath; }, CheckRuleMatching = context => { return true; }, Name="default limit rule", LimitNumber=30, StatWindow=TimeSpan.FromSeconds(1) } }) )); ...
}
-
-
koa-better-ratelimit:使用“ koa-ip-filter”,基于“ koa”构建的API的智能便捷请求速率限制器。...
2021-02-03 14:54:30koa-better-ratelimit:使用“ koa-ip-filter”,基于“ koa”构建的API的智能便捷请求速率限制器。 支持自定义存储,自定义ID,自定义错误消息和自定义标头 -
ratelimit:Smyte的高性能速率限制器
2021-05-26 11:33:34限速 不再维护:请注意,该代码不再处于主动维护状态。 用C ++编写的高性能速率限制器,使用Redis协议。 请参阅我们的以了解更多信息。 ...示例: ./bazel-bin/ratelimit/ratelimit --rocksdb_db_ -
swift-RateLimit简单的工具仅用于每隔一段时间执行代码
2019-08-15 06:43:14RateLimit:简单的工具仅用于每隔一段时间执行代码 -
ratelimit:API速率限制装饰器
2021-04-29 18:34:35限速 API是与Web服务进行交互的一种非常... ratelimit 然后执行: $ pip install -r requirements.txt 或自己安装: $ pip install ratelimit 的GitHub 从Github安装最新版本: $ git clone https://github.co -
rack-ratelimit:灵活的Rack应用速率限制
2021-05-25 01:44:59Rack :: Ratelimit 在单个应用程序中运行多个速率限制器 将每个速率限制范围限制为某些请求:API,文件,GET与POST等。 根据请求特征应用每个速率限制:IP,子域,OAuth2令牌等。 灵活的时间窗口可限制突发流量与... -
ratelimit+redis+lua对接口限流
2021-11-27 10:19:41背景:为防止接口QPS太大而造成系统运行卡顿的现象,在这儿以ratelimit+redis+lua对系统接口做了个限流。当时也考虑过使用其他的限流方法,比如微服务生态中使用的sentinel中间件,但是这个如果要实现持久化要进行...背景:为防止接口QPS太大而造成系统运行卡顿的现象,在这儿以ratelimit+redis+lua对系统接口做了个限流。当时也考虑过使用其他的限流方法,比如微服务生态中使用的sentinel中间件,但是这个如果要实现持久化要进行特殊的配置,比如使用nacos进行持久化,需要修改sntinel源码,相比较而言单纯为了限流儿集成两个中间件会显得比较臃肿,所以到最后还是使用了retelimit+redis+lua这个方案,本身redis系统中就会使用,存储token、部门信息等一些读取次数多的数据。
一、主要逻辑实现:
- 首先确定的是要采用切面的方式,后期如果相对某一个接口进行限流可以直接采用注解的方式。
- 其二在redis存储的key的名称要以方法名+ip的方式,这样可以更好的实现思路1指出的问题。
- 使用lua脚本直接传到redis中操作,这样可以减少网络开销以及复用,并且可以保证是原子操作。
- 第四点就是lua脚本的编写啦,redis 以有序队列进行存储,每一个key值都带有当前得分为当前时间戳的元素,每次新增的的时候都会将过时的元素进行清理,并进行判断是否达到限流条件。
二、代码实现:
代码结构:
限流注解接口类
package com.heyu.ratelimit.annotation; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AnnotationUtils; import java.lang.annotation.*; import java.util.concurrent.TimeUnit; /** * <p> * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 * </p> * * @author: 程鹏 * @date: 2021-02-24 14:45 * @Description: 限流切面 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimiter { long DEFAULT_REQUEST = 5; /** * max 最大请求数 */ @AliasFor("max") long value() default DEFAULT_REQUEST; /** * max 最大请求数 */ @AliasFor("value") long max() default DEFAULT_REQUEST; /** * 限流key */ String key() default ""; /** * 超时时长,默认1分钟 */ long timeout() default 1; /** * 超时时间单位,默认 分钟 */ TimeUnit timeUnit() default TimeUnit.MINUTES; }
切面操作类
package com.heyu.ratelimit.aspect; import cn.hutool.core.util.StrUtil; import com.heyu.ratelimit.annotation.RateLimiter; import com.heyu.ratelimit.util.IpUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.time.Instant; import java.util.Collections; import java.util.concurrent.TimeUnit; /** * @author: 程鹏 * @date: 2021-02-24 14:13 * @Description: 限流切面 */ @Slf4j @Aspect @Component @RequiredArgsConstructor(onConstructor_ = @Autowired) public class RateLimiterAspect { private final static String SEPARATOR = ":"; private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; private final StringRedisTemplate stringRedisTemplate; private final RedisScript<Long> limitRedisScript; @Pointcut("@annotation(com.heyu.ratelimit.annotation.RateLimiter)") public void rateLimit() { } @Around("rateLimit()") public Object pointcut(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); if (rateLimiter != null) { String key = rateLimiter.key(); // 默认用类名+方法名做限流的 key 前缀 if (StrUtil.isBlank(key)) { key = method.getDeclaringClass().getName() + StrUtil.DOT + method.getName(); } // 最终限流的 key 为 前缀 + IP地址 // TODO: 此时需要考虑局域网多用户访问的情况,因此 key 后续需要加上方法参数更加合理 key = key + SEPARATOR + IpUtil.getIpAddr(); long max = rateLimiter.max(); long timeout = rateLimiter.timeout(); TimeUnit timeUnit = rateLimiter.timeUnit(); boolean limited = shouldLimited(key, max, timeout, timeUnit); if (limited) { throw new RuntimeException("手速太快了,慢点儿吧~"); } } return point.proceed(); } private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) { // 最终的 key 格式为: // limit:自定义key:IP // limit:类名.方法名:IP key = REDIS_LIMIT_KEY_PREFIX + key; // 统一使用单位毫秒 long ttl = timeUnit.toMillis(timeout); // 当前时间毫秒数 long now = Instant.now().toEpochMilli(); long expired = now - ttl; // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); if (executeTimes != null) { if (executeTimes == 0) { log.error("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max); return true; } else { log.info("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes); return false; } } return false; } }
redis配置类
package com.heyu.ratelimit.config; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Component; /** * @author: 程鹏 * @date: 2021-02-26 14:35 * @Description: */ @Component public class RedisConfig { @Bean @SuppressWarnings("unchecked") public RedisScript<Long> limitRedisScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/limit.lua"))); redisScript.setResultType(Long.class); return redisScript; } }
Ip解析类
package com.heyu.ratelimit.util; import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * @author: 程鹏 * @date: 2021-02-26 14:28 * @Description: */ @Slf4j public class IpUtil { private final static String UNKNOWN = "unknown"; private final static int MAX_LENGTH = 15; /** * 获取IP地址 * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址 */ public static String getIpAddr() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String ip = null; try { ip = request.getHeader("x-forwarded-for"); if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StrUtil.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } catch (Exception e) { log.error("IPUtils ERROR ", e); } // 使用代理,则获取第一个IP地址 if (!StrUtil.isEmpty(ip) && ip.length() > MAX_LENGTH) { if (ip.indexOf(StrUtil.COMMA) > 0) { ip = ip.substring(0, ip.indexOf(StrUtil.COMMA)); } } log.error("访客ip:"+ip); return ip; } }
lua脚本
-- 下标从 1 开始 local key = KEYS[1] local now = tonumber(ARGV[1]) local ttl = tonumber(ARGV[2]) local expired = tonumber(ARGV[3]) -- 最大访问量 local max = tonumber(ARGV[4]) -- 清除过期的数据 -- 移除指定分数区间内的所有元素,expired 即已经过期的 score -- 根据当前时间毫秒数 - 超时毫秒数,得到过期时间 expired redis.call('zremrangebyscore', key, 0, expired) -- 获取 zset 中的当前元素个数 local current = tonumber(redis.call('zcard', key)) local next = current + 1 if next > max then -- 达到限流大小 返回 0 return 0; else -- 往 zset 中添加一个值、得分均为当前时间戳的元素,[value,score] redis.call("zadd", key, now, now) -- 每次访问均重新设置 zset 的过期时间,单位毫秒 redis.call("pexpire", key, ttl) return next end
controller层测试
-
限流框架 - RateLimit(深入篇)
2020-05-22 17:07:20} add-response-headers true/false 默认true,是否添加响应头 X-RateLimit-Limit: 60//每秒60次请求 X-RateLimit-Remaining: 23//当前还剩下多少次 X-RateLimit-Reset: 1540650789//限制重置时间 key-prefix ...1.常用限流算法
1.1计数器法
计数器是最简单的限流算法,思路是维护一个单位时间内的计数器 Counter,如判断单位时间已经过去,则将计数器归零。
我们假设有个需求对于某个接口 /query 每分钟最多允许访问 200 次。
- 可以在程序中设置一个变量 count,当过来一个请求我就将这个数 +1,同时记录请求时间。
- 当下一个请求来的时候判断 count 的计数值是否超过设定的频次,以及当前请求的时间和第一次请求时间是否在 1 分钟内。
- 如果在 1 分钟内并且超过设定的频次则证明请求过多,后面的请求就拒绝掉。
- 如果该请求与第一个请求的间隔时间大于 1 分钟,且 count 值还在限流范围内,就重置 count。
1.2滑动窗口
滑动窗口(Sliding window)(https://en.wikipedia.org/wiki/Sliding_window_protocol) 是一种流量控制技术,这个词出现在 TCP 协议中。我们来看看在限流中它是怎样表现的:
上图中我们用红色的虚线代表一个时间窗口(一分钟),每个时间窗口有 6 个格子,每个格子是 10 秒钟。每过 10 秒钟时间窗口向右移动一格,可以看红色箭头的方向。我们为每个格子都设置一个独立的计数器 Counter,假如一个请求在 0:45 访问了那么我们将第五个格子的计数器 +1(也是就是 0:40~0:50),在判断限流的时候需要把所有格子的计数加起来和设定的频次进行比较即可。
我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。
由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
1.3漏桶算法
从图中我们可以看到,整个算法其实十分简单。首先,我们有一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。
我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。
漏桶算法有以下特点:
- 漏桶具有固定容量,出水速率是固定常量(流出请求)
- 如果桶是空的,则不需流出水滴
- 可以以任意速率流入水滴到漏桶(流入请求)
- 如果流入水滴超出了桶的容量,则流入的水滴溢出(新请求被拒绝)
漏桶限制的是常量流出速率(即流出速率是一个固定常量值),所以最大的速率就是出水的速率,不能出现突发流量。
1.4令牌桶算法
令牌桶算法和漏桶算法的方向刚好是相反的,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以 一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。
令牌桶有以下特点:
- 令牌按固定的速率被放入令牌桶中
- 桶中最多存放 B 个令牌,当桶满时,新添加的令牌被丢弃或拒绝
- 如果桶中的令牌不足 N 个,则不会删除令牌,且请求将被限流(丢弃或阻塞等待)
令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量。
1.5 小结
计数器和滑动窗口比较
计数器算法实现起来最简单,可以看成是滑动窗口的低精度实现。滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。
漏桶算法和令牌桶算法比较
漏桶
令牌桶
何时拒绝请求 流入请求速率任意,以固定的速率流出请求,流入请求数超过漏桶容量,拒绝请求 以固定速率往桶中添加令牌,桶中无令牌则拒绝请求 速率限制 限制常量流出速率,从而平滑突发流入速率 限制平均流入速率,允许一定程度的突发请求(允许一次拿多个令牌) 2.ratelimit总览
开启zuul服务限流的组件,包含五种内置的限流方式:
限流方式
说明
Authenticated User 使用经过身份验证的用户名或“anonymous匿名” public
String getUser(
final
HttpServletRequest request) {
return
request.getRemoteUser() !=
null
? request.getRemoteUser() : ANONYMOUS_USER;
}
Request Origin 使用用户原始请求(通过客户端IP地址区分) public
String getRemoteAddress(
final
HttpServletRequest request) {
String xForwardedFor = request.getHeader(X_FORWARDED_FOR_HEADER);
if
(properties.isBehindProxy() && xForwardedFor !=
null
) {
return
xForwardedFor.split(X_FORWARDED_FOR_HEADER_DELIMITER)[
0
].trim();
}
return
request.getRemoteAddr();
}
URL 使用服务的请求路径 ROLE 使用经过身份验证的用户角色 Request method 使用HTTP请求方法 Global configuration per service 这个不验证请求Origin,Authenticated User或URI,要使用这个,请不要设置type 只需向列表中添加多个值,就可以将经过身份验证的用户、请求源、URL、角色和请求方法组合在一起
3.用法
添加ratelimit依赖
<dependency> <groupId>com.marcosbarbero.cloud</groupId> <artifactId>spring-cloud-zuul-ratelimit</artifactId> <version>LATEST</version> </dependency>
使用数据存储不同,则需引入不同依赖
Redis:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
Consul:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul</artifactId> </dependency>
Spring Data JPA:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Bucket4j JCache:
<dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-core</artifactId> </dependency> <dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-jcache</artifactId> </dependency> <dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> </dependency>
Bucket4j Hazelcast (depends on Bucket4j JCache):
<dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-hazelcast</artifactId> </dependency> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast</artifactId> </dependency>
Bucket4j Infinispan (depends on Bucket4j JCache):
<dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-infinispan</artifactId> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-core</artifactId> </dependency>
Bucket4j Ignite (depends on Bucket4j JCache):
<dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-ignite</artifactId> </dependency> <dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-core</artifactId> </dependency>
配置示例:
zuul: ratelimit: key-prefix: your-prefix #限流key前缀 enabled: true #是否启用限流 repository: REDIS #使用何种方式存储数据 behind-proxy: true add-response-headers: true default-policy-list: #optional - will apply unless specific policy exists 默认策略 (60s内超过10次或请求时间累积超过1000s触发限流) - limit: 10 #optional - request number limit per refresh interval window 单位时间内请求次数限制 quota: 1000 #optional - request time limit per refresh interval window (in seconds) 单位时间内累计请求时间限制(秒) refresh-interval: 60 #default value (in seconds) 限流时间窗口,默认60s type: #optional 限流方式 - user - origin - url - httpmethod policy-list: # 自定义策略 myServiceId: #本例配置:60s内超过10次,请求时间累积超过1000s触发限流 - limit: 10 #optional - request number limit per refresh interval window 单位时间内请求次数限制 quota: 1000 #optional - request time limit per refresh interval window (in seconds) 单位时间内累计请求时间限制(秒) refresh-interval: 60 #default value (in seconds) 限流时间窗口,默认60s type: #optional 限流方式 - user - origin - url - type: #optional value for each type - user=anonymous - origin=somemachine.com - url=/api #url prefix - role=user - httpmethod=get #case insensitive
4.多种实现
实现
说明
Memory
基于本地内存,默认,使用currentHashMap存储key值 redis 基于redis,使用时必须引入redis相关依赖 JPA 基于SpringDataJPA,需要用到数据库 consul 基于consul BUKET4J 使用一个Java编写的基于令牌桶算法的限流库 1.7.1.RELEASE
2.2.6.RELEASE
5.常用配置解读
zuul.ratelimit. :配置项
配置项
可选项
说明
enabled Boolean 是否启用限流 behind-proxy true/false 默认false,是否是代理之后的请求,type=origin,影响ip取值 public
String getRemoteAddress(
final
HttpServletRequest request) {
String xForwardedFor = request.getHeader(X_FORWARDED_FOR_HEADER);
if
(properties.isBehindProxy() && xForwardedFor !=
null
) {
return
xForwardedFor.split(X_FORWARDED_FOR_HEADER_DELIMITER)[
0
].trim();
}
return
request.getRemoteAddr();
}
add-response-headers true/false 默认true,是否添加响应头 X-RateLimit-Limit: 60//每秒60次请求
X-RateLimit-Remaining: 23//当前还剩下多少次
X-RateLimit-Reset: 1540650789//限制重置时间
key-prefix String 限流key前缀(默认${spring.application.name:rate-limit-application}) repository CONSUL(K/V存储), REDIS, JPA, BUCKET4J_JCACHE, BUCKET4J_HAZELCAST, BUCKET4J_INFINISPAN, BUCKET4J_IGNITE, IN_MEMORY 限流数据的存储方式,默认是:IN_MEMORY(内存) BUCKET4J 基于令牌算法 default-policy list-of-policy 默认策略 policy-list Map of Lists of Policy 自定义策略 postFilterOrder int postFilter(后置)过滤顺序 FilterConstants.SEND_RESPONSE_FILTER_ORDER - 10 preFilterOrder int preFilter(前置)过滤顺序 FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER
policy(策略配置项):
配置项
说明
默认值
limit 刷新窗口期 限流调用次数阈值 quota 刷新窗口期 所有的请求的总时间 限制(秒) refresh-interval
刷新窗口期 60s type 限流方式:ORIGIN, USER, URL,ROLE,HTTP_METHOD 6.自定义生成key
@RequiredArgsConstructor public class CustomRateLimitKeyGenerator implements RateLimitKeyGenerator { private final RateLimitProperties properties; private final RateLimitUtils rateLimitUtils; @Override public String key(final HttpServletRequest request, final Route route, final Policy policy) { final List<Type> types = policy.getType().stream().map(MatchType::getType).collect(Collectors.toList()); final StringJoiner joiner = new StringJoiner(":"); joiner.add(properties.getKeyPrefix()); if (route != null) { joiner.add(route.getId()); } if (!types.isEmpty()) { if (types.contains(Type.URL) && route != null) { joiner.add(route.getPath()); } if (types.contains(Type.ORIGIN)) { joiner.add(rateLimitUtils.getRemoteAddress(request)); } if (types.contains(Type.USER)) { joiner.add(rateLimitUtils.getUser(request)); } } return joiner.toString(); } }
7.源码
properties配置类:
RateLimitProperties加载前缀:zuul.ratelimit的配置
@Data @Validated @RefreshScope @NoArgsConstructor @ConfigurationProperties(RateLimitProperties.PREFIX) public class RateLimitProperties { public static final String PREFIX = "zuul.ratelimit";
key\rate
redis\jpa\consul 用的是计数器的方式实现限流,不同的限流策略生成一个限流key,计算剩余的限流数据等存入rate,key–rate 一一对应,请求进来后由key查询是否已有rate,没有则生成rate保存,后置filter更新剩余请求次数、请求耗时
public class Rate { @Id @Column(name = "rate_key") private String key; /** * 剩余的请求数 * */ private Long remaining; /** * 剩余的请求耗时 * */ private Long remainingQuota; /** * * */ private Long reset; /** * 过期时间 * */ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss") private Date expiration; }
前置过滤
取得配置的Route信息,策略集合,遍历集合,生成限流key,创建rate(保存或更新),比较limit调用次数,请求时间是否超过阈值
后置过滤
计算请求耗时,更新key值对应的 限流rate的请求时间阈值
-
RateLimit zuul.ratelimit.enabled=false报错
2020-05-18 17:25:43项目中采用了spring-cloud-zuul-ratelimit,并发测试的时候想禁用掉限流zuul.ratelimit.enabled=false,结果报以下错误。 Description: Parameter 0 of method ratelimitKeyGenerator in ... -
golang--ratelimit令牌桶原理分析
2022-05-21 22:39:00juju/ratelimit中主要有三个文件,ratelimit.go, ratelimit_test.go、reader.go 先看ratelimit定义令牌桶的结构Bucket,简单看Bucket中属性的值代表的意义 type Bucket struct { //Clock是个接口,由... -
Linux_内核打印限速函数net_ratelimit()使用介绍,1) net_ratelimit()用于保护内核网 - phpStudy
2021-05-07 01:34:50内核打印限速函数net_ratelimit()使用介绍1) net_ratelimit()用于保护内核网络调试信息的打印, 当它返回(TRUE)时则可以打印调试信息,返回零则禁止信息打印. 它的特性为当"极快地"调用net_ratelimit()时,它最多只允许... -
caddy-ratelimit
2021-05-31 10:56:39球童速率限制插件 Caddyfile中的示例配置: rate_limit /api/* { by_header Authorization max_requests 180 window_length 500 } -
p-ratelimit:基于 Promise 的实用程序,可确保您不会过快调用受速率限制的 API
2021-05-31 05:41:04p-ratelimit可以为 API 系列中的所有函数强制执行单个共享配额。 最少的实现 实用程序提供了低级工具,需要您管理令牌并提供您自己的队列。 p-ratelimit要求对现有代码进行最少的修改。 分布式速 -
常见限流算法和go语言time/rate go.uber.org/ratelimit讲解
2020-05-15 14:45:22目录1 背景2 主流限流算法2.1 固定/滑动窗口限流算法2.1.1 固定窗口限流2.1.2 滑动窗口限流2.2 漏桶算法2.3 令牌桶3 golang标准库库实现限流算法3.1 构造...go官方库限流4.1 ratelimit 的使用4.2 基本实现4.3 最大松弛