mirror of
				https://github.com/lingble/safe-redis-leader.git
				synced 2025-10-30 20:17:48 +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" | ||||
|   }, | ||||
|   | ||||
| @@ -108,86 +108,3 @@ 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; | ||||
		Reference in New Issue
	
	Block a user
	 mike k
					mike k