本文目录导读:

在 PHP 中实现加锁超时放弃策略,主要有以下几种方案,我会逐一详细讲解:
文件锁(flock)
基础实现
<?php
class FileLock {
private $fp;
private $lockFile;
public function __construct($lockFile = '/tmp/lockfile.lock') {
$this->lockFile = $lockFile;
}
/**
* 获取锁(带超时)
* @param float $timeout 超时时间(秒)
* @param int $blocking 是否阻塞
* @return bool 是否获取成功
*/
public function acquire($timeout = 5, $blocking = true) {
$this->fp = fopen($this->lockFile, 'c');
if (!$blocking) {
return flock($this->fp, LOCK_EX | LOCK_NB);
}
$startTime = microtime(true);
// 循环尝试获取锁,直到超时
while (true) {
if (flock($this->fp, LOCK_EX | LOCK_NB)) {
return true;
}
// 检查是否超时
if (microtime(true) - $startTime >= $timeout) {
fclose($this->fp);
return false;
}
// 短暂休眠,避免CPU空转
usleep(100000); // 100ms
}
}
/**
* 释放锁
*/
public function release() {
if ($this->fp) {
flock($this->fp, LOCK_UN);
fclose($this->fp);
}
}
}
// 使用示例
$lock = new FileLock('/tmp/data.lock');
if ($lock->acquire(5)) { // 5秒超时
try {
// 执行需要加锁的操作
file_put_contents('/tmp/data.txt', 'new data');
echo "操作成功\n";
} finally {
$lock->release();
}
} else {
echo "获取锁超时,任务放弃\n";
}
Redis 锁(SET NX EX)
<?php
class RedisLock {
private $redis;
private $prefix = 'lock:';
public function __construct($host = '127.0.0.1', $port = 6379) {
$this->redis = new Redis();
$this->redis->connect($host, $port);
}
/**
* 获取分布式锁
* @param string $key 锁的名称
* @param int $timeout 锁的自动过期时间(秒)
* @param int $waitTimeout 等待获取锁的超时时间(秒)
* @param string $token 唯一标识,防止误删
* @return bool|string 成功返回token,失败返回false
*/
public function acquire($key, $timeout = 30, $waitTimeout = 5) {
$token = uniqid('lock_', true);
$lockKey = $this->prefix . $key;
$startTime = microtime(true);
while (true) {
// 使用SET NX EX命令原子设置
$result = $this->redis->set($lockKey, $token, ['NX', 'EX' => $timeout]);
if ($result) {
return $token;
}
// 检查是否超时
if (microtime(true) - $startTime >= $waitTimeout) {
return false;
}
usleep(50000); // 50ms后重试
}
}
/**
* 释放分布式锁
* @param string $key 锁的名称
* @param string $token 之前获取的token
*/
public function release($key, $token) {
$lockKey = $this->prefix . $key;
// 使用Lua脚本确保原子性
$script = <<<LUA
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
LUA;
return $this->redis->eval($script, [$lockKey, $token], 1);
}
/**
* 带自动重试的锁使用
*/
public function withLock($key, $callback, $timeout = 30, $waitTimeout = 5) {
$token = $this->acquire($key, $timeout, $waitTimeout);
if (!$token) {
return false;
}
try {
return $callback();
} finally {
$this->release($key, $token);
}
}
}
// 使用示例
$redisLock = new RedisLock();
$result = $redisLock->withLock(
'payment:order:1001',
function() {
// 执行关键业务
return "处理订单成功";
},
30, // 锁的过期时间
5 // 等待超时时间
);
if ($result === false) {
echo "获取锁超时,请稍后重试\n";
}
数据库锁(悲观锁)
<?php
class DatabaseLock {
private $pdo;
public function __construct($dsn, $username, $password) {
$this->pdo = new PDO($dsn, $username, $password);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
/**
* 获取数据库行锁
* @param int $id 记录ID
* @param int $timeout 超时时间(秒)
* @return bool
*/
public function acquireLock($id, $timeout = 5) {
try {
// 设置锁等待超时时间
$this->pdo->exec("SET innodb_lock_wait_timeout = $timeout");
// 开始事务
$this->pdo->beginTransaction();
// 使用 SELECT ... FOR UPDATE 获取行锁
$stmt = $this->pdo->prepare(
"SELECT id FROM resources WHERE id = :id FOR UPDATE"
);
$stmt->execute(['id' => $id]);
if ($stmt->rowCount() === 0) {
$this->pdo->rollBack();
return false;
}
return true;
} catch (Exception $e) {
// 超时或获取失败
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
return false;
}
}
/**
* 释放锁(提交事务)
*/
public function releaseLock() {
$this->pdo->commit();
}
}
信号量锁(System V Semaphore)
<?php
class SemaphoreLock {
private $sem;
private $key;
public function __construct($key = 12345, $maxLocks = 1) {
$this->key = $key;
$this->sem = sem_get($key, $maxLocks);
}
/**
* 获取信号量锁
* @param int $timeout 超时时间(秒)
* @return bool
*/
public function acquire($timeout = 5) {
// sem_acquire 不支持超时,需要自己实现
$startTime = microtime(true);
while (true) {
// 非阻塞获取
if (sem_acquire($this->sem, false)) {
return true;
}
// 检查超时
if (microtime(true) - $startTime >= $timeout) {
return false;
}
usleep(100000); // 100ms
}
}
/**
* 释放信号量锁
*/
public function release() {
sem_release($this->sem);
}
public function __destruct() {
if (isset($this->sem)) {
sem_remove($this->sem);
}
}
}
高级封装 - 通用锁类
<?php
interface LockInterface {
public function acquire($key, $timeout = 5);
public function release($key, $token = null);
public function withLock($key, $callback, $timeout = 5);
}
abstract class AbstractLock implements LockInterface {
protected $timeout = 5;
protected $retryInterval = 100000; // 100ms
public function withLock($key, $callback, $timeout = 5) {
$token = $this->acquire($key, $timeout);
if (!$token) {
throw new Exception("获取锁超时: $key");
}
try {
return $callback();
} catch (Exception $e) {
throw $e;
} finally {
$this->release($key, $token);
}
}
protected function sleepBeforeRetry() {
usleep($this->retryInterval);
}
}
// Redis实现
class RedisLockImpl extends AbstractLock {
private $redis;
public function __construct($redis) {
$this->redis = $redis;
}
public function acquire($key, $timeout = 5) {
$token = uniqid('', true);
$startTime = microtime(true);
while (true) {
if ($this->redis->set($key, $token, ['NX', 'EX' => $timeout])) {
return $token;
}
if (microtime(true) - $startTime >= $timeout) {
return false;
}
$this->sleepBeforeRetry();
}
}
public function release($key, $token = null) {
if (!$token) return false;
$script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
return $this->redis->eval($script, [$key, $token], 1);
}
}
// 使用
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lock = new RedisLockImpl($redis);
try {
$result = $lock->withLock(
'inventory:product:1001',
function() {
// 更新库存
return "库存更新成功";
},
10 // 10秒超时
);
echo $result;
} catch (Exception $e) {
echo "操作失败: " . $e->getMessage();
}
最佳实践建议
选择合适的超时时间
$timeout = 5; // 一般任务 $timeout = 30; // 长时间任务 $timeout = 0; // 有冲突就立即放弃
实现优雅降级
function executeWithFallback($lock, $key, $task, $timeout = 5) {
try {
return $lock->withLock($key, $task, $timeout);
} catch (TimeoutException $e) {
// 超时处理策略
// 1. 重试
// 2. 返回错误
// 3. 使用旧数据
// 4. 排队等待
return ['success' => false, 'message' => '系统繁忙,请稍后重试'];
}
}
监控和日志记录
class MonitoredLock extends RedisLockImpl {
protected function logLockEvent($event, $key, $duration) {
// 记录到日志系统
error_log("[LOCK] $event $key took $duration ms");
}
public function acquire($key, $timeout = 5) {
$start = microtime(true);
$result = parent::acquire($key, $timeout);
$this->logLockEvent(
$result ? 'ACQUIRED' : 'TIMEOUT',
$key,
(microtime(true) - $start) * 1000
);
return $result;
}
}
性能优化建议
- 缩短重试间隔(10-50ms)
- 使用随机退避算法避免惊群效应
- 考虑使用 Redisson 等成熟组件
public function acquireWithExponentialBackoff($key, $maxTimeout = 5) {
$baseDelay = 10; // 10ms
$maxDelay = 500; // 500ms
$attempt = 0;
$startTime = microtime(true);
while (true) {
if ($this->attemptAcquire($key)) {
return true;
}
if (microtime(true) - $startTime >= $maxTimeout) {
return false;
}
$delay = min($baseDelay * pow(2, $attempt), $maxDelay);
usleep($delay * 1000);
$attempt++;
}
}
选择哪种方案取决于你的具体需求:
- 单机应用:文件锁
- 分布式应用:Redis锁
- 强一致性:数据库锁
- 性能需求高:Redis或内存锁
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播