Nodejs 基于 Redis 的分布式锁


Nodejs 基于 Redis 的分布式锁

http://myfjdthink.com/2016/11/29/nodejs-%E4%B8%AD%E5%9F%BA%E4%BA%8E-redis-%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E3%80%82/

  1. 简单的解决方案
  • 发送 SETNX lock.orderid 尝试获得锁。
  • 如果锁不存在,则 set 并获得锁。
  • 如果锁存在,则跳过此次操作或者等待一下再重试。

SETNX 是一个原子操作,可以保证只有一个节点会拿到锁。

  1. 推荐的方案

Redis 出现单点故障,例如 master 节点宕机了,而 Redis 的复制是异步的,可能出现以下情况:

  • 客户端 A 在 master 节点拿到了锁。
  • master 节点在把 A 创建的 key 写入 slave 之前宕机了。
  • slave 变成了 master 节点 4.B 也得到了和 A 还持有的相同的锁(因为原来的 slave 里还没有 A 持有锁的信息)

Redis 官方给出了更加可靠的实现 Distributed locks with Redis

  1. Redlock 的实现

NodeJS 的实现是node-redlock

https://github.com/mike-marcacci/node-redlock

  • install
1
npm i --save redlock
  • Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var client1 = require('redis').createClient(6379, 'redis1.example.com');
var client2 = require('redis').createClient(6379, 'redis2.example.com');
var client3 = require('redis').createClient(6379, 'redis3.example.com');
var Redlock = require('redlock');

var redlock = new Redlock(
// you should have one client for each independent redis node
// or cluster
[client1, client2, client3],
{
// the expected clock drift; for more details
// see http://redis.io/topics/distlock
driftFactor: 0.01, // time in ms

// the max number of times Redlock will attempt
// to lock a resource before erroring
retryCount: 10,

// the time in ms between attempts
retryDelay: 200, // time in ms

// the max time in ms randomly added to retries
// to improve performance under high contention
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
retryJitter: 200 // time in ms
}
);
  • Error Handling
1
2
3
redlock.on('clientError', function(err) {
console.error('A redis error has occurred:', err);
});
  • Usage (promise style)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';

// the maximum amount of time you want the resource locked,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;

redlock.lock(resource, ttl).then(function(lock) {

// ...do something here...

// unlock your resource when you are done
return lock.unlock()
.catch(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
});

官网给出的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';

// the maximum amount of time you want the resource locked,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;
redlock.lock(resource, ttl, function(err, lock) {
// we failed to lock the resource
if(err) {
// ...
}
// we have the lock
else {
// ...do something here...
// unlock your resource when you are done
lock.unlock(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
}
});