<?php
class Rediss {
private static $_instance = null;
private function __construct(){
self::$_instance = new \Redis();
$config = [
'host' => '127.0.0.1',
'port' => 6379
];
self::$_instance->connect($config['host'],$config['port']);
if(isset($config['password'])){
self::$_instance->auth($config['password']);
}
}
public static function instance(){
if(!self::$_instance){
new self;
}
return self::$_instance;
}
private function __clone(){}
}
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;
/**
* 每次取锁间隔毫秒数1-1000000
*/
const GET_LOCK_SLEEP_MICRO_SECONDS = 10;
/**
* @var mixed redis 实例
*/
private $redisIns;
/**
* @var string 锁名
*/
private $lockName;
/**
* @var int 超时时间,不可超过 self::MAX_TIMEOUT_SETTING
*/
private $timeout;
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);
}
}
class test {
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实例
//$aa = Rediss::instance();
$processRedisLock = new ProcessRedisLock(Rediss::instance(), $lockName, $timeout);
$this->echoAndSaveInfo($queryName, "try to get lock,timeover is {$timeout} seconds");
// 取锁
$id = $processRedisLock->lock();
// 如果到了超时时间还未取到会返回false,则直接抛异常
if($id === false){
throw new Exception('get Lock err');
}
// $this->echoAndSaveInfo($queryName, "get Lock success Id is:".$id);
// 模拟耗时操作
sleep($handlerTime);
// 释放锁
$processRedisLock->unlock($id);
// $this->echoAndSaveInfo($queryName, "lockOut lock");
} 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);
}
}
//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
$a = new test();
$a->requestTask('mytest',0.1,'locks',5);//锁名,模拟中间业务时间,