怎么在Python中使用redis.asyncio实现分布式异步锁?

胖静大大_6734

胖静大大_6734

2026-10-08

417人浏览

原创

直接用 redis.asyncio 的 set 加锁易失败,因set不原子且无安全释放机制;须用Lua脚本校验value后删除,并设唯一client_id与过期时间。

怎么在python中使用redis.asyncio实现分布式异步锁?

为什么直接用 redis.asyncio 的 set 加锁容易失败

因为 set 操作本身不保证原子性——即使加了 nx=True, ex=10,在高并发下仍可能因网络延迟、客户端重试或 Redis 主从同步滞后,导致多个协程同时认为自己“抢到了锁”。更关键的是,redis.asyncio 默认不提供带原子释放逻辑的锁封装,你得自己处理 GET + DEL 的竞态问题。

正确做法是用 Lua 脚本把“判断锁归属 + 删除”打包成一个原子操作。官方推荐的锁值必须是唯一标识(比如随机 UUID),不能用固定字符串。

  • 锁 key 建议带业务前缀,如 "lock:order:create"
  • 锁 value 必须是 client_id(如 str(uuid4())),不能是 "1" 或时间戳
  • 务必设置过期时间(ex),防止死锁;但不要依赖它作为唯一保障
  • 不要用 await redis.delete(key) 直接删——这会误删别人持有的锁

如何写一个安全的异步分布式锁类

核心是封装两个原子操作:获取锁(SET key value NX EX)和释放锁(Lua 脚本校验 value 后 DEL)。下面是一个最小可用实现:

import uuid
import asyncio
import redis.asyncio as redis
<p>class AsyncRedisLock:
def <strong>init</strong>(self, redis_client: redis.Redis, key: str, timeout: int = 10):
self.redis = redis_client
self.key = key
self.timeout = timeout
self.value = str(uuid.uuid4())
self._acquired = False</p><pre class="brush:php;toolbar:false;">async def acquire(self) -> bool:
    ok = await self.redis.set(
        self.key, self.value, nx=True, ex=self.timeout
    )
    self._acquired = bool(ok)
    return self._acquired

async def release(self) -> bool:
    if not self._acquired:
        return False
    # Lua script: only delete if value matches
    script = """
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end
    """
    result = await self.redis.eval(script, 1, self.key, self.value)
    self._acquired = False
    return bool(result)

注意:redis.eval() 在 redis.asyncio 中是协程函数,必须 await;脚本里用 KEYS[1] 和 ARGV[1] 传参,不能拼字符串——否则有注入风险。

python-pro
python-pro

高级 Python 特性、异步编程、性能调优、静态类型、内存管理、Python 内部机制及生态库方面的专家。

下载

使用时怎么避免常见超时与重入陷阱

这个锁不是可重入的,同一个协程重复 acquire() 会失败。如果业务需要重入,得自己维护计数器(不推荐,复杂度陡增);更合理的做法是确保锁粒度合理,避免嵌套加锁。

  • 不要在锁内做耗时 IO(如 HTTP 请求、长 SQL)——超时会导致锁自动释放,其他协程趁虚而入
  • 务必用 try/finally 或异步上下文管理器确保 release() 执行,否则锁泄漏
  • 网络分区时,Redis 可能返回 ConnectionError 或 TimeoutError,这时无法确定锁状态,应视为加锁失败并退避重试
  • 若用连接池,确认 max_connections 足够,否则 acquire() 可能卡在等待连接上

有没有更省心的现成方案

可以考虑 aioredis-lock(已归档)或更活跃的 redlock-py 的异步分支,但要注意:它们多数没适配 redis.asyncio 4.x 的 API,且 Redlock 算法在 Redis Cluster 下仍有争议。生产环境若对一致性要求极高,建议优先用 Redis 官方的 RediSearch 或外部协调服务(如 etcd)。

真正容易被忽略的一点是:锁的 key 设计要和业务语义对齐。比如订单创建锁用 "lock:order:create" 没问题,但如果要按用户维度隔离,就得是 f"lock:order:create:{user_id}"——漏掉这个动态部分,就等于没锁。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1671

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

4204

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1669

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

24357

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2987

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3007

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1163

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

596

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2323

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程