redis锁的使用方式一般有三种,INCR,SETNX,SET。
1.INCR
INCR命令会将key的值加一,如果key值不存在,则key值会被初始化为0,然后执行INCR操作。
127.0.0.1:6379> GET LOCK_1234
(nil)
127.0.0.1:6379> INCR LOCK_1234
(integer) 1
127.0.0.1:6379> GET LOCK_1234
"1"
利用INCR命令,结合程序构建锁,具体使用逻辑如下:
1)创建程序中需要加锁的key的关联key值,可以在原key_name前加特定字符实现。如要加锁的key_name为1234,则关联key_name为LOCK_1234。
2)客户端A执行INCR LOCK_1234操作,如果为1,则无其他客户端使用,先设置过期时间避免程序异常影响其他程序使用此key。如果值不为1,则有其他客户端在占用此key,等一会再次访问,直到值为0。
3)客户端A处理完逻辑后删除LOCK_1234。
具体代码实现:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock(order_id):
lock_key = 'LOCK_' + str(order_id)
incr = rdb.incr(lock_key)
if incr == 1:
rdb.expire(lock_key, 10)
print 'lock success'
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock(order_id)
if __name__ == '__main__':
redis_lock(1234)
2.SETNX
SETNX(SET is Not eXists),当key不存在时可以为key设置值,返回1,否则返回0。
127.0.0.1:6379> GET LOCK_2345
(nil)
127.0.0.1:6379> SETNX LOCK_2345 lock
(integer) 1
127.0.0.1:6379> SETNX LOCK_2345 lock
(integer) 0
127.0.0.1:6379> SETNX LOCK_2345 abc
(integer) 0
利用SETNX实现锁操作和上边的INCR类似,具体实现如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock_setnx(order_id):
lock_key = 'LOCK_' + str(order_id)
setnx = rdb.setnx(lock_key, 'lock')
if setnx == 1:
rdb.expire(lock_key, 10)
print 'lock success'
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock_setnx(order_id)
if __name__ == '__main__':
redis_lock_setnx(2345)
3.SET
Redis从2.6.12版本开始, SET命令的行为可以通过一系列参数来修改。具体参数如下:
1)EX:设置键的过期时间为 second 秒。
2)PX:设置键的过期时间为 millisecond 毫秒。
3)NX:只在键不存在时,才对键进行设置操作。
4)XX:只在键已经存在时,才对键进行设置操作。
可以通过NX参数和EX参数实现锁操作,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock_set(order_id):
lock_key = 'LOCK_' + str(order_id)
set = rdb.set(lock_key, 'lock', ex=10, nx=True)
if set:
print 'lock success'
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock_set(order_id)
if __name__ == '__main__':
redis_lock_set(3456)
4.以上方式存在的问题
以上方式都设置了过期时间,原因在于如果程序由于某些bug以外退出了,不加过期时间的话,这个key会一直被锁定,无法更新。
但是加过期时间来处理就会有问题,如果客户端1在设置的过期时间内程序正常执行,但是没有处理完,这时过期时间失效了,然后这个key的锁被客户端2获取,在客户端2还没处理完的时候客户端1处理完了,然后删除了客户端2的锁。
针对这个问题可以在给锁赋值的时候增加随机字符,然后删除的时候判断下是否为自己赋的值就可以了。具体实现如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
import time
rdb_host = '127.0.0.1'
rdb_port = 6379
rdb_pwd = 'root'
rdb_db = 13
db = redis.ConnectionPool(
host=rdb_host,
port=rdb_port,
password=rdb_pwd,
db=rdb_db)
rdb = redis.Redis(connection_pool=db)
def redis_lock_set_random(order_id):
lock_key = 'LOCK_' + str(order_id)
lock_value = 'lock' + str(int(time.time()))
set = rdb.set(lock_key, lock_value, ex=10, nx=True)
if set:
print 'lock success'
if rdb.get(lock_key) == lock_value:
rdb.delete(lock_key)
else:
print 'current key has been occupied'
time.sleep(1)
redis_lock_set_random(order_id)
if __name__ == '__main__':
redis_lock_set_random(4567)