PHP+redis实现的悲观锁机制示例


Posted in PHP onJune 12, 2018

本文实例讲述了PHP+redis实现的悲观锁。分享给大家供大家参考,具体如下:

锁机制

通常使用的锁分为乐观锁,悲观锁这两种,简单介绍下这两种锁,作为本文的背景知识,对这类知识已经有足够了解的同学可以跳过这部分。

乐观锁

先来看下百度百科上的解释:大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

其实说白了,就是好比一个健身房里只有一台跑步机,在健身房门口有个排号机,每个进健身房的人都得先领一个号码才能进入,如果跑步机上有人,则在一边做做热身、喝喝水,如果跑步机上没人,则确认跑步机上当前显示的号码(上一个用过跑步机的人的号码)是否比自己手持的小,如果小,则可以使用;否则,就意味着过号,而过号在现实中我们的都知道要么走,要么重排,就是不能插队,在系统中也是一样的,通常是返回错误。

悲观锁

同样,来看下百度百科的解释:具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

然后,也同样通俗的解释下,还是那个健身房。这次在门口不需要排号机了,而是挂着把钥匙(只有一把),想进去的人必须拿到这把钥匙才行,拿到钥匙的人可以进入,不管是热身、喝水还是跑步都可以,直到他出来把钥匙挂回墙上,下一个才能去争取,拿到的才可以再进去。听着好像有点不人性化,所以悲观锁比较适合强一致性的场景,但效率比较低,特别是读的并发低。乐观锁则适用于读多写少,并发冲突少的场景。

背景

先说下,本文的开发背景,方便大家了解为什么要使用悲观锁以及文中锁的详细设计。

任务分发系统:任务池(mysql)中存在大量任务(文章),现在需要用户协助编辑,系统基本需求如下(简化版):

1、推送用户感兴趣的分类下的任务到用户编辑器中;
2、用户编辑提交一个任务后,自动推送下一个任务;
3、每次只分配一个任务给用户;
4、如果一个用户占有某任务超过一定时间,则自动释放任务,任务进任务池,重新循环;
5、……

目标

目标有两个:

1、一个任务在同一时间段内只能被一个用户所持有;

2、避免出现死任务,即避免任务被用户长时间占有,无法释放。

思路

由于系统并发量较大,并且有频繁的写操作,所以选择悲观锁来控制每个任务只能同时被一个用户领取。主要思路如下:

1、从任务池中找出一部分可分配的任务;
2、根据一定顺序,选择一个任务,作为候选推送任务;
3、尝试对候选推送任务加锁;
4、如果加锁成功,则推送任务给用户,并修改对应的任务状态和用户状态;
5、如果加锁失败,则任务已被领取,重复2-5,直到推送成功。

实现

这里只介绍下锁的实现机制,其余业务逻辑略过。由于加锁过程应该是不可拆解的,也就是常说的原子型操作,因此这里选择redis中的setnx操作作为加锁的方法。

简化版的代码如下:

function lock($strMutex, $intTimeout) {
  $objRedis = new Redis();
  //使用setnx原子型操作加锁
  $intRet  = $objRedis->setnx($strMutex, 1);
  if ($intRet) {
    //设置过期时间,防止死任务的出现
    $objRedis->expire($strMutex, $intTimeout);
    return true;
  }
  return false;
}

这段代码有个问题,就是setnx成功,但expire失败,这就可能存在死任务的情况。解决这个问题的一种通用方法是通过使用incr方法代替setnx,具体如下:

function lock($strMutex, $intTimeout, $intMaxTimes = 0) {
  $objRedis = new Redis();
  //使用incr原子型操作加锁
  $intRet  = $objRedis->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;
}

这段代码通过$intMaxTimes来保证即使在expire未成功的时候也能强制解锁,保证系统不会出现死任务。

还有没有更好的方法呢?

其实redis中的set操作已兼容了setnx,并且支持设置过期时间。

function lock($strMutex, $intTimeout) {
  $objRedis = new Redis();
  //使用setnx操作加锁,同时设置过期时间
  $strRet  = $objRedis->set($strMutex, 1, 'ex', $intTimeout, 'nx');
  if ($strRet === 'OK') {
    return true;
  }
  return false;
}

这个方法是我认为目前最好的,但是为什么没有直接介绍这个方法,而是先介绍incr那个方法呢?其实细心的同学可以看到上面那个方面有两个加粗的字”通用“。之所以这么说是因为set方法是从redis2.6.12版本才开始支持多参数的。

水平有限,欢迎指正~

参考资料:http://redisdoc.com/string/set.html

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
用PHP产生动态的影像图
Oct 09 PHP
PHP 读取文件内容代码(txt,js等)
Dec 06 PHP
使用Apache的htaccess防止图片被盗链的解决方法
Apr 27 PHP
CodeIgniter上传图片成功的全部过程分享
Aug 12 PHP
PHP实现显示照片exif信息的方法
Jul 11 PHP
从零开始学YII2框架(二)通过 Composer 安装扩展插件
Aug 20 PHP
PHP中array_slice函数用法实例详解
Nov 25 PHP
php从memcache读取数据再批量写入mysql的方法
Dec 29 PHP
详解HTTP Cookie状态管理机制
Jan 14 PHP
php正则表达式验证(邮件地址、Url地址、电话号码、邮政编码)
Mar 14 PHP
Ubuntu server 11.04安装memcache及php使用memcache来存储session的方法
May 31 PHP
ThinkPHP 5.x远程命令执行漏洞复现
Sep 23 PHP
thinkPHP5框架auth权限控制类与用法示例
Jun 12 #PHP
thinkPHP5框架实现基于ajax的分页功能示例
Jun 12 #PHP
Laravel框架路由和控制器的绑定操作方法
Jun 12 #PHP
Laravel框架路由设置与使用示例
Jun 12 #PHP
Laravel框架生命周期与原理分析
Jun 12 #PHP
Laravel框架分页实现方法分析
Jun 12 #PHP
php 可变函数使用小结
Jun 12 #PHP
You might like
成本8450万,票房仅2亿,口碑两极分化,又一部DC电影扑街了
2020/04/09 欧美动漫
PHP小技巧搜集,每个PHPer都来露一手
2007/01/02 PHP
php表单转换textarea换行符的方法
2010/09/10 PHP
php面向对象 字段的声明与使用
2012/06/14 PHP
php中动态变量用法实例
2015/06/10 PHP
Smarty简单生成表单元素的方法示例
2016/05/23 PHP
ThinkPHP下表单令牌错误与解决方法分析
2017/05/20 PHP
JavaScript 快捷键设置实现代码
2009/03/13 Javascript
javascript下对于事件、事件流、事件触发的顺序随便说说
2010/07/17 Javascript
jquery autocomplete自动完成插件的的使用方法
2010/08/07 Javascript
js 时间格式与时间戳的相互转换示例代码
2013/12/25 Javascript
jquery插件开发之实现jquery手风琴功能分享
2014/03/10 Javascript
javascript数组操作(创建、元素删除、数组的拷贝)
2014/04/07 Javascript
javascript实现在某个元素上阻止鼠标右键事件的方法和实例
2014/08/12 Javascript
js格式化时间小结
2014/11/03 Javascript
JavaScript与HTML的结合方法详解
2015/11/23 Javascript
JavaScript转换与解析JSON方法实例详解
2015/11/24 Javascript
Bootstrap4一次重大更新 几乎涉及每行代码
2016/05/16 Javascript
DOM事件探秘篇
2017/02/15 Javascript
深入理解Commonjs规范及Node模块实现
2017/05/17 Javascript
bootstrap switch开关组件使用方法详解
2017/08/22 Javascript
vue项目中实现的微信分享功能示例
2019/01/21 Javascript
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧不动,右侧滑动)
2019/01/23 Javascript
JavaScript实现文件下载并重命名代码实例
2019/12/12 Javascript
vue+iview实现分页及查询功能
2020/11/17 Vue.js
[04:28]DOTA2亚洲邀请赛小组赛第五日 TOP10精彩集锦
2015/02/03 DOTA
Python Sleep休眠函数使用简单实例
2015/02/02 Python
python基于C/S模式实现聊天室功能
2019/01/09 Python
Python GUI库PyQt5图形和特效样式QSS介绍
2020/02/25 Python
python爬虫请求头设置代码
2020/07/28 Python
OpenCV利用python来实现图像的直方图均衡化
2020/10/21 Python
小学语文业务学习材料
2014/06/02 职场文书
化工实习心得体会
2014/09/09 职场文书
工作作风整顿个人剖析材料
2014/10/11 职场文书
如何在CocosCreator里画个炫酷的雷达图
2021/04/16 Javascript
Oracle 11g数据库使用expdp每周进行数据备份并上传到备份服务器
2022/06/28 Oracle