mokies / ratelimitj Goto Github PK
View Code? Open in Web Editor NEWA Java library for Rate-Limiting, providing extensible storage and application framework adaptors.
License: Apache License 2.0
A Java library for Rate-Limiting, providing extensible storage and application framework adaptors.
License: Apache License 2.0
This is a great project,But I have encountered some problems in use it, i change ReidsClient
to RedisClusterClient
and 'StatefulRedisConnection' and to StatefulRedisClusterConnection
, such as below code:
RedisClusterClient client = RedisClusterClient.create(getRedisUri(properties.getRedisCluster()));
GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool = ConnectionPoolSupport
.createGenericObjectPool(() -> client.connect(), new GenericObjectPoolConfig());
try {
StatefulRedisClusterConnection<String, String> connection = pool.borrowObject()
Set<RequestLimitRule> rules = Collections.singleton(RequestLimitRule.of(1, TimeUnit.SECONDS, 1000).withPrecision(2));
Occasionally, the current limit will occur. I have determined that the maximum flow rate has not been reached. It often happens around 1 second,my question is why and how to fix it ? thanks.
the server env:
Redis Cluster: 3.x (3 master and 3 slave)
CentOS 7.x
when does the stable version of Rate limiting toggles (dark launch) be release ?
Hi
I am using RateLimitJ - Dropwizard lib to maintain the distributed rate limit in one of my projects but I observe nothing is logged on limit details.
Am I missing something ? kindly help me out. Thanks in advance.
Hi,
I may be confused with the 'precision' but I don't get the expected results when I use the default precision.
When I set a single rule for a limit of X requests per Y seconds, the rate limiter actually allows 2X requests per Y seconds to pass through. More interesting, it seems like this problem decays and disappears when we wait enough time or there are enough requests - i'm not sure.
When I use precision = 1, the results I get are much more what I'm looking for but it feels like a "fixed window" algorithm rather than a sliding one.
Also,
I've created a very simple test application for testing this issue and made a CSV output of the results.
I also made a scatter chart in Excel to visualize the problem very clearly.
Test 1: duration = 5 seconds, limit = 1, precision = 3 (default)
https://www.dropbox.com/s/llk479gbq5e3c4t/out1.xlsx?dl=0
Test 2: duration = 3 seconds, limit = 3, precision = 3 (default)
https://www.dropbox.com/s/yi88xd6e9zp9m46/out2.xlsx?dl=0
Test 3: duration = 3 seconds, limit = 3, precision = 1
https://www.dropbox.com/s/ag61g3osuteor9v/out3.xlsx?dl=0
public class Application {
private final static long TEST_DURATION_MILLIS = 120000;
public static void main(String[] args) throws InterruptedException, IOException {
Set<RequestLimitRule> rules = Collections
.singleton(RequestLimitRule.of(Duration.ofSeconds(3), 3).withPrecision(1));
RequestRateLimiter requestRateLimiter = new InMemorySlidingWindowRequestRateLimiter(rules);
try (PrintWriter writer = new PrintWriter(new FileWriter("out.csv"))) {
writer.write("timestamp,delta time,time from last 'under limit',under limit\r\n");
long startTime = time();
long currentTime = 0;
long previousTime = time();
long previousUnderLimitTime = time();
while ((currentTime - startTime) < TEST_DURATION_MILLIS) {
currentTime = time();
Timestamp timestamp = new Timestamp(currentTime);
boolean underLimit = !requestRateLimiter.overLimitWhenIncremented("x");
writer.write(timestamp
+ ","
+ (currentTime - previousTime)
+ ","
+ (currentTime - previousUnderLimitTime)
+ ","
+ underLimit
+ "\r\n");
if (underLimit) {
previousUnderLimitTime = currentTime;
}
previousTime = currentTime;
Thread.sleep(100);
}
}
}
private static long time() {
return System.currentTimeMillis();
}
}
Why when I set the 2 following rules I get only 2 requests under-limit per each 10 seconds (and not 4)?
RequestLimitRule.of(Duration.ofSeconds(10), 4).withPrecision(1)
RequestLimitRule.of(Duration.ofSeconds(2), 2).withPrecision(1)
Thank you very much!
Asaf
Hi Craig, when are you planning to do the next release with the changes since M2?
Thanks for the great lib.
May I know if it is possible to:
Hi!
We use RateLimitJ with AWS ElastiCache. Today we had an outage because AWS ElastiCache performed a maintenance and for some reason (we're going to check with AWS guys as well) the Lua scripts disappeared.
We started getting this error:
NOSCRIPT No matching script. Please use EVAL.
Would be great if the library will handle it gracefully and re-upload the scripts if they're missing.
I assume this is easy to test by uploading the scripts, running FLUSHALL
and then attempting to apply the rate limiting.
Shameless plug: you can use http://github.com/testcontainers/testcontainers-java to test that
We love your published library and very likely to use it.
One idea to improve is to return not just a simple boolean
but a rich object of type {isOverLimit:boolean, limitingRule:RequestLimitRule}
or similar.
Exact type is not critical, it's just the idea of letting the user (be it internal or external) - which limit was reached.
So currently - the user would just get false
when she's limited.
After - she'd know what exactly blocked her access.
WDYT?
p.s
I know it's a major refactor to internal logic + interface.. but it can be done in a non-breaking mode
I have following bundle setup for my application, it seems that if we have other bundle added into the BootStrap
, the rate limit is not working and would throw NullPointerException
LOGGER.info("starting to do initialization...");
LOGGER.info("adding ratelimit factory bundle");
RequestRateLimiterFactory factory = new InMemoryRateLimiterFactory();
bootstrap.addBundle(new RateLimitBundle(factory));
LOGGER.info("added ratelimit factory bundle");
guiceBundle = GuiceBundle.<KrylovMasterConfiguration>newBuilder()
.addModule(new ConfigModule())
.addModule(new ResourceModule())
.addModule(new CoreServiceModule())
.addModule(new AuthModule())
// .addModule(new CommonModule()) // Needed for workflow and workspace, do not remove
//.addModule(new ZookeeperClientModule()) //Needed for workflow and workspace, do not remove
.addModule(new DefaultServerModule())
.addModule(new DefaultWorkspaceModule())
.addModule(new ExtensionModule())
.addModule(new RateLimitModule())
//.enableAutoConfig(getClass().getPackage().getName()) // disable this to prevent accidental registration.
.setConfigClass(KrylovMasterConfiguration.class)
.build();
// if we add this line of code. rate limit won't work
bootstrap.addBundle(guiceBundle);
the exception is
2020-12-28 08:41:58 thread:[dw-47 - GET /api/hello-world?name=zhonwu] ERROR e.m.r.d.f.RateLimit429EnforcerFilter - Error occurred checking rate-limit. Assuming under limit
java.lang.NullPointerException: null
at es.moki.ratelimij.dropwizard.filter.RateLimit429EnforcerFilter.filter(RateLimit429EnforcerFilter.java:51)
at org.glassfish.jersey.server.ContainerFilteringStage.apply(ContainerFilteringStage.java:132)
at org.glassfish.jersey.server.ContainerFilteringStage.apply(ContainerFilteringStage.java:68)
at org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:318)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
at io.dropwizard.jetty.NonblockingServletHolder.handle(NonblockingServletHolder.java:49)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1655)
at io.dropwizard.servlets.ThreadNameFilter.doFilter(ThreadNameFilter.java:35)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
at io.dropwizard.jersey.filter.AllowedMethodsFilter.handle(AllowedMethodsFilter.java:45)
at io.dropwizard.jersey.filter.AllowedMethodsFilter.doFilter(AllowedMethodsFilter.java:39)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:89)
at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:120)
at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:135)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:533)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1317)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1219)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at com.codahale.metrics.jetty9.InstrumentedHandler.handle(InstrumentedHandler.java:239)
at io.dropwizard.jetty.RoutingHandler.handle(RoutingHandler.java:52)
at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:724)
at io.dropwizard.jetty.BiDiGzipHandler.handle(BiDiGzipHandler.java:67)
at org.eclipse.jetty.server.handler.RequestLogHandler.handle(RequestLogHandler.java:56)
at org.eclipse.jetty.server.handler.StatisticsHandler.handle(StatisticsHandler.java:169)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:531)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:352)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:762)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:680)
at java.lang.Thread.run(Thread.java:748)
I noticed this item in the project backlog and would like to express my interest in it. Do you anticipate this will be implemented in the near future?
I was using this library(Redis module) and really wanted to use it to dynamically rate limit APIs at runtime. But looking at the code it appears like limit rule is bound to instance created by the factory. So to make it to work dynamically, I need to create a new instance with new limit rule.
Is my understanding correct or is there a way to change limit rule on the instance already created?
I would like to ask how to obtain the remaining time of the current limit through the interface. For example, if I limit the number of requests to 100 in 1 minute, and the request reaches 100 in the 20th second, I want to return the remaining time: 40 seconds.
In Dropwizard: Is there any way that I can return a JSON 429 response instead of the default HTML/text response?
I looked at https://nickb.dev/blog/writing-a-dropwizard-json-app, but couldn't wrap my head around how to do this for the Dropwizard RateLimitBundle
Hello!
few years ago I worked on a similar project (https://github.com/ecostanzi/spring-rate-limiter). The idea was using annotations to rate limiting method calls. Since on the rate limiter section this project seems more mature I tried to merge the two. I might use this in some project next month so let me know what you think.
There are scenarios where the client is interested in knowing the remaining limit. This information is usually passed back to client as part of response headers.
Can we add a method like
long getRemainingLimitInCurrentWindow(String key)
in es.moki.ratelimitj.core.limiter.request.RequestRateLimiter
This will definitely help in utilising this library in web filters.
Please advise I am interested in taking this up.
Hey!
We have been using the library for a while (with redis) and we would like to complement it with a backoff period on top of the sliding window algorithm. We believe this would be a nice improvement on it by giving an extra punishment to whoever is misusing our systems.
"Tokens" will be used and added back like we are used to in a sliding window algorithm, but the change is:
We could probably achieve that by populating the cache of a given key like time:precision:bo
with the expiry of the backoff period for example, and simply return 1
early in the algorithm
This would be a backwards compatible change, probably on the RequestLimitRule
adding the possibility to have the backoff only to the rules that configured it.
Do you find this interesting and would be willing to have it on the library? I can work on it and open the necessary PRs.
In case you agree, feel free to point me to any directions you may think it would be the best.
Hi, we're using ratelimitj for quite a while now and right now it's the only thing keeping us from uprading to dropwizard 2.0.
Are there any plans to support it in the near future?
Thanks
I have the following dependencies in the gradle file.
dependencies {
. . .
// 'ratelimitj-redis-0.4.1.jar'
compile ('io.projectreactor:reactor-core:3.1.2.RELEASE')
compile ('es.moki.ratelimitj:ratelimitj-redis:0.4.1')
. . .
}
The first line throws me an NoClassDefFoundError. Any idea to workaround this?
RedisClient client = RedisClient.create(RedisURI.create("localhost", 6379));
Caused by: java.lang.NoClassDefFoundError: reactor/core/scheduler/Schedulers
at io.lettuce.core.resource.DefaultClientResources.(DefaultClientResources.java:171)
at io.lettuce.core.resource.DefaultClientResources$Builder.build(DefaultClientResources.java:461)
at io.lettuce.core.resource.DefaultClientResources.create(DefaultClientResources.java:229)
at io.lettuce.core.AbstractRedisClient.(AbstractRedisClient.java:96)
at io.lettuce.core.RedisClient.(RedisClient.java:86)
at io.lettuce.core.RedisClient.create(RedisClient.java:123)
at io.squelch.collector.api.controller.OAuthController.health(OAuthController.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
This Lib looks exactly to what I was looking for, but the lack of documentation makes it difficult to get started.
A quick example on how to use the APIs to limit REST calls should be enough. Also Spring Boot integration will be quite handy.
It looks like lettuce 6.x introduced a binary incompatibility that makes ratelimitj break at runtime.
The breakage we're running into is that RedisScriptLoader
calls RedisScriptingReactiveCommands.scriptLoad
, and this changed from being a single method with the signature Mono<String> scriptLoad(V script)
to a pair of methods, Mono<String> scriptLoad(String script)
and Mono<String> scriptLoad(byte[] script)
.
This change appears to be source compatible with ratelimitj. I tried changing ratelimitj's dependency on lettuce from io.lettuce:lettuce-core:5.3.4.RELEASE
to io.lettuce:lettuce-core:6.0.2.RELEASE
, and ./gradlew ratelimitj-redis:check
builds and passes with the new version of lettuce.
I am wondering if this could be use with Redis cluster.
As far as I know, LUA scripts does not work with Redis cluster.
Can someone explain this to me please.
If redis is down the whole reactive app stops working . This is due to the fact that there is no timeout setup
To fix the issue
public static final ExecutorService CANCEL_EXECUTOR = Executors.newCachedThreadPool(); .cancelOn(Schedulers.fromExecutor(RedisCacheApiConfiguration.CANCEL_EXECUTOR)) .timeout(Duration.ofMillis(config.getReadTimeout()))
Notice we will need a different scheduler for cancel because redis has a bug where on cancel it blocks the core thread.
I'm using Ratelimitj with my Spring Boot application. In Spring Boot I use spring-boot-starter-webflux:2.4.1
and it requires io.projectreactor:reactor-core
. The problem is that ratelimitj-redis:0.7.0-RC3
needs reactor-core:3.3.8.RELEASE
and spring-boot-starter-webflux:2.4.1
needs reactor-core:3.4.1
and there are some breaking changes between these 2 versions.
To be more specific I'm getting the following error: java.lang.NoSuchMethodError: 'reactor.core.publisher.Flux reactor.core.publisher.Flux.retry(long, java.util.function.Predicate)'
in RedisSlidingWindowRequestRateLimiter.java:139
. I think the error is quite self-explanatory.
@igorbolic already created a PR in which he fixes the problem: #50
@mokies any chance to merge it anytime soon and publish a new release?
Thanks in advance!
I’ve a use case where the provider wants us to throttle the calls not by some fixed rate/second, but by individual user ids. In other words, a user must wait for a period x before making a call again. So it’s more like a cool off period than the usual rate limiting.
One obvious way to approach this problem is to create a rate limiter per user. But there will be many such limiters, and very short lived. Are there better ways?
Hi,
Many thanks for your library! I'm trying to use it within one Tibco Business Works project but unfortunately Business Works does not support Java 8. Is it possible to have ratelimitj build with 1.7?
Kind Regards,
Milan
Hi there,
I am getting the following error, when ratelimitj tries to load the lua script.
io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'SCRIPT'
Redis version 3.2.11
ratelimitj-redis:04.1
Hi, Any plans to support Jedis ?
Many projects don't use letucce and work with Jedis instead.
Thanks
Hi, your library looks great, with the best-of-breed sliding window algorithm and the pluggable backends!
I've got a use case that it doesn't support very well though, where I need to check if the limit has been reached without incrementing it if not. We're going to track login failures, and once we've reached the limit, disallow further login attempts. So we first need to check if the failure rate has reached limit, and only then allow the request and increment the count if it fails.
So I'm thinking of adding another method to RequestRateLimiter to enable this, say
/**
* Determine if the given key, after incrementing by the given weight, is >= the configured rate limit.
* @param key key.
* @param weight A variable weight.
* @return {@code true} if the key is >== the limit, otherwise {@code false} .
*/
boolean geLimit(String key, int weight);
Looking at the implementation code, this would be easily implemented since it's a slight variant of the existing code for overlimit().
Would you be open to a pull request if I add this?
thx,
Chris
I need to run a same service with multiple instances to run a rate limit cluster, for high-availabilty and high performance purpose, is that supported?
Are you considering this project still active? It looks like it has some nice features, but development seems to have stopped, and the CI status is failing. If it's still live, I'm interested in helping get some improvements made (especially Spring advice support).
Hi,
I am getting below error while calling overLimitWhenIncremented
Pleae guide me if I am doing anything wrong
2019-09-06 12:26:26.821 DEBUG 15328 --- [nio-7070-exec-2] o.s.web.servlet.DispatcherServlet : Failed to complete request: io.lettuce.core.RedisException: Connection is closed
2019-09-06 12:26:26.832 ERROR 15328 --- [nio-7070-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is io.lettuce.core.RedisException: Connection is closed] with root cause
io.lettuce.core.RedisException: Connection is closed
at io.lettuce.core.protocol.DefaultEndpoint.validateWrite(DefaultEndpoint.java:195) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.protocol.DefaultEndpoint.write(DefaultEndpoint.java:137) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisChannelHandler.dispatch(RedisChannelHandler.java:187) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.StatefulRedisConnectionImpl.dispatch(StatefulRedisConnectionImpl.java:152) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher$RedisSubscription.dispatchCommand(RedisPublisher.java:390) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher$CommandDispatch$1.dispatch(RedisPublisher.java:477) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher$RedisSubscription.checkCommandDispatch(RedisPublisher.java:385) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher$State$2.request(RedisPublisher.java:543) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher$RedisSubscription.request(RedisPublisher.java:242) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:102) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxReplay$ReplaySubscriber.onSubscribe(FluxReplay.java:1148) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:64) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at io.lettuce.core.RedisPublisher$State$1.subscribe(RedisPublisher.java:522) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher$RedisSubscription.subscribe(RedisPublisher.java:225) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at io.lettuce.core.RedisPublisher.subscribe(RedisPublisher.java:126) ~[lettuce-core-5.1.8.RELEASE.jar:na]
at reactor.core.publisher.MonoFromPublisher.subscribe(MonoFromPublisher.java:43) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxSourceMono.subscribe(FluxSourceMono.java:46) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.Flux.subscribe(Flux.java:7921) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxReplay.connect(FluxReplay.java:1052) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxAutoConnectFuseable.subscribe(FluxAutoConnectFuseable.java:62) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoNext.subscribe(MonoNext.java:40) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.Mono.subscribe(Mono.java:3852) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:128) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoFlatMapMany.subscribe(MonoFlatMapMany.java:49) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.Flux.subscribe(Flux.java:7921) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.resubscribe(FluxRetryPredicate.java:123) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxRetryPredicate.subscribe(FluxRetryPredicate.java:52) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoSingle.subscribe(MonoSingle.java:58) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:56) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.Mono.block(Mono.java:1517) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at es.moki.ratelimitj.redis.request.RedisSlidingWindowRequestRateLimiter.throwOnTimeout(RedisSlidingWindowRequestRateLimiter.java:153) ~[ratelimitj-redis-0.6.0.jar:na]
at es.moki.ratelimitj.redis.request.RedisSlidingWindowRequestRateLimiter.overLimitWhenIncremented(RedisSlidingWindowRequestRateLimiter.java:73) ~[ratelimitj-redis-0.6.0.jar:na]
at es.moki.ratelimitj.redis.request.RedisSlidingWindowRequestRateLimiter.overLimitWhenIncremented(RedisSlidingWindowRequestRateLimiter.java:68) ~[ratelimitj-redis-0.6.0.jar:na]
at com.srijan.throttling.StudentResource.getStudent(StudentResource.java:26) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_221]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_221]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_221]
at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_221]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.22.jar:9.0.22]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.22.jar:9.0.22]
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [na:1.8.0_221]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [na:1.8.0_221]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.22.jar:9.0.22]
at java.lang.Thread.run(Unknown Source) [na:1.8.0_221]
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:133) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
at reactor.core.publisher.Mono.block(Mono.java:1518) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
... 58 common frames omitted
Adding code for creating rate limiter
@Bean
public RequestRateLimiter createRequestRateLimiter() {
RedisClient redisClient = RedisClient.create(RedisURI.create("127.0.0.1", 6379));
RequestRateLimiter rateLimiter = null;
try(RedisRateLimiterFactory factory = new RedisRateLimiterFactory(redisClient)){
rateLimiter = factory.getInstance(Collections.singleton(RequestLimitRule.of(Duration.ofMinutes(1), 10)));
}
return rateLimiter;
}
Adding pom.xml here
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.srijan.throttling</groupId>
<artifactId>RateLimitJ</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>RateLimitJ</name>
<description>Project for Demo of Throttling using RateLimitJ</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>es.moki.ratelimitj</groupId>
<artifactId>ratelimitj-redis</artifactId>
<version>0.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Often many APIs that use rate limiting return the number of remaining calls using headers like X-RateLimit-Remaining
. It would be ideal for the interfaces to return an object that contains additional information like number of calls remaining and when the limit will be reset. This makes it easier for clients to deal with the rate limits in a better way.
redis version 3.2 and cluster is build with official solution
https://redis.io/topics/cluster-tutorial
i create the cluster:
master A and slave a
master B and slave b
master C and slave c
when my application started ,client will auto create connect with master node A,B,C.
but when i killed node A , client can not failover to a( a has upgrade to Master this time)
client will always send command to A node, but the problem is, node A is down.
it should auto update the cluster infomation, and auto reconnect to the new master node a.
Thank you for all your hard work. Am trying to include your work and realized we must have requestLimitRules in hand before we create RedisSlidingWindowRequestRateLimiter instance. I would like to create vanilla RedisSlidingWindowRequestRateLimiter instance during application startup and inject different rules at different point in the process.
So I was looking at your code and thinking whether we can create vanilla/generic RedisSlidingWindowRequestRateLimiter without tying to limit rules and expose public method to pass in serialized rules later when needed?
Something like
public RedisSlidingWindowRequestRateLimiter(StatefulRedisConnection<String, String> connection)
Highly appreciate your thoughts on my approach.
Hi,
We noticed that 0.7.0-RC3 has been released a while ago. Assuming things are pretty stable, would it be possible to final release?
Also, it seems that ratelimitj-inmemory is only available as 0.7.0-RC1
The computation of blockId is as follows:
blockId = Math.floor(time_now/precision);
Currently, while computing the blockId in all the test cases the BanditSupplier starts the timer at perfectly rounded count of 1000000. So whenever a delta time is added to this count the blockId works as expected.
Consider the following situation:
If the BanditSupplier was started with a counter of 1000007 and the Ratelimiter rule of (duration=10sec, limit = 5, precision = 10).
The window starts with the blockId of (1000007/10) i.e 100000
With an elapse of 3 seconds, the timer count now is 1000010 and the corresponding blockId is 100001 which results in a different window and defeats the purpose of FixedWindowRateLimiting
I have very simple example of in memory sliding window. Limit is one call for a give key and duration is 10 seconds. It starts of with limiting one request but after 10th second two calls are being allowed.
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import es.moki.ratelimitj.core.limiter.request.RequestLimitRule;
import es.moki.ratelimitj.core.limiter.request.RequestRateLimiter;
import es.moki.ratelimitj.inmemory.request.InMemorySlidingWindowRequestRateLimiter;
public class RateLimitExample {
public static void main(final String... strings) {
final Duration precisionDuration = Duration.of(1, ChronoUnit.SECONDS);
// 1 request per 10 seconds if address is same
final Duration addressDuration = Duration.of(10, ChronoUnit.SECONDS);
final RequestLimitRule addressRule = RequestLimitRule.of(addressDuration, 1).withPrecision(precisionDuration);
final RequestRateLimiter addressRuleRateLimiter = new InMemorySlidingWindowRequestRateLimiter(addressRule);
while(true) {
final UserObject user = UserObject.builder().age(11).name("ABC").address("NewYork").build();
processStudent(user, addressRuleRateLimiter);
}
}
private static void processStudent(final UserObject user, final RequestRateLimiter addressRuleRateLimiter) {
if (!addressRuleRateLimiter.overLimitWhenIncremented(user.getAddress())) {
user.callStudent();
}
}
}
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@EqualsAndHashCode
@Builder
public class UserObject {
String name;
int age;
String address;
boolean isProcessed;
static DateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
public void callStudent() {
Date currentDate = new Date(System.currentTimeMillis());
System.out.println(dateFormat.format(currentDate)+" "+this.toString());
}
}
Output
10 Mar 2020 14:26:17 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:27 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:27 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:37 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:37 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:47 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:47 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:57 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:57 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:27:07 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:27:07 UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
As you can see first call for key NewYork was allowed only once for 10 seconds. But after that it allowed two calls.
Also how actively this library is maintained?
Would it be possible to get the latest changes onto maven central?
I'm particularly interested in the in-memory rate limiter ones.
Thanks,
Hi,
It seems that windows doesn't slides in inMemory mode. Maybe is it a bug?
In InMemorySlidingWindowRequestRateLimiter.java line 136:
replace == by <=
Regards
The RedisClient
docs say:
RedisClient is an expensive resource. It holds a set of netty's EventLoopGroup's that use multiple threads. Reuse this instance as much as possible or share a ClientResources instance amongst multiple client instances.
RedisRateLimiterFactory
makes it difficult to share a single RedisClient
, because its close()
method closes the client. (That is, it effectively "takes ownership" of the client.)
Taking a RedisClient
also makes it incompatible with connection pooling, as typically one does not have visibility to the RedisClient
itself outside of the pool's construction.
Given that RedisRateLimiterFactory
only uses a single connection, could an additional constructor that takes a StatefulRedisConnection<String, String>
be added?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.