用Redis搞个锁,简单说说怎么实现和用法示例分享
- 问答
- 2025-12-29 20:43:53
- 5
用Redis搞个锁,说白了就是利用Redis的一个特性:因为它是个单线程的程序,所以它能保证同一个时刻只有一个命令能被执行,我们就是利用这个特性,让多个程序或者多个线程在抢一个“钥匙”,谁先把这个钥匙设置到Redis里,谁就抢到了锁,就可以去执行那些不能同时多人干的活儿了。
最基本的实现思路
最直接的想法是这样的:当一个程序(我们叫它客户端A)需要锁的时候,它就在Redis里创建一个特定的键值对,比如键叫 my_lock,值可以随便设一个能唯一标识这个客户端的值(比如用UUID),创建的时候,我们给这个键设置一个过期时间,比如10秒钟。
为什么一定要设置过期时间?这是为了防止死锁,万一客户端A抢到锁之后,因为网络问题或者自己崩溃了,没有来得及释放锁,那么这个锁就会永远留在Redis里,其他客户端就永远拿不到锁了,整个系统就卡死了,设置了过期时间,哪怕客户端A出了意外,这个锁也会在10秒后自动消失,其他客户端就能继续竞争了。
当客户端A做完事情后,它需要释放锁,释放锁就是去Redis里把那个 my_lock 键删掉,但是这里有个关键点:只能删自己设置的锁,为什么?想象一下这个场景:

- 客户端A抢到锁,设置锁的过期时间是10秒。
- 客户端A的业务操作比较慢,干了15秒,这时候锁因为过期已经自动消失了。
- 客户端B趁虚而入,成功设置了
my_lock,抢到了锁。 - 客户端A终于干完活了,它就去执行删除锁的操作。
- 如果客户端A不管三七二十一直接删,它就会把客户端B刚创建的锁给删掉!这就乱套了。
正确的做法是,在删除之前,要先检查一下这个锁的值是不是还是自己当初设置的那个UUID,如果是,才能删;如果不是,说明锁已经属于别人了,就不能删。
一个简单的用法示例(使用Python语言和redis-py库)
下面我用伪代码混合Python代码的逻辑来演示一下这个过程,这样更容易理解。

import redis
import uuid
# 连接到Redis服务器
redis_client = redis.Redis(host='localhost', port=6379, db=0)
# 定义一个唯一的锁键名
lock_key = "order_lock"
# 生成一个唯一标识符,代表当前客户端
client_identifier = str(uuid.uuid4())
# 定义锁的过期时间,单位是秒
lock_timeout = 10
# 尝试获取锁的函数
def acquire_lock():
# 使用SET命令,并加上NX和PX参数。
# NX表示“只有当这个key不存在时才设置”,这保证了只有一个客户端能设置成功。
# PX表示过期时间,单位是毫秒,这里我们设置10000毫秒(10秒)。
result = redis_client.set(lock_key, client_identifier, nx=True, px=lock_timeout * 1000)
# 如果设置成功,result为True,表示获取到了锁;失败则为False。
return result is True
# 释放锁的函数
def release_lock():
# 使用Lua脚本保证原子性操作
lua_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
# 执行Lua脚本:传入锁的键名和我们自己的标识符
result = redis_client.eval(lua_script, 1, lock_key, client_identifier)
# 如果删除成功,返回1;如果锁已经不是自己的了,返回0。
return result == 1
# 业务代码的使用方式
if acquire_lock():
print("成功抢到锁,开始处理关键任务...")
try:
# 这里写你的业务逻辑,比如扣减库存、生成订单等。
# ... 执行一些需要互斥访问的操作 ...
print("任务处理中...")
# 模拟一个耗时操作,注意这个时间最好不要超过锁的过期时间
# time.sleep(5)
finally:
# 无论业务逻辑是否出现异常,最终都要尝试释放锁
release_success = release_lock()
if release_success:
print("任务完成,锁已释放。")
else:
# 这里可能意味着锁已经过期并被其他客户端获取,日志记录一下就好,一般不需要特殊处理
print("锁释放失败(可能已超时自动释放)。")
else:
print("没能抢到锁,可能是其他程序正在处理,稍后再试。")
关于上面代码的一些重要解释
- 原子性:在获取锁的时候,我们使用了
set key value NX PX timeout这个命令,这个命令是原子性的,意思是“判断是否存在”和“设置值并设置超时”这两个动作是一气呵成的,中间不会被打断,这非常重要,如果分成两个命令(先判断exists,再setex),就可能出现多个客户端同时判断为“不存在”,然后都去设置,导致锁被多个客户端同时获得。 - 释放锁的原子性:释放锁时,我们使用了Lua脚本,因为判断值是否相等和删除键是两个操作,如果不放在一个原子操作里,也可能出问题,比如客户端A判断值相等,正准备删除时,锁过期了,客户端B设置了新锁,然后客户端A的删除命令才执行,就又误删了客户端B的锁,使用Lua脚本可以确保Redis会连续执行脚本里的所有命令,中间不会插入其他客户端的命令。
- 过期时间设置:设置一个合理的过期时间是个经验活,太短了,业务没处理完锁就没了,可能导致多个客户端同时进入临界区;太长了,万一客户端挂掉,其他客户端要等待很久才能继续,有时候可能需要设计一个“看门狗”机制,在业务处理期间不断延长锁的过期时间。
这种简单锁的局限性
这种用Redis实现的锁,通常被称为“互斥锁”或者“分布式锁”的简易版,它在很多场景下够用,但并不完美,比如它存在一个问题:如果Redis采用主从复制模式,客户端在主节点上设置了锁,但还没来得及同步到从节点,主节点就宕机了,之后从节点升级为主节点,但新的主节点上没有这个锁,其他客户端就可以再次获取到锁,导致锁失效,对于要求极端严格的场景,可能需要更复杂的算法(比如Redlock),但对于一般的防止重复提交、缓解并发冲突等场景,上面这种简单实现已经非常有用了。
用Redis搞个锁的核心就是“占坑+过期时间+安全释放”,理解了这三点,你就掌握了最基本也是最实用的方法。
本文由称怜于2025-12-29发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/70860.html
