mirror of
https://github.com/lingble/safe-redis-leader.git
synced 2025-11-01 04:58:02 +00:00
readme
This commit is contained in:
116
README.md
116
README.md
@@ -1 +1,115 @@
|
|||||||
README.md
|
# Safe Redis Leader
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
The Safe Redis Leader JS module is designed to provide a leader election implementation that provides tested gaurentees that there is only a single leader elected from a group of clients at one time.
|
||||||
|
|
||||||
|
|
||||||
|
The implementation is a port of the stale [Redis Leader npm package](https://github.com/pierreinglebert/redis-leader) that implements a solution to the [known race condition](https://github.com/pierreinglebert/redis-leader/blob/c3b4db5df9802908728ad0ae4310a52e74acb462/index.js#L81). Additionally, this rewritten package:
|
||||||
|
|
||||||
|
1. Removes the usage of `.bind` and `this`, as well as prototype inheritance (Without introducing classes in the main impl)
|
||||||
|
2. Only exposes public api functions that should be exposed (no more public-but-should-be-private `_elect` fn)
|
||||||
|
3. has a test suite within docker-compose using a real redis instance, which allows anyone to run the tests with no heavy dependency setup
|
||||||
|
4. Has tests to assert the known race condition can no longer occur
|
||||||
|
5. removes the need for `new`, by providing a simple `createSafeRedisLeader(...)` public fn
|
||||||
|
6. Replace callback-hell with async/await
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install the package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save safe-redis-leader
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
in one terminal, run the follow index.js:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const {createSafeRedisLeader} = require('safe-redis-leader')
|
||||||
|
const Redis = require('ioredis')
|
||||||
|
|
||||||
|
async function main(){
|
||||||
|
|
||||||
|
const asyncRedis = new Redis({
|
||||||
|
host: "locahost",
|
||||||
|
port: 6379,
|
||||||
|
password: "some-password"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const leaderElectionKey = 'the-election'
|
||||||
|
|
||||||
|
const safeLeader = await createSafeRedisLeader({
|
||||||
|
asyncRedis: asyncRedis,
|
||||||
|
ttl: 1500,
|
||||||
|
wait: 3000,
|
||||||
|
key: leaderElectionKey
|
||||||
|
})
|
||||||
|
|
||||||
|
safeLeader.on("elected", ()=>{
|
||||||
|
console.log("I'm the leader - 1")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
await safeLeader.elect()
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e)=>{
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
In a seperate terminal/tab, run the following index.js:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const {createSafeRedisLeader} = require('safe-redis-leader')
|
||||||
|
const Redis = require('ioredis')
|
||||||
|
|
||||||
|
async function main(){
|
||||||
|
|
||||||
|
const asyncRedis = new Redis({
|
||||||
|
host: "locahost",
|
||||||
|
port: 6379,
|
||||||
|
password: "some-password"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const leaderElectionKey = 'the-election'
|
||||||
|
|
||||||
|
const safeLeader = await createSafeRedisLeader({
|
||||||
|
asyncRedis: asyncRedis,
|
||||||
|
ttl: 1500,
|
||||||
|
wait: 3000,
|
||||||
|
key: leaderElectionKey
|
||||||
|
})
|
||||||
|
|
||||||
|
safeLeader.on("elected", ()=>{
|
||||||
|
console.log("I'm the leader - 2")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
await safeLeader.elect()
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e)=>{
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Run Library Tests
|
||||||
|
|
||||||
|
|
||||||
|
npm run docker:test
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# License
|
||||||
|
MIT
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "safe-redis-leader",
|
"name": "safe-redis-leader",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "Redis leader election implementation that does not have any race conditions",
|
"description": "Redis leader election implementation that does not have any race conditions",
|
||||||
"main": "index.js",
|
"main": "src/src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm install && node ./docker/scripts/runner.js"
|
"test": "npm install && node ./docker/scripts/runner.js"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -108,86 +108,3 @@ async function createSafeRedisLeader({
|
|||||||
|
|
||||||
|
|
||||||
module.exports.createSafeRedisLeader = createSafeRedisLeader
|
module.exports.createSafeRedisLeader = createSafeRedisLeader
|
||||||
|
|
||||||
// function Leader(redis, options) {
|
|
||||||
// options = options || {};
|
|
||||||
// this.id = uuid.v4();
|
|
||||||
// this.redis = redis;
|
|
||||||
// this.options = {};
|
|
||||||
// this.options.ttl = options.ttl || 10000; // Lock time to live in milliseconds
|
|
||||||
// this.options.wait = options.wait || 1000; // time between 2 tries to get lock
|
|
||||||
|
|
||||||
// this.key = hashKey(options.key || 'default');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// util.inherits(Leader, EventEmitter);
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Renew leader as elected
|
|
||||||
// */
|
|
||||||
// Leader.prototype._renew = function _renew() {
|
|
||||||
// // it is safer to check we are still leader
|
|
||||||
// this.isLeader(function(err, isLeader) {
|
|
||||||
// if(isLeader) {
|
|
||||||
// this.redis.pexpire(this.key, this.options.ttl, function(err) {
|
|
||||||
// if(err) {
|
|
||||||
// this.emit('error', err);
|
|
||||||
// }
|
|
||||||
// }.bind(this));
|
|
||||||
// } else {
|
|
||||||
// clearInterval(this.renewId);
|
|
||||||
// this.electId = setTimeout(Leader.prototype.elect.bind(this), this.options.wait);
|
|
||||||
// this.emit('revoked');
|
|
||||||
// }
|
|
||||||
// }.bind(this));
|
|
||||||
// };
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Try to get elected as leader
|
|
||||||
// */
|
|
||||||
// Leader.prototype.elect = function elect() {
|
|
||||||
// // atomic redis set
|
|
||||||
// this.redis.set(this.key, this.id, 'PX', this.options.ttl, 'NX', function(err, res) {
|
|
||||||
// if(err) {
|
|
||||||
// return this.emit('error', err);
|
|
||||||
// }
|
|
||||||
// if(res !== null) {
|
|
||||||
// this.emit('elected');
|
|
||||||
// this.renewId = setInterval(Leader.prototype._renew.bind(this), this.options.ttl / 2);
|
|
||||||
// } else {
|
|
||||||
// // use setTimeout to avoid max call stack error
|
|
||||||
// this.electId = setTimeout(Leader.prototype.elect.bind(this), this.options.wait);
|
|
||||||
// }
|
|
||||||
// }.bind(this));
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Leader.prototype.isLeader = function isLeader(done) {
|
|
||||||
// this.redis.get(this.key, function(err, id) {
|
|
||||||
// if(err) {
|
|
||||||
// return done(err);
|
|
||||||
// }
|
|
||||||
// done(null, (id === this.id));
|
|
||||||
// }.bind(this));
|
|
||||||
// };
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * if leader, stop being a leader
|
|
||||||
// * stop trying to be a leader
|
|
||||||
// */
|
|
||||||
// Leader.prototype.stop = function stop() {
|
|
||||||
// this.isLeader(function(err, isLeader) {
|
|
||||||
// if(isLeader) {
|
|
||||||
// // possible race condition, cause we need atomicity on get -> isEqual -> delete
|
|
||||||
// this.redis.del(this.key, function(err) {
|
|
||||||
// if(err) {
|
|
||||||
// return this.emit('error', err);
|
|
||||||
// }
|
|
||||||
// this.emit('revoked');
|
|
||||||
// }.bind(this));
|
|
||||||
// }
|
|
||||||
// clearInterval(this.renewId);
|
|
||||||
// clearTimeout(this.electId);
|
|
||||||
// }.bind(this));
|
|
||||||
// };
|
|
||||||
|
|
||||||
// module.exports = Leader;
|
|
||||||
Reference in New Issue
Block a user