1.使用set和其两个扩展参数(ex,nx)实现分布式锁

​ 可以使用setnx (set if not exists) 加锁,如果执行结束,再将锁释放掉。

1
2
3
> setnx lock:task_id true
··· doing task ···
> del lock:task_id

​ 但是这样会有问题,如果在执行task之间出现了异常,删除锁的操作无法执行,就会出现死锁。

​ 于是可以在加锁的时候加上过期时间,这样即使出现异常也可以保证锁可以正常释放。

1
2
3
4
> setnx lock:task_id true
> expire lock:task_id 5
··· doing task ···
> del lock:task_id

​ 以上逻辑还会有问题,如果在setnx和expire之间服务器突然挂掉了,导致没有给锁加上过期时间,仍然会造成死锁。

​ 这种问题的根源在于setnx和expire不是原子指令。可以使用set的扩展参数,实现setnx和expire的原子操作,从而实现分布式锁。

1
2
3
> set lock:task_id true ex 5 nx
··· doing task ···
> del lock:task_id

2.分布式锁的超时问题

​ redis分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻辑处理时间很长,超过了锁的过期时间,那么就会出现在第一个线程加锁之后,在处理过程中锁过期了,其他线程就可以重新持有这把锁,导致代码不能得到严格串行执行。

​ 为了避免这个问题,redis分布式锁不要用于太长时间的任务。

3.可重入锁

​ 可重入锁是指线程在持有锁的情况下再次请求加锁,如果一个锁支持一个线程持续加锁,那么这个锁是可重入的。Redis分布式锁如果要支持可重入,需要对客户端的set方法进行包装,使用Treadlocal变量存储当前持有锁的计数。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# -*- coding: utf-8
import redis
import threading

locks = threading.local()
locks.redis = {}


def key_for(task_id):
return "account_{}".format(task_id)


def _lock(redis_client, key):
return bool(redis_client.set(key, 1, nx=True, ex=5))


def _unlock(redis_client, key):
redis_client.delete(key)


def lock(redis_client, task_id):
key = key_for(task_id)
if key in locks.redis:
locks.redis[key] += 1
return True
locked = _lock(redis_client, key)
if not locked:
return False
locks.redis[key] = 1
return True


def unlock(redis_client, task_id):
key = key_for(task_id)
if key in locks.redis:
locks.redis[key] -= 1
if locks.redis[key] <= 0:
del locks.redis[key]
_unlock(redis_client, key)
return True
return False


client = redis.StrictRedis(password='Donghuan@2019')
print("lock: ", lock(client, 'task1'))
print("now lock num: ", locks.redis)
print("lock: ", lock(client, 'task1'))
print("now lock num: ", locks.redis)
print("unlock: ", unlock(client, 'task1'))
print("now lock num: ", locks.redis)
print("unlock: ", unlock(client, 'task1'))
print("now lock num: ", locks.redis)