rate-limiter-flexible
Advanced tools
Comparing version 0.12.6 to 0.13.0
@@ -1,4 +0,4 @@ | ||
## Block Strategy | ||
## In-memory Block Strategy | ||
Block strategy is against DDoS attacks. | ||
In-memory Block Strategy is against DDoS attacks. | ||
Redis is quite fast. It can process over 100k requests per second. | ||
@@ -17,3 +17,3 @@ However, performance still depends on amount of requests per second. | ||
Block strategy algorithm developed with specificity rate limiter in mind: | ||
In-memory Block strategy algorithm developed with specificity rate limiter in mind: | ||
* it doesn't use `setTimeout` to expire blocked keys, so doesn't overload Event Loop | ||
@@ -59,3 +59,3 @@ * blocked keys expired in two cases: | ||
#### Without Block Strategy | ||
#### Without In-memory Block Strategy | ||
@@ -89,3 +89,3 @@ 5 points per second to consume | ||
#### Setup Block Strategy | ||
#### Setup In-memory Block Strategy | ||
@@ -125,3 +125,3 @@ 5 points per second to consume | ||
* Latency is smaller with Block Strategy | ||
* Latency is smaller with In-memory Block Strategy | ||
* Number of requests to Redis less on 59k roughly |
const RateLimiterAbstract = require('./RateLimiterAbstract'); | ||
const BlockedKeys = require('./component/BlockedKeys'); | ||
const RateLimiterRes = require('./RateLimiterRes'); | ||
@@ -24,8 +25,11 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract { | ||
getInmemoryBlockMsBeforeExpire(rlKey) { | ||
if (this.inmemoryBlockOnConsumed > 0) { | ||
return this._inmemoryBlockedKeys.msBeforeExpire(rlKey); | ||
get client() { | ||
return this._client; | ||
} | ||
set client(value) { | ||
if (typeof value === 'undefined') { | ||
throw new Error('storeClient is not set'); | ||
} | ||
return 0; | ||
this._client = value; | ||
} | ||
@@ -37,2 +41,4 @@ | ||
* | ||
* It uses _getRateLimiterRes function to prepare RateLimiterRes from store result | ||
* | ||
* @param resolve | ||
@@ -91,2 +97,10 @@ * @param reject | ||
getInmemoryBlockMsBeforeExpire(rlKey) { | ||
if (this.inmemoryBlockOnConsumed > 0) { | ||
return this._inmemoryBlockedKeys.msBeforeExpire(rlKey); | ||
} | ||
return 0; | ||
} | ||
get inmemoryBlockOnConsumed() { | ||
@@ -147,2 +161,53 @@ return this._inmemoryBlockOnConsumed; | ||
/** | ||
* | ||
* @param key | ||
* @param pointsToConsume | ||
* @returns {Promise<any>} | ||
*/ | ||
consume(key, pointsToConsume = 1) { | ||
return new Promise((resolve, reject) => { | ||
const rlKey = this.getKey(key); | ||
const inmemoryBlockMsBeforeExpire = this.getInmemoryBlockMsBeforeExpire(rlKey); | ||
if (inmemoryBlockMsBeforeExpire > 0) { | ||
return reject(new RateLimiterRes(0, inmemoryBlockMsBeforeExpire)); | ||
} | ||
this._upsert(rlKey, pointsToConsume, this.msDuration) | ||
.then((res) => { | ||
this._afterConsume(resolve, reject, rlKey, pointsToConsume, res); | ||
}) | ||
.catch((err) => { | ||
this._handleError(err, 'consume', resolve, reject, key, pointsToConsume); | ||
}); | ||
}); | ||
} | ||
penalty(key, points = 1) { | ||
const rlKey = this.getKey(key); | ||
return new Promise((resolve, reject) => { | ||
this._upsert(rlKey, points, this.msDuration) | ||
.then((res) => { | ||
resolve(this._getRateLimiterRes(rlKey, points, res)); | ||
}) | ||
.catch((err) => { | ||
this._handleError(err, 'penalty', resolve, reject, key, points); | ||
}); | ||
}); | ||
} | ||
reward(key, points = 1) { | ||
const rlKey = this.getKey(key); | ||
return new Promise((resolve, reject) => { | ||
this._upsert(rlKey, -points, this.msDuration) | ||
.then((res) => { | ||
resolve(this._getRateLimiterRes(rlKey, -points, res)); | ||
}) | ||
.catch((err) => { | ||
this._handleError(err, 'reward', resolve, reject, key, -points); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Get RateLimiterRes object filled depending on storeResult, which specific for exact store | ||
@@ -164,3 +229,3 @@ * | ||
* @param rlKey | ||
* @param points | ||
* @param initPoints | ||
* @param msDuration | ||
@@ -170,5 +235,13 @@ * | ||
*/ | ||
_block(rlKey, points, msDuration) { // eslint-disable-line no-unused-vars | ||
return Promise.reject(new Error("You have to implement the method '_block'!")); | ||
_block(rlKey, initPoints, msDuration) { | ||
return new Promise((resolve, reject) => { | ||
this._upsert(rlKey, initPoints, msDuration, true) | ||
.then(() => { | ||
resolve(new RateLimiterRes(0, msDuration, initPoints)); | ||
}) | ||
.catch((err) => { | ||
this._handleError(err, 'block', resolve, reject, this.parseKey(rlKey), initPoints); | ||
}); | ||
}); | ||
} | ||
}; |
## RateLimiterMongo | ||
RateLimiterMongo creates unique collection for each rate limiter `keyPrefix` | ||
### Benchmark | ||
@@ -4,0 +6,0 @@ |
{ | ||
"name": "rate-limiter-flexible", | ||
"version": "0.12.6", | ||
"version": "0.13.0", | ||
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -14,3 +14,3 @@ [![Build Status](https://travis-ci.org/animir/node-rate-limiter-flexible.png)](https://travis-ci.org/animir/node-rate-limiter-flexible) | ||
Flexible rate limiter and anti-DDoS protector works in process | ||
_Memory_, _Cluster_, _MongoDB_ or _Redis_ allows to control requests rate in single process or distributed environment. | ||
_Memory_, _Cluster_, _MongoDB_, _MySQL_ or _Redis_ allows to control requests rate in single process or distributed environment. | ||
@@ -37,2 +37,3 @@ It uses **fixed window** as it is much faster than rolling window. | ||
* [RateLimiterMongo](#ratelimitermongo) | ||
* [RateLimiterMySQL](#ratelimitermysql) | ||
* [RateLimiterCluster](#ratelimitercluster) | ||
@@ -48,3 +49,6 @@ * [RateLimiterMemory](#ratelimitermemory) | ||
Average latency of pure NodeJS endpoint limited by (all set up on one server): | ||
Average latency during test pure NodeJS endpoint in cluster of 4 workers with everything set up on one server by | ||
1000 concurrent clients with maximum 2000 requests per sec during 30 seconds. | ||
```text | ||
@@ -57,2 +61,7 @@ 1. Memory 0.34 ms | ||
500 concurrent clients with maximum 1000 req per sec during 30 seconds | ||
```text | ||
5. MySQL 11.10 ms | ||
``` | ||
#### RateLimiterRedis benchmark | ||
@@ -127,3 +136,3 @@ | ||
insuranceLimiter: new RateLimiterMemory( | ||
// It will be used only on Redis or Mongo error as insurance | ||
// It will be used only on Redis, Mongo or MySQL error as insurance | ||
// Can be any implemented limiter like RateLimiterMemory or RateLimiterRedis extended from RateLimiterAbstract | ||
@@ -224,4 +233,37 @@ { | ||
`insuranceLimiter` can be setup to avoid errors, but all changes won't be written from `insuranceLimiter` to `RateLimiterMongo` when connection established | ||
`insuranceLimiter` can be setup to avoid errors, but all changes won't be written from `insuranceLimiter` to `RateLimiterMongo` when connection established | ||
### RateLimiterMySQL | ||
It supports `mysql2` and `mysql` node packages. | ||
MySQL connection have to be created with allowed `multipleStatementes`. | ||
Limits data, which expired more than an hour ago, are removed every 5 minutes by `setTimeout`. | ||
[Read more about RateLimiterMySQL here](https://github.com/animir/node-rate-limiter-flexible/blob/master/MYSQL.md) | ||
```javascript | ||
const mysql = require('mysql2'); | ||
const client = mysql.createConnection({ | ||
host : 'localhost', | ||
user : 'root', | ||
password : 'secret', | ||
multipleStatements: true // it is required by limiter | ||
}); | ||
const opts = { | ||
storeClient: client, | ||
dbName: 'mydb', | ||
tableName: 'mytable', // all limiters store data in one table | ||
points: 5, // Number of points | ||
duration: 1, // Per second(s) | ||
}; | ||
const rateLimiter = new RateLimiterMySQL(opts); | ||
// Usage is the same as for RateLimiterRedis | ||
``` | ||
Connection to MySQL takes milliseconds, so any method of rate limiter is rejected with Error, until connection is established | ||
### RateLimiterCluster | ||
@@ -266,3 +308,3 @@ | ||
```javascript | ||
const rateLimiter = new RateLimiterMemory( // It will be used only on Redis error as insurance | ||
const rateLimiter = new RateLimiterMemory( | ||
{ | ||
@@ -316,3 +358,3 @@ keyPrefix: 'rlflx', | ||
* | ||
* It may be Error if you use Redis, Mongo or Cluster without insurance | ||
* It may be Error if you use Redis, Mongo, MySQL or Cluster without insurance | ||
* { limit2: Error } | ||
@@ -368,7 +410,7 @@ */ | ||
#### Options specific to Redis and Mongo | ||
#### Options specific to Redis, Mongo, MySQL | ||
* `inmemoryBlockOnConsumed` `Default: 0` Against DDoS attacks. Blocked key isn't checked by requesting Redis or Mongo. | ||
* `inmemoryBlockOnConsumed` `Default: 0` Against DDoS attacks. Blocked key isn't checked by requesting Redis, MySQL or Mongo. | ||
In-memory blocking works in **current process memory**. | ||
Redis and Mongo are quite fast, however, they may be significantly slowed down on dozens of thousands requests. | ||
Redis, MySQL and Mongo are quite fast, however, they may be significantly slowed down on dozens of thousands requests. | ||
@@ -379,3 +421,3 @@ * `inmemoryBlockDuration` `Default: 0` Block key for `inmemoryBlockDuration` seconds, | ||
* `insuranceLimiter` `Default: undefined` Instance of RateLimiterAbstract extended object to store limits, | ||
when Redis or Mongo comes up with any error. | ||
when Redis, MySQL or Mongo comes up with any error. | ||
@@ -387,2 +429,11 @@ All data from `insuranceLimiter` is NOT copied to parent limiter, when error gone | ||
#### Options specific to MySQL | ||
* `storeClient` `Required` Have to be `mysql2` or `mysql` connection | ||
* `dbName` `Default: 'rtlmtrflx'` Database where limits are stored. It is created during creating a limiter | ||
* `tableName` `Default: equals to 'keyPrefix' option` By default, limiter creates table for each unique `keyPrefix`. | ||
All limits for all limiters are stored in one table if custom name is set. | ||
#### Options specific to Cluster | ||
@@ -413,3 +464,3 @@ | ||
* **resolved** with `RateLimiterRes` when point(s) is consumed, so action can be done | ||
* **rejected** only for Redis and Mongo if `insuranceLimiter` isn't setup: when some error happened, where reject reason `rejRes` is Error object | ||
* **rejected** only for Redis, Mongo and MySQL if `insuranceLimiter` isn't setup: when some error happened, where reject reason `rejRes` is Error object | ||
* **rejected** only for RateLimiterCluster if `insuranceLimiter` isn't setup: when `timeoutMs` exceeded, where reject reason `rejRes` is Error object | ||
@@ -416,0 +467,0 @@ * **rejected** when there is no points to be consumed, where reject reason `rejRes` is `RateLimiterRes` object |
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
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
130699
30
1200
500