<?php
class ProcessRedisLock
{
/**
* redis key 前缀
*/
const KEY_PREFIX = 'PROCESS_REDIS_LOCK:'; /**
* 默认超时时间(秒)
*/
const DEFAULT_TIMEOUT = 5; /**
* 最大超时时间(秒)
*/
const MAX_TIMEOUT_SETTING = 60; /**
* 随机数的最小值
*/
const MIN_RAND_NUM = 0; /**
* 随机数的最大值
*/
const RAND_MAX_NUM = 100000; /**
* 每次取锁间隔毫秒数
*/
const GET_LOCK_SLEEP_MICRO_SECONDS = 0.1; /**
* @var mixed redis 实例
*/
private $redisIns; /**
* @var string 锁名
*/
private $lockName; /**
* @var int 超时时间,不可超过 self::MAX_TIMEOUT_SETTING
*/
private $timeout; /*
单元测试步骤
--------------------------------------------------------------------------------------
1.分别用一个chrome和一个ie,模拟并发请求:
1)http://xxx/test?queryName=task1&handlerTime=10&lockName=myLocktest&timeout=10
2)http://xxx/test?queryName=task2&handlerTime=10&lockName=myLocktest&timeout=5
2.如上
task1处理耗时10s,取锁超时时间10s
task2处理耗时10s,取锁超时时间5s
3.日志结果
2019-11-22 17:16:30 [task1]: 尝试取锁,超时时间设定10秒
2019-11-22 17:16:30 [task1]: 获取到锁,唯一标志:PROCESS_REDIS_LOCK:5dd7a76e1bafe37915
2019-11-22 17:16:32 [task2]: 尝试取锁,超时时间设定5秒
2019-11-22 17:16:35 [task1]: 释放锁
2019-11-22 17:16:35 [task2]: 获取到锁,唯一标志:PROCESS_REDIS_LOCK:5dd7a770d0c2b41355
2019-11-22 17:16:45 [task2]: 释放锁
-------------------------------------------------------------------------------------- 单元测试代码
--------------------------------------------------------------------------------------
// 浏览器模拟并发请求
// 注意:这里测试的时候如果session以文件存储的话,要避免两个窗口共用一个会话id,因为同样的会话id在session_start()时会锁文件
// 这样会造成请求阻塞,模拟不了请求并发的情况,所以应该使用两个不同的浏览器(如一个chrome,一个firefox)(同样的浏览器共享cookie也会导致拿到同样的会话id)
public function testAction()
{
$params = $this->getRequest()->getParams();
$this->requestTask($params['queryName'], $params['handlerTime'], $params['lockName'], $params['timeout']);
} // 模拟取锁、耗时操作、释放锁
public function requestTask($queryName, $handlerTime, $lockName, $timeout)
{
try {
// 获取redis实例
$processRedisLock = new ProcessRedisLock(redis(), $lockName, $timeout); $this->echoAndSaveInfo($queryName, "尝试取锁,超时时间设定{$timeout}秒"); // 取锁
$id = $processRedisLock->lock();
// 如果到了超时时间还未取到会返回false,则直接抛异常
if($id === false){
throw new Exception('获取锁失败');
} $this->echoAndSaveInfo($queryName, "获取到锁,唯一标志:".$id); // 模拟耗时操作
sleep($handlerTime); // 释放锁
$processRedisLock->unlock($id);
$this->echoAndSaveInfo($queryName, "释放锁");
} catch (Exception $e) {
$this->echoAndSaveInfo($queryName, $e->getMessage());
// do something
}
} // 输出并且记录日志
public function echoAndSaveInfo($queryName, $content)
{
$info = date('Y-m-d H:i:s') . " [{$queryName}]: {$content}" . PHP_EOL;
echo $info;
file_put_contents('test.txt', $info . PHP_EOL, FILE_APPEND);
}
--------------------------------------------------------------------------------------
*/ /**
* ProcessRedisLock constructor.
* @param $redisIns
* @param $lockName
* @param int $timeout
* @throws Exception
*/
public function __construct($redisIns, $lockName, $timeout = self::DEFAULT_TIMEOUT)
{
if (!$redisIns) {
new Exception('The redis instance is empty');
} if(!$lockName){
throw new Exception('Lock name invalid');
} // 校验超时时间
$timeout = intval($timeout);
if (!($timeout > 0 && $timeout <= self::MAX_TIMEOUT_SETTING)) {
throw new Exception('The timeout interval is (0,' . self::MAX_TIMEOUT_SETTING . ']');
} $this->redisIns = $redisIns;
$this->lockName = $lockName;
$this->timeout = $timeout;
} /**
* 加锁
* @return bool
* @Date 2019/11/22
*/
public function lock()
{
// redis key
$key = $this->getRedisKey(); // 唯一标志
$id = $this->getId(); // 超时时间
$endTime = time() + $this->timeout; // 循环取锁
while (time() < $endTime) {
// 尝试加锁,若给定的 key 已经存在,则 SETNX 不做任何动作。
if ($this->redisIns->setnx($key, $id)) {
// 设置过期时间,防止程序异常退出没有解锁导致死锁
$this->redisIns->expire($key, $this->timeout);
// 返回唯一标志,用于解锁
return $id;
}
usleep(self::GET_LOCK_SLEEP_MICRO_SECONDS);
} return false;
} /**
* 解锁
* @param string $id 唯一标志,加锁成功时返回
* @return bool
* @Date 2019/11/22
*/
public function unlock($id)
{
$key = self::getRedisKey(); // 如果锁的值与没有被修改
if ($this->redisIns->get($key) == $id) {
// 开始事务
$this->redisIns->multi();
// 释放该锁
$this->redisIns->del($key);
// 执行
$this->redisIns->exec();
return true;
} else {
return false;
}
} /**
* 获取redis key
* @return string
* @Date 2019/11/22
*/
public function getRedisKey()
{
return self::KEY_PREFIX . $this->lockName;
} /**
* 获取唯一标志位
* @Date 2019/11/22
*/
public function getId()
{
return uniqid(self::KEY_PREFIX) . mt_rand(self::MIN_RAND_NUM, self::RAND_MAX_NUM);
}
}

PHP 基于redis的分布式锁的更多相关文章

  1. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  2. 基于redis的分布式锁

    <?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...

  3. 基于Redis的分布式锁真的安全吗?

    说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...

  4. 基于 Redis 的分布式锁

    前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...

  5. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  6. 基于redis的分布式锁实现

    1.分布式锁介绍 在计算机系统中,锁作为一种控制并发的机制无处不在. 单机环境下,操作系统能够在进程或线程之间通过本地的锁来控制并发程序的行为.而在如今的大型复杂系统中,通常采用的是分布式架构提供服务 ...

  7. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  8. 基于 redis 的分布式锁实现 Distributed locks with Redis debug 排查错误

    小结: 1. 锁的实现方式,按照应用的实现架构,可能会有以下几种类型: 如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访 ...

  9. 转载:基于Redis实现分布式锁

    转载:基于Redis实现分布式锁  ,出处: http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如 ...

  10. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

随机推荐

  1. 《原创视频》牛腩学docker简记

    牛腩学docker简记 http://blog.niunan.net/blog/show/1258https://www.cnblogs.com/niunan/p/10917506.htmlhttps ...

  2. Git采坑与问题排查

    目录 Case1:代码库中存大文件 Case1:代码库中存大文件 背景 有这种情况,项目运行需要依赖一个文件(比如需要读一个文件中的数据),那么最直接的方式就是将该文件添加到代码中的分支中,然后将分支 ...

  3. PRINT_INITA整体偏移值传变量

    打印维护和整体偏移相关博文:打印维护和整体偏移相关简短问答.打印维护开放给客户端可进行微调,结果在客户端本地.(建议开放给客户打印维护功能,调整一次,以后都会走调整后的,通过不同任务名区分模版) 如果 ...

  4. web基础---->script标签的特殊使用

    今天要讲的就是怎样使用<script>去请求一个servlet,加载一些js资源以及额外的逻辑处理: 目录: JS的引入的几种方式 在script的标签中引入Servlet 动态引入JS的 ...

  5. 将SpringCloud Eureka 服务注册与发现部署到docker

    一.前言 最近在学习docker,顺便把之前学习的spring cloud 部署到Docker 中.至于什么是SpringCloud的服务注册与发现,什么是docker,我这里就不作赘述了.可以先去学 ...

  6. PHP防止被重复请求接口的方法(网页端签名验证的方法)

    PHP防止被重复请求接口的方法(网页端签名验证的方法) 可以采取签名验证的方式来 解决这个问题 1 time和随机数都是PHP生成的显示在前端 2 前端生成sign 3 进行每次请求的sign计数 这 ...

  7. 基于Livy的Spark提交平台搭建与开发

    为了方便使用Spark的同学提交任务以及加强任务管理等原因,经调研采用Livy比较靠谱,下图大致罗列一下几种提交平台的差别. 本文会以基于mac的单机环境搭建一套Spark+Livy+Hadoop来展 ...

  8. 027 SSM综合练习03--数据后台管理系统--product-list.jsp和main.jsp页面制作

    1.product-list.jsp页面制作 (1)创建一个product-list1.jsp文件,清空,只保留 <%@ page contentType="text/html;cha ...

  9. Appium移动端测试--基础预热

    目录 Android自动化环境准备 需要安装的软件: Appium多端架构与自动化 Android自动化前提依赖: 获取App的信息: Android常用命令 adb shell 常用命令列表: An ...

  10. emmet 配置文件

    snippets.json(添加自己的或更新现有的片段) preferences.json(更改某些Emmet过滤器和操作的行为) SyntaxProfiles.json(定义生成的HTML / XM ...