mirror of
https://github.com/lingble/safe-redis-leader.git
synced 2025-10-29 11:42:34 +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",
|
||||
"version": "0.0.1",
|
||||
"description": "Redis leader election implementation that does not have any race conditions",
|
||||
"main": "index.js",
|
||||
"main": "src/src/index.js",
|
||||
"scripts": {
|
||||
"test": "npm install && node ./docker/scripts/runner.js"
|
||||
},
|
||||
|
||||
@@ -107,87 +107,4 @@ async function 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;
|
||||
module.exports.createSafeRedisLeader = createSafeRedisLeader
|
||||
Reference in New Issue
Block a user