使用redis锁的正确姿势

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)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值