Hyperf中@Lock注解实现分布式锁的核心在于Redlock实例必须单例注入、锁状态跨请求复用、key和租期动态提取、协程安全释放及异常兜底;若切面中每次new实例,将导致锁状态丢失、无法续期、误释放,破坏互斥性。

Hyperf 中用 @Lock 注解实现分布式锁,核心不是“加个注解就行”,而是必须让 Redlock 的租约、重试、释放时机和切面执行生命周期对齐,否则极易出现锁失效或死锁。
为什么不能直接在切面里 new Redlock 实例
常见错误是切面中每次拦截都创建新 Redlock 实例,导致无法共享锁状态、无法自动续期、释放时找不到对应锁实例。Hyperf 的切面是无状态的,锁资源必须可跨请求复用且线程安全。
-
Redlock实例应作为单例注入(如通过Container::get(Redlock::class)),而非每次 new - 锁的 key 和租期必须从注解参数提取,不能硬编码
- 若使用 Swoole 协程,
Redlock客户端必须支持协程(如hyperf/redis封装的RedisClient)
@Lock 注解定义要暴露哪些关键参数
自定义注解至少需支持 key(锁名)、leaseTime(租约秒数)、waitTime(最大等待秒数)、retryCount(重试次数),否则无法适配不同业务场景。
-
key支持 SpEL 表达式(如"#user.id"),需配合ExpressionEvaluator解析,不能只接受静态字符串 -
leaseTime建议默认 30 秒,但必须允许覆盖;过短易被误释放,过长故障恢复慢 -
waitTime和retryCount要联动:若设为 0 或 -1,表示不等待直接失败,避免线程卡住
切面逻辑必须处理协程上下文与锁释放时机
Hyperf 是协程环境,切面中获取锁后,若方法抛异常或协程被 cancel,锁必须确保释放——不能依赖 try-finally 在切面内硬写,因为协程可能被调度中断。
- 推荐用
defer+go启动守护协程,在方法返回/异常后延时释放(但需校验锁是否仍属当前协程) - 更稳妥做法:在切面
before获取锁成功后,将锁 token 和协程 ID 绑定存入Coroutine::getContext(),在after或exception通知中查 context 并释放 - 禁止在切面
after中直接调unlock():若方法耗时超 leaseTime,锁已被 Redis 自动释放,此时 unlock 会误删别人持有的锁
Redlock 失败时的降级与日志必须显式控制
Redlock 获取失败默认抛异常会中断业务,但有些场景(如非关键缓存更新)应允许跳过锁直接执行。
- 注解增加
failFast = false参数,控制失败时是否继续执行原方法 - 所有锁获取/释放操作必须打 debug 日志,含
key、token、elapsed、result,否则线上问题无法定位 - 若使用多个 Redis 实例做 Redlock,注意
quorum计算(n/2+1),实例数为偶数时容错能力下降,建议奇数节点
真正难的不是写完注解和切面,而是当服务重启、协程 panic、Redis 节点临时失联时,锁的语义是否还能保得住——这些边界必须在测试里用 kill -9、redis-cli shutdown、超时 mock 拿真实数据压出来。



















