rate-limiter-flexible
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -1,3 +0,7 @@ | ||
const RateLimiter = require('./lib/RateLimiter'); | ||
const RateLimiterRedis = require('./lib/RateLimiterRedis'); | ||
const RateLimiterMemory = require('./lib/RateLimiterMemory'); | ||
module.exports.RateLimiter = RateLimiter; | ||
module.exports = { | ||
RateLimiterRedis, | ||
RateLimiterMemory | ||
}; |
module.exports = class RateLimiterRes { | ||
constructor() { | ||
this._msBeforeNext = 0; // Milliseconds before next action | ||
this._remainingPoints = 0; // Remaining points in current duration | ||
constructor(remainingPoints, msBeforeNext) { | ||
this.remainingPoints = typeof remainingPoints === 'undefined' ? 0 : remainingPoints ; // Remaining points in current duration | ||
this.msBeforeNext = typeof msBeforeNext === 'undefined' ? 0 : msBeforeNext ; // Milliseconds before next action | ||
} | ||
@@ -6,0 +6,0 @@ |
{ | ||
"name": "rate-limiter-flexible", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
112
README.md
@@ -6,5 +6,49 @@ [![Build Status](https://travis-ci.org/animir/node-rate-limiter-flexible.png)](https://travis-ci.org/animir/node-rate-limiter-flexible) | ||
Flexible rate limiter with Redis as broker allows to control requests rate in cluster or distributed environment. | ||
Backed on native Promises. It uses fixed window to limit requests. | ||
Flexible rate limiter with Redis as broker allows to control requests rate in cluster or distributed environment. | ||
It uses fixed window to limit requests. | ||
Advantages: | ||
* backed on native Promises | ||
* actions can be done evenly over duration window to cut off picks | ||
* no race conditions | ||
* covered by tests | ||
* no prod dependencies | ||
* Redis errors don't result to broken app if `inMemoryLimiter` set up | ||
* useful `penalty` and `reward` methods to change limits on some results of an action | ||
### Benchmark | ||
By `bombardier -c 1000 -l -d 10s -r 2500 -t 5s http://127.0.0.1:3000/pricing` | ||
```text | ||
Statistics Avg Stdev Max | ||
Reqs/sec 2491.79 801.92 9497.25 | ||
Latency 8.62ms 11.69ms 177.96ms | ||
Latency Distribution | ||
50% 5.41ms | ||
75% 7.65ms | ||
90% 15.07ms | ||
95% 27.24ms | ||
99% 70.85ms | ||
HTTP codes: | ||
1xx - 0, 2xx - 25025, 3xx - 0, 4xx - 0, 5xx - 0 | ||
others - 0 | ||
``` | ||
Endpoint is simple Express 4.x route launched in `node:latest` and `redis:alpine` Docker containers by PM2 with 4 workers | ||
Endpoint is limited by `RateLimiterRedis` with config: | ||
```javascript | ||
new RateLimiterRedis( | ||
{ | ||
redis: redisClient, | ||
points: 1000, | ||
duration: 1, | ||
}, | ||
); | ||
``` | ||
## Installation | ||
@@ -16,2 +60,4 @@ | ||
### RateLimiterRedis | ||
Redis client must be created with offline queue switched off | ||
@@ -21,3 +67,3 @@ | ||
const redis = require('redis'); | ||
const { RateLimiter } = require('rate-limiter-flexible'); | ||
const { RateLimiterRedis, RateLimiterMemory } = require('rate-limiter-flexible'); | ||
@@ -32,9 +78,17 @@ const redisClient = redis.createClient({ enable_offline_queue: false }); | ||
const opts = { | ||
redis: redisClient, | ||
points: 5, // Number of points | ||
duration: 5, // Per second(s) | ||
execEvenly: false, | ||
inMemoryLimiter: new RateLimiterMemory( // It will be used only on Redis error as insurance | ||
{ | ||
points: 1, // 1 is fair if you have 5 workers and 1 cluster | ||
duration: 5, | ||
execEvenly: false, | ||
}) | ||
}; | ||
const rateLimiter = new RateLimiter(redisClient, opts); | ||
const rateLimiterRedis = new RateLimiterRedis(opts); | ||
rateLimiter.consume(remoteAddress) | ||
rateLimiterRedis.consume(remoteAddress) | ||
.then(() => { | ||
@@ -44,5 +98,5 @@ // ... Some app logic here ... | ||
// Depending on results it allows to fine | ||
rateLimiter.penalty(remoteAddress, 3); | ||
rateLimiterRedis.penalty(remoteAddress, 3); | ||
// or rise number of points for current duration | ||
rateLimiter.reward(remoteAddress, 2); | ||
rateLimiterRedis.reward(remoteAddress, 2); | ||
}) | ||
@@ -52,6 +106,7 @@ .catch((rejRes) => { | ||
// Some Redis error | ||
// Decide what to do with it on your own | ||
// Never happen if `inMemoryLimiter` set up | ||
// Decide what to do with it in other case | ||
} else { | ||
// Can't consume | ||
// If there is no error, rateLimiter promise rejected with number of ms before next request allowed | ||
// If there is no error, rateLimiterRedis promise rejected with number of ms before next request allowed | ||
const secs = Math.round(rejRes.msBeforeNext / 1000) || 1; | ||
@@ -64,7 +119,24 @@ res.set('Retry-After', String(secs)); | ||
### RateLimiterMemory | ||
It manages limits in **current process memory**, so keep it in mind when use it in cluster | ||
```javascript | ||
const rateLimiter = new RateLimiterMemory( // It will be used only on Redis error as insurance | ||
{ | ||
points: 1, // 1 is fair if you have 5 workers and 1 cluster | ||
duration: 5, | ||
execEvenly: false, | ||
}); | ||
// Usage is the same as for RateLimiterRedis | ||
// Except: it never rejects Promise with Error | ||
``` | ||
## Options | ||
* `points` Maximum number of points can be consumed over duration | ||
* `duration` Number of seconds before points are reset | ||
* `execEvenly` Delay action to be executed evenly over duration | ||
* `points` `Default: 4` Maximum number of points can be consumed over duration | ||
* `duration` `Default: 1` Number of seconds before points are reset | ||
* `execEvenly` `Default: false` Delay action to be executed evenly over duration | ||
First action in duration is executed without delay. | ||
@@ -74,3 +146,9 @@ All next allowed actions in current duration are delayed by formula `msBeforeDurationEnd / (remainingPoints + 2)` | ||
Note: it isn't recommended to use it for long duration, as it may delay action for too long | ||
* `inMemoryLimiter` `Default: undefined` RateLimiterMemory object to store limits in process memory, when Redis comes up with any error. | ||
Be careful when use it in cluster or in distributed app. | ||
It may result to floating number of allowed actions. | ||
If an action with a same `key` is launched on one worker several times in sequence, limiter will reach out of points soon. | ||
Omit it if you want strictly use Redis and deal with errors from it | ||
## API | ||
@@ -93,3 +171,3 @@ | ||
* resolved when point(s) is consumed, so action can be done | ||
* rejected when some Redis error happened, where reject reason `rejRes` is Error object | ||
* only for RateLimiterRedis: rejected when some Redis error happened, where reject reason `rejRes` is Error object | ||
* rejected when there is no points to be consumed, where reject reason `rejRes` is `RateLimiterRes` object | ||
@@ -103,14 +181,14 @@ | ||
Fine `key` by `points` number of points. | ||
Fine `key` by `points` number of points for **one duration**. | ||
Note: Depending on time penalty may go to next durations | ||
Doesn't return anything | ||
Returns Promise | ||
### rateLimiter.reward(key, points = 1) | ||
Reward `key` by `points` number of points. | ||
Reward `key` by `points` number of points for **one duration**. | ||
Note: Depending on time reward may go to next durations | ||
Doesn't return anything | ||
Returns Promise |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
66091
22
596
185
1