All Projects → danielstjules → Redislock

danielstjules / Redislock

Licence: mit
Node distributed locking using redis

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Redislock

Vue Family Bucket Ssr Koa2 Full Stack Development From Meituan
🚀🚀2020最新Vue全家桶+SSR+Koa2全栈开发☁
Stars: ✭ 100 (-3.85%)
Mutual labels:  redis
Bojack
🐴 The unreliable key-value store
Stars: ✭ 101 (-2.88%)
Mutual labels:  redis
Whatsmars
Java生态研究(Spring Boot + Redis + Dubbo + RocketMQ + Elasticsearch)🔥🔥🔥🔥🔥
Stars: ✭ 1,389 (+1235.58%)
Mutual labels:  redis
Springboot Demo
springboot学习
Stars: ✭ 100 (-3.85%)
Mutual labels:  redis
Springboot Link Admin
Vue Springboot Project | Link Admin后端,适用于小型项目
Stars: ✭ 100 (-3.85%)
Mutual labels:  redis
Docker Laravel
🐳 Docker Images for Laravel development
Stars: ✭ 101 (-2.88%)
Mutual labels:  redis
Craftcms Docker
Craft3/Craft2 CMS Docker base (Nginx, PHP-FPM 8, PostgreSQL/MariaDB, Redis)
Stars: ✭ 99 (-4.81%)
Mutual labels:  redis
Actix Redis
Redis actor and middleware for Actix
Stars: ✭ 104 (+0%)
Mutual labels:  redis
Redis Cui
Simple, visual command line tool for redis
Stars: ✭ 101 (-2.88%)
Mutual labels:  redis
Pythonstudy
Python related technologies used in work: crawler, data analysis, timing tasks, RPC, page parsing, decorator, built-in functions, Python objects, multi-threading, multi-process, asynchronous, redis, mongodb, mysql, openstack, etc.
Stars: ✭ 103 (-0.96%)
Mutual labels:  redis
Foundatio
Pluggable foundation blocks for building distributed apps.
Stars: ✭ 1,365 (+1212.5%)
Mutual labels:  redis
Fastapi cache
FastAPI simple cache
Stars: ✭ 96 (-7.69%)
Mutual labels:  redis
Ohm
Object-Hash Mapping for Redis
Stars: ✭ 1,386 (+1232.69%)
Mutual labels:  redis
Springboot Templates
springboot和dubbo、netty的集成,redis mongodb的nosql模板, kafka rocketmq rabbit的MQ模板, solr solrcloud elasticsearch查询引擎
Stars: ✭ 100 (-3.85%)
Mutual labels:  redis
Emkc
Engineer Man Knowledge Center
Stars: ✭ 104 (+0%)
Mutual labels:  redis
Django Rq
A simple app that provides django integration for RQ (Redis Queue)
Stars: ✭ 1,361 (+1208.65%)
Mutual labels:  redis
Production Ready Expressjs Server
Express.js server that implements production-ready error handling and logging following latest best practices.
Stars: ✭ 101 (-2.88%)
Mutual labels:  redis
Redisql
Redis module that provides a completely functional SQL database
Stars: ✭ 1,393 (+1239.42%)
Mutual labels:  redis
Spring Boot 2.x Examples
Spring Boot 2.x code examples
Stars: ✭ 104 (+0%)
Mutual labels:  redis
Seckill
Spring Boot+MySQL+Redis+RabbitMQ的高性能高并发商品秒杀系统设计与优化
Stars: ✭ 103 (-0.96%)
Mutual labels:  redis

redislock

Node distributed locking using redis with lua scripts. Compatible with redis >= 2.6.12. A better alternative to locking strategies based on SETNX or WATCH/MULTI. Refer to Implementation and Alternatives for details.

Build Status

Installation

Using npm, you can install redislock with npm install --save redislock. You can also require it as a dependency in your package.json file:

"dependencies": {
    "redislock": "*"
}

Overview

redislock offers both atomic acquire and release operations, avoiding race conditions among clients, as well as the need for lock-specific redis connections. Lock creation requires a node_redis client, and accepts an object specifying the following three options:

  • timeout: Time in milliseconds before which a lock expires (default: 10000 ms)
  • retries: Maximum number of retries in acquiring a lock if the first attempt failed (default: 0, infinite: -1)
  • delay: Time in milliseconds to wait between each attempt (default: 50 ms)
var client = require('redis').createClient();
var lock   = require('redislock').createLock(client, {
  timeout: 20000,
  retries: 3,
  delay: 100
});

lock.acquire('app:feature:lock', function(err) {
  // if (err) ... Failed to acquire the lock

  lock.release(function(err) {
    // if (err) ... Failed to release
  });
});

Supports promises, thanks to bluebird, out of the box:

var client    = require('redis').createClient();
var redislock = require('redislock');
var lock      = redislock.createLock(client);

var LockAcquisitionError = redislock.LockAcquisitionError;
var LockReleaseError     = redislock.LockReleaseError;

lock.acquire('app:feature:lock').then(function() {
  // Lock has been acquired
  return lock.release();
}).then(function() {
  // Lock has been released
}).catch(LockAcquisitionError, function(err) {
  // The lock could not be acquired
}).catch(LockReleaseError, function(err) {
  // The lock could not be released
});

And an example with co:

var co     = require('co');
var client = require('redis').createClient();
var lock   = require('redislock').createLock(client);

co(function *(){
  try {
    yield lock.acquire('app:feature:lock');
  } catch (e) {
    // Failed to acquire the lock
  }

  try {
    yield lock.release();
  } catch (e) {
    // Failed to release
  }
})();

Implementation

Locking is performed using the following redis command:

SET key uuid PX timeout NX

If the SET returns OK, the lock has been acquired on the given key, and an expiration has been set. Then, releasing a lock uses the following redis script:

if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
end
return 0

This ensures that the key is deleted only if it is currently holding the lock, by passing its UUID as an argument. Extending a lock is done with a similar lua script:

if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('PEXPIRE', KEYS[1], ARGV[2])
end
return 0

Alternatives

Some alternative locking implementations do not use a random identifier, but instead simply invoke SETNX, assigning a timestamp. This has the problem of requiring synchronization of clocks between all instances to maintain timeout accuracy. Furthermore, freeing a lock with such an implementation may risk deleting a key set by a different lock.

Another technique used is to WATCH the key for changes when freeing, achieving a CAS-like operation, as described below:

WATCH key  # Begin watching the key for changes
GET key    # Retrieve its value, return an error if not equal to the lock's UUID
MULTI      # Start transaction
DEL key    # Delete the key
EXEC       # Execute the transaction, which will fail if the key had expired

However, this has the issue of requiring that you use a 1:1 mapping of redis clients to locks to ensure that a competing MULTI is not invoked, and that the release is unaffected by other watched keys.

In addition to the above, most locking libraries aren't compatible with promises by default, and due to their API, require "promisifying" individual locks. redislock avoids this issue by taking advantage of bluebird's nodeify function to offer an API that easily supports both callbacks and promises.

API

The module exports three functions for lock creation and management, as well as two errors for simplified error handling when using promises.

redislock.createLock(client, [options])

Creates and returns a new Lock instance, configured for use with the supplied redis client, as well as options, if provided. The options object may contain following three keys, as outlined at the start of the documentation: timeout, retries and delay.

var lock = redislock.createLock(client, {
  timeout: 10000,
  retries: 3,
  delay: 100
})

redislock.setDefaults(options)

Sets the default options to be used by any new lock created by redislock. Only available options are modified, and all other keys are ignored.

redislock.setDefaults({
  timeout: 200000,
  retries: 1,
  delay: 50
});

redislock.getAcquiredLocks()

Returns an array of currently active/acquired locks.

// Create 3 locks, but only acquire 2
redislock.createLock(client);

redislock.createLock(client).acquire('app:lock1', function(err) {
  redislock.createLock(client).acquire('app:lock2', function(err) {
    var locks = redislock.getAcquiredLocks(); // [lock, lock]
  });
});

redislock.LockAcquisitionError

The constructor for a LockAcquisitionError. Thrown or returned when a lock could not be acquired.

redislock.LockReleaseError

The constructor for a LockReleaseError. Thrown or returned when a lock could not be released.

redislock.LockExtendError

The constructor for a LockExtendError. Thrown or returned when a lock could not be extended.

Class: Lock

The lock class exposed by redislock. Each instance is assigned a UUID v1 string as an id, and is configured to work with the given redis client. The default options from which is inherits may be changed by using redislock.setDefaults.

lock.acquire[key, [fn]]

Attempts to acquire a lock, given a key, and an optional callback function. If the initial lock fails, additional attempts will be made for the configured number of retries, and padded by the delay. The callback is invoked with an error on failure, and returns a promise if no callback is supplied. If invoked in the context of a promise, it may throw a LockAcquisitionError.

var lock = redislock.createLock(client);
lock.acquire('example:lock', function(err) {
  if (err) return console.log(err.message); // 'Lock already held'
});

lock.release([fn])

Attempts to release the lock, and accepts an optional callback function. The callback is invoked with an error on failure, and returns a promise if no callback is supplied. If invoked in the context of a promise, it may throw a LockReleaseError.

var lock = redislock.createLock(client);
lock.acquire('app:lock', function(err) {
  if (err) return;

  setTimeout(function() {
    lock.release(function(err) {
      if (err) return console.log(err.message); // 'Lock on app:lock has expired'
    });
  }, 20000)
});

lock.extend(time, [fn])

Attempts to extend the timeout of a lock, and accepts an optional callback function. The callback is invoked with an error on failure, and returns a promise if no callback is supplied. If invoked in the context of a promise, it may throw a LockExtendError.

var lock = redislock.createLock(client);
lock.acquire('app:lock', function(err) {
  if (err) return;

  setTimeout(function() {
    lock.extend(20000, function(err) {
      if (err) return console.log(err.message); // 'Lock on app:lock has expired'
    });
  }, 20000)
});

Tests

Unit and functional tests are available in the base spec directory, and can be ran using npm test. Additional integration tests, which require an active redis-server configured on the default port and host, can be ran using mocha spec/integration/. Both tests suites are ran as part of the Travis CI build thanks to their support for services such as redis.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].