Created
November 3, 2014 14:20
-
-
Save kawa-/4dc2b8a9c1bd018abc32 to your computer and use it in GitHub Desktop.
ソースに書いてあるURLにあるRedisのLockを参考にPHP版を作ろうとしたが失敗したけれど捨てがたいからスニペットにしておいた
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// http://ameblo.jp/principia-ca/entry-11770810115.html | |
// ここ↑にはJavaのRedisのLockが書いてあり、それを参考にPHP版を作ろうとしたが上手く行かず。 | |
// try {} finallyとか、exceptionがなんかだめな感じ(´・ω・`) | |
define('LOCK_KEY_PREFIX', 'lock:'); | |
define('LOCK_EXPIRE_SEC', 30); | |
define('LOCK_SLEEP_MILL', 10); | |
class RedisLockTimeoutException extends Exception { | |
public function __construct() { | |
parent::__construct('Redis lock time out.', -1); | |
} | |
} | |
class RedisLock { | |
private $redis; | |
private $lockKey; | |
private $lockMills; | |
private $isLocked = FALSE; | |
private $lockedValue = ''; | |
function __construct(Redis $redis, $lockKey, $lockMills) { | |
$this->redis = $redis; | |
$this->lockKey = LOCK_KEY_PREFIX . $lockKey; | |
$this->lockMills = $lockMills; | |
} | |
public function lock() { | |
if ($this->isLocked === TRUE) { | |
return; | |
} | |
// ロック待機時間 | |
$max = round(microtime(true) * 1000) + $this->lockMills; | |
while (TRUE) { | |
$now = round(microtime(true) * 1000); | |
$value = (string) $now; | |
// ロック試行 | |
$result = $this->redis->setnx($this->lockKey, $value); | |
// ロックが成功したらexpireを設定してデッドロック防止 | |
if ($result === TRUE) { | |
$this->redis->expire($this->lockKey, LOCK_EXPIRE_SEC); | |
$this->isLocked = TRUE; | |
$this->lockedValue = $value; | |
break; | |
} | |
// ロック待機時間を過ぎたら終了 | |
if ($max <= $now) { | |
$locked = $this->redis->get($this->lockKey); | |
if ($locked === FALSE) { | |
$elaspedTime = $now - (int) $locked; | |
// expireが効いていなかったら削除してデッドロック防止 | |
if ($elaspedTime >= LOCK_EXPIRE_SEC * 1000) { | |
$this->redis->del($this->lockKey); | |
} | |
} | |
throw new RedisLockTimeoutException(); | |
} | |
// ロックできなかったらsleepして再試行 | |
usleep(LOCK_SLEEP_MILL * 1000); | |
} | |
} | |
public function unlock() { | |
if ($this->isLocked === FALSE) { | |
return; | |
} | |
// ロックの値を取得 | |
$value = $this->redis->get($this->lockKey); | |
// このインスタンスでロックされたものなら削除 | |
if ($this->lockedValue === $value) { | |
$this->redis->del($this->lockKey); | |
} | |
$this->isLocked = FALSE; | |
$this->lockedValue = ''; | |
} | |
} | |
class RedisLockFactory { | |
private $lockTime; | |
private $redis; | |
public function RedisLockFactory(Redis $redis, $lockTime) { | |
$this->redis = $redis; | |
$this->lockTime = $lockTime; | |
} | |
public function getLock($lockKey) { | |
return new RedisLock($this->redis, $lockKey, $this->lockTime); | |
} | |
} | |
$redis = new Redis(); | |
$redis->connect('127.0.0.1', 26379, 1.0); | |
$lockFactory = new RedisLockFactory($redis, 10000); | |
for ($i = 0; $i < 10; $i++) { | |
// ロック取得 | |
$lock = $lockFactory->getLock('hoge'); | |
// ロック実行 | |
$lock->lock(); | |
try { | |
// 排他制御を行いたい更新処理 | |
update(); | |
sleep(3); | |
} catch (RedisLockTimeoutException $exc) { | |
var_dump($exc); | |
} | |
$lock->unlock(); | |
sleep(1); | |
} | |
function update(){ | |
echo 'hoge'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment