基於redis實現的鎖(用於控制nodejs的併發)

navyxie發表於2016-05-20

場景:使用node+mongo搭建web應用時,常常遇到併發問題導致建立一條資料的時候會同時建立多條。

通過redis set 方法 配合 ‘NX’ 選項即可實現鎖機制。

主要的方法有三個:

lock(加鎖)

unlock(解鎖)

extend(更改鎖過期時間)

Redlock.prototype.lock = function(resource,ttl,callback){
  var self = this;
  var _val = _random();
  return new Promise(function(resolve, reject) {
    var lock = new Lock(self, resource, _val);
    self.client.SET(resource,_val,`EX`,ttl,`NX`,function(err,data){
      if(!err && data === `OK`){
        return resolve(lock);
      }else{
        var err = err || `resource: `+resource+` is locked.`;
        self.emit(`lockError`, err);
        return reject(err);
      }
    })
  }).asCallback(callback);
}

Redlock.prototype.unlock = function(lock,callback){
  var self = this;
  return new Promise(function(resolve, reject) {
    if(typeof lock !== `object`) return reject(`unlockError:lock is not object.`);
    self.client.GET(lock.resource,function(err,data){
      if(!err && data === lock.value){
        self.client.DEL(lock.resource,function(err){
          if(err){
            self.emit(`unlockError`,err);
            return reject(`unlockError:`+err);
          }else{
            return resolve(lock);
          }     
        })
      }else{
        self.emit(`unlockError`,err || `resource:` + lock.resource + ` unable to unlock.`)
        return reject(`unlockError:` + err || (`resource:` + lock.resource + ` unable to unlock.`));
      }
    })
  }).asCallback(callback);
}

Redlock.prototype.extend = function(lock,ttl,callback){
  var self = this;
  return new Promise(function(resolve, reject){
    if(typeof lock !== `object`) return reject(`unlockError:lock is not object.`);
    self.client.GET(lock.resource,function(err,data){
      if(!err && data === lock.value){
        self.client.expire(lock.resource,ttl,function(err,data){
          if(!err && data === 1){
            return resolve(lock);
          }else{
            self.emit(`extendError`, err);
            return reject(`extendError:`+err);
          }          
        });
      }else{
        self.emit(`extendError`, err);
        return reject(`extendError:`+err);
      }
    })
  }).asCallback(callback);
}

用法

var client = require(`redis`).createClient(`port`,`host`);
var redlock = new RedLock(client);
var lock;

`callback`:

//lock
redlock.lock(`test-resource-lock`,3,function(err,lockInstance){
  lock = lockInstance;
  done(err);
});
//unlock
redlock.unlock(lock,function(err,data){
  done(err);
});


`promise`:

redlock.lock(`test-resource-lock-promise`,3).done(
  function(lock){
    //todo
    redlock.unlock(lock);
  },
  function(){
  }
) 

PS:支援callback and promise.

實現比較簡單,100行內,有興趣的可以在 這裡https://github.com/navyxie/redlock-node 檢視原始碼

相關文章