本文目录导读:

在PHP项目中设置锁超时以防止死锁常驻,主要有以下几种方法:
文件锁超时设置
使用flock + 超时机制
class FileLock {
private $fp;
private $lockFile;
public function acquire($key, $timeout = 5) {
$this->lockFile = sys_get_temp_dir() . '/' . md5($key) . '.lock';
$this->fp = fopen($this->lockFile, 'c');
$start = time();
while (true) {
if (flock($this->fp, LOCK_EX | LOCK_NB)) {
return true;
}
if (time() - $start > $timeout) {
// 超时处理 - 强制释放旧锁
$this->forceRelease();
// 重试获取锁
if (flock($this->fp, LOCK_EX | LOCK_NB)) {
return true;
}
return false;
}
usleep(100000); // 等待100ms重试
}
}
public function release() {
if ($this->fp) {
flock($this->fp, LOCK_UN);
fclose($this->fp);
@unlink($this->lockFile);
}
}
private function forceRelease() {
// 检查锁文件是否过时
if (file_exists($this->lockFile)) {
$pid = file_get_contents($this->lockFile);
if ($pid && !posix_kill($pid, 0)) {
// 进程不存在,强制删除锁
@unlink($this->lockFile);
}
}
}
}
Redis分布式锁超时设置
使用Redis的SET命令+Lua脚本
class RedisLock {
private $redis;
private $lockKey;
private $lockValue;
private $ttl; // 锁过期时间(秒)
public function __construct($redis, $key, $ttl = 10) {
$this->redis = $redis;
$this->lockKey = 'lock:' . $key;
$this->lockValue = uniqid('', true); // 唯一标识
$this->ttl = $ttl;
}
public function acquire($timeout = 5) {
$start = microtime(true);
while (true) {
// SET NX EX(原子操作)
$result = $this->redis->set(
$this->lockKey,
$this->lockValue,
['NX', 'EX' => $this->ttl]
);
if ($result) {
return true;
}
// 检查是否超时
if ((microtime(true) - $start) > $timeout) {
return false;
}
usleep(100000); // 等待100ms
}
}
public function release() {
// 使用Lua脚本确保只有锁持有者才能释放
$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, [$this->lockKey, $this->lockValue], 1);
}
// 续期锁(防止业务处理时间超过锁过期时间)
public function extend($extraTtl = null) {
$ttl = $extraTtl ?: $this->ttl;
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('expire', KEYS[1], ARGV[2])
else
return 0
end
";
return $this->redis->eval($script, [$this->lockKey, $this->lockValue, $ttl], 1);
}
}
MySQL行锁超时设置
使用GET_LOCK()函数
class MySQLLock {
private $pdo;
private $lockName;
public function __construct($pdo, $name) {
$this->pdo = $pdo;
$this->lockName = 'lock_' . $name;
}
public function acquire($timeout = 5) {
// 设置MySQL锁等待超时
$this->pdo->exec("SET innodb_lock_wait_timeout = {$timeout}");
// 获取命名锁
$stmt = $this->pdo->prepare("SELECT GET_LOCK(:name, :timeout)");
$stmt->execute([
':name' => $this->lockName,
':timeout' => $timeout
]);
$result = $stmt->fetchColumn();
return $result == 1;
}
public function release() {
$stmt = $this->pdo->prepare("SELECT RELEASE_LOCK(:name)");
$stmt->execute([':name' => $this->lockName]);
return $stmt->fetchColumn() == 1;
}
// 设置全局超时
public static function setGlobalTimeout($pdo, $seconds = 5) {
$pdo->exec("SET GLOBAL innodb_lock_wait_timeout = {$seconds}");
$pdo->exec("SET SESSION lock_wait_timeout = {$seconds}");
}
}
高级防死锁方案
看门狗机制(自动续期)
class WatchdogLock {
private $redis;
private $lockKey;
private $lockValue;
private $ttl;
private $watchdogInterval;
private $running = false;
public function acquire($key, $ttl = 10, $timeout = 5) {
$this->lockKey = 'watchdog:' . $key;
$this->lockValue = uniqid('', true);
$this->ttl = $ttl;
$this->watchdogInterval = $ttl / 3; // 每1/3 TTL续期一次
$start = microtime(true);
while (true) {
if ($this->redis->set($this->lockKey, $this->lockValue, ['NX', 'EX' => $ttl])) {
// 启动看门狗
$this->startWatchdog();
return true;
}
if ((microtime(true) - $start) > $timeout) {
return false;
}
usleep(50000);
}
}
private function startWatchdog() {
$this->running = true;
// 使用popen或pcntl_fork启动后台进程
$pid = pcntl_fork();
if ($pid == -1) {
// fork失败,使用crontab或定时器方式
return false;
} elseif ($pid == 0) {
// 子进程执行续期
while ($this->running) {
sleep($this->watchdogInterval);
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
redis.call('expire', KEYS[1], ARGV[2])
return 1
end
return 0
";
$result = $this->redis->eval(
$script,
[$this->lockKey, $this->lockValue, $this->ttl],
1
);
if ($result == 0) {
// 锁丢失,退出
exit(0);
}
}
exit(0);
}
}
public function stopWatchdog() {
$this->running = false;
}
public function release() {
$this->stopWatchdog();
// 等待子进程退出
pcntl_wait($status, WNOHANG);
// 释放锁
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
";
return $this->redis->eval($script, [$this->lockKey, $this->lockValue], 1);
}
}
死锁检测和恢复
class DeadlockDetector {
private $redis;
private $lockRegistry;
public function __construct($redis) {
$this->redis = $redis;
$this->lockRegistry = 'deadlock:registry';
}
public function registerLock($lockKey, $processId, $ttl) {
$lockInfo = [
'pid' => $processId,
'acquired_at' => time(),
'ttl' => $ttl,
'host' => gethostname()
];
$this->redis->hSet($this->lockRegistry, $lockKey, json_encode($lockInfo));
$this->redis->expire($this->lockRegistry, 3600); // 1小时清理
}
public function detectDeadlocks() {
$locks = $this->redis->hGetAll($this->lockRegistry);
$deadlocks = [];
foreach ($locks as $key => $info) {
$lockInfo = json_decode($info, true);
if (time() - $lockInfo['acquired_at'] > $lockInfo['ttl']) {
// 检查进程是否还活着
if (!$this->isProcessAlive($lockInfo['pid'], $lockInfo['host'])) {
$deadlocks[] = $key;
}
}
}
return $deadlocks;
}
public function releaseDeadlocks($deadlocks) {
foreach ($deadlocks as $key) {
// 强制释放死锁
$this->redis->del($key);
$this->redis->hDel($this->lockRegistry, $key);
// 记录日志
$this->logDeadlock($key);
}
}
private function isProcessAlive($pid, $host) {
// 根据实际情况实现进程存活检测
if ($host === gethostname()) {
return file_exists("/proc/{$pid}");
}
// 远程主机检测逻辑
return false;
}
private function logDeadlock($lockKey) {
// 记录死锁信息到日志
error_log("Deadlock detected and released: {$lockKey}");
}
}
最佳实践总结
超时设置原则
class LockConfigurator {
/**
* 推荐的超时配置
*/
public static function getRecommendedConfig($type) {
$configs = [
'file_lock' => [
'acquire_timeout' => 5, // 获取锁超时
'lock_ttl' => 30, // 锁持有时间
'retry_interval' => 100, // 重试间隔(ms)
'max_retries' => 50
],
'redis_lock' => [
'acquire_timeout' => 3, // 获取锁超时
'lock_ttl' => 10, // 锁过期时间
'watchdog_ttl' => 3, // 看门狗续期间隔
'max_wait_time' => 10
],
'mysql_lock' => [
'acquire_timeout' => 5, // MySQL锁等待超时
'lock_ttl' => 30, // 最大持有时间
'innodb_timeout' => 5
]
];
return $configs[$type] ?? $configs['redis_lock'];
}
}
// 使用示例
$config = LockConfigurator::getRecommendedConfig('redis_lock');
$lock = new RedisLock($redis, 'process_data', $config['lock_ttl']);
// 设置PHP执行超时
set_time_limit($config['lock_ttl'] + 5);
if ($lock->acquire($config['acquire_timeout'])) {
try {
// 业务逻辑
processData();
} finally {
$lock->release();
}
} else {
// 处理获取锁失败的情况
throw new \RuntimeException('无法获取锁,请稍后重试');
}
关键建议
- 始终设置超时:所有锁操作都必须有超时机制
- 使用唯一标识:每个锁请求使用唯一标识防止误释放
- 原子操作:使用Redis的SET NX EX命令确保原子性
- 错误处理:确保在
finally块中释放锁 - 监控告警:监控锁超时和死锁情况
- 分级超时:根据业务重要性设置不同的超时策略
通过这些方法,可以有效防止PHP项目中的死锁常驻问题,选择哪种方案取决于你的项目架构(单体/分布式)、性能要求和复杂性。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播