Redlock在Webman中需用≥3个独立Redis节点(非主从),通过Predis连接池初始化RedLock客户端,锁key须带业务上下文,value用sha1生成唯一标识,加锁后必须显式unlock并校验返回值,重试应设指数退避与熔断,时钟同步误差须<100ms。

Webman里怎么用Redlock而不是单机Redis锁
单机Redis锁在Webman多实例部署下必然失效——Nginx负载到两个PHP进程,SETNX各自对不同Redis连接生效,根本互斥不了。Redlock本质是向≥3个独立Redis节点(不能是主从)并发尝试加锁,多数节点成功才算持有锁。Webman作为常驻内存框架,必须传入连接池实例,不能每次new Redis()新建连接。
关键点:
- Redlock不是“一个Redis实例+集群模式”,而是多个物理隔离的Redis服务(比如
127.0.0.1:6380、127.0.0.1:6381、127.0.0.1:6382),且不共用主从关系 - Webman中需在
bootstrap/app.php或容器绑定时初始化RedLock客户端,并注入连接池(如Predis\Client数组) - 官方
redlock-php库要求每个连接是独立Predis\Client实例,不能复用Webman默认的Redis::connection() - 锁key必须带业务上下文标识,例如
sync_order:20260729,避免不同任务互相干扰
为什么task:run --name=sync_order不能靠uniqid()或getmypid()加锁
Webman的task:run命令每次启动都是新进程,uniqid()生成的ID不跨进程可比,getmypid()在子进程里每次都不一样——这意味着你根本没法安全释放锁,也没法判断锁是否已被自己持有。
真正可行的做法:
- 锁value必须是全局唯一且可验证的标识,推荐用
sha1(microtime(true) . rand(1000, 9999))生成,写入锁时存入,解锁时严格比对 - 加锁时用
RedLock::create($servers)->lock($resource, $ttl, $retryDelay, $retryCount),其中$resource就是sync_order:20260729这类key - 业务逻辑执行完必须调用
$lock->unlock(),不能依赖TTL自动过期——否则可能在临界区被其他实例抢入 - 务必检查
unlock()返回值,false代表锁已失效或不属于当前实例,此时应记录告警而非静默忽略
RedLock::lock()失败后要不要重试?重试策略怎么设
Redlock设计上就允许短暂失败,但盲目sleep(100)再重试会拖慢整个任务调度。实际生产中,重试必须带退避和熔断。
建议配置:
-
$retryCount = 3:最多尝试3次,超过即认为资源不可用,直接抛异常或跳过本次任务 -
$retryDelay = 50(毫秒):首次等待50ms,后续按指数退避(50 → 100 → 200) - 每次重试前检查
date('Ymd') !== '20260729'——如果日期已变,说明是昨天遗留的锁,应主动清理旧key再重试 - 若连续两次
lock()返回null(代表超时未获取到多数节点同意),立即终止,避免阻塞队列
Webman里锁没释放干净导致第二天任务卡住怎么办
最常见原因是进程崩溃或exit()前没调用unlock(),导致锁残留。Redlock本身不提供自动清理机制,得靠运维手段兜底。
实操补救步骤:
- 手动用
redis-cli -p 6380 keys "sync_order:*"查出所有待清理key - 对每个key执行
redis-cli -p 6380 get <code>sync_order:20260728确认value是否为已下线实例的标识 - 确认后用
redis-cli -p 6380 del <code>sync_order:20260728删除(注意:必须逐个del,不能keys * | xargs del,会阻塞Redis) - 长期方案:在Webman的
onWorkerStop事件里注册钩子,遍历本地持有的RedLock实例并强制unlock()
Redlock的可靠性高度依赖时间同步和网络稳定性,Webman部署机必须开启chronyd校时,且各Redis节点间时钟差不能超过100ms,否则会出现“假释放”或“假持有”。这点容易被忽略,但一旦出问题,日志里只显示“锁获取失败”,根本看不出是时钟漂移导致的。


















