PHP+redis实现的悲观锁机制示例
本篇章节讲解PHP+redis实现的悲观锁。分享给大家供大家参考,具体如下: 锁机制 通常使用的锁分为乐观锁,悲观锁这两种,简单介绍下这两种锁,作为本文的背景知识,对这类知识已经有足够了解的同学可以跳过这部分。 乐观锁
其实说白了,就是好比一个健身房里只有一台跑步机,在健身房门口有个排号机,每个进健身房的人都得先领一个号码才能进入,如果跑步机上有人,则在一边做做热身、喝喝水,如果跑步机上没人,则确认跑步机上当前显示的号码(上一个用过跑步机的人的号码)是否比自己手持的小,如果小,则可以使用;否则,就意味着过号,而过号在现实中我们的都知道要么走,要么重排,就是不能插队,在系统中也是一样的,通常是返回错误。 悲观锁
然后,也同样通俗的解释下,还是那个健身房。这次在门口不需要排号机了,而是挂着把钥匙(只有一把),想进去的人必须拿到这把钥匙才行,拿到钥匙的人可以进入,不管是热身、喝水还是跑步都可以,直到他出来把钥匙挂回墙上,下一个才能去争取,拿到的才可以再进去。听着好像有点不人性化,所以悲观锁比较适合强一致性的场景,但效率比较低,特别是读的并发低。乐观锁则适用于读多写少,并发冲突少的场景。 背景 先说下,本文的开发背景,方便大家了解为什么要使用悲观锁以及文中锁的详细设计。 任务分发系统:任务池(mysql)中存在大量任务(文章),现在需要用户协助编辑,系统基本需求如下(简化版): 1、推送用户感兴趣的分类下的任务到用户编辑器中; 2、用户编辑提交一个任务后,自动推送下一个任务; 3、每次只分配一个任务给用户; 4、如果一个用户占有某任务超过一定时间,则自动释放任务,任务进任务池,重新循环; 5、…… 目标 目标有两个: 1、一个任务在同一时间段内只能被一个用户所持有; 2、避免出现死任务,即避免任务被用户长时间占有,无法释放。 思路 由于系统并发量较大,并且有频繁的写操作,所以选择悲观锁来控制每个任务只能同时被一个用户领取。主要思路如下: 1、从任务池中找出一部分可分配的任务; 2、根据一定顺序,选择一个任务,作为候选推送任务; 3、尝试对候选推送任务加锁; 4、如果加锁成功,则推送任务给用户,并修改对应的任务状态和用户状态; 5、如果加锁失败,则任务已被领取,重复2-5,直到推送成功。 实现 这里只介绍下锁的实现机制,其余业务逻辑略过。由于加锁过程应该是不可拆解的,也就是常说的原子型操作,因此这里选择redis中的setnx操作作为加锁的方法。 简化版的代码如下: setnx($strMutex,1);
if ($intRet) {
//设置过期时间,防止死任务的出现
$objRedis->expire($strMutex,$intTimeout);
return true;
}
return false;
}
这段代码有个问题,就是setnx成功,但expire失败,这就可能存在死任务的情况。解决这个问题的一种通用方法是通过使用incr方法代替setnx,具体如下: incr($strMutex);
if ($intRet === 1) {
//设置过期时间,防止死任务的出现
$objRedis->expire($strMutex,$intTimeout);
return true;
}
if ($intMaxTimes > 0 && $intRet >= $intMaxTimes && $objRedis->ttl($strMutex) === -1) {
//当设置了最大加锁次数时,如果尝试加锁次数大于最大加锁次数并且无过期时间则强制解锁
$objRedis->del($strMutex);
}
return false;
}
这段代码通过
还有没有更好的方法呢? 其实redis中的set操作已兼容了setnx,并且支持设置过期时间。 这个方法是我认为目前最好的,但是为什么没有直接介绍这个方法,而是先介绍incr那个方法呢?其实细心的同学可以看到上面那个方面有两个加粗的字”通用“。之所以这么说是因为set方法是从redis2.6.12版本才开始支持多参数的。 水平有限,欢迎指正~ 参考资料:http://redisdoc.com/string/set.html 更多关于PHP相关内容感兴趣的读者可查看本站专题:《》、《》、《》、《》、《》、《》及《》 希望本文所述对大家PHP程序设计有所帮助。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |