本文目录导读:

在PHP中避免脚本重复执行有多种方法,以下是几种常用的解决方案:
使用文件锁(推荐)
<?php
class SingleScript {
private $lockFile;
private $fp;
public function __construct($name = 'default') {
// 锁文件放在临时目录
$this->lockFile = sys_get_temp_dir() . '/' . md5($name) . '.lock';
}
public function acquire() {
$this->fp = fopen($this->lockFile, 'w');
// 尝试获取独占锁(非阻塞模式)
if (!flock($this->fp, LOCK_EX | LOCK_NB)) {
fclose($this->fp);
return false;
}
// 写入当前进程PID和时间戳
fwrite($this->fp, "PID: " . getmypid() . " Time: " . date('Y-m-d H:i:s'));
fflush($this->fp);
return true;
}
public function release() {
if ($this->fp) {
flock($this->fp, LOCK_UN);
fclose($this->fp);
}
}
public function __destruct() {
$this->release();
}
}
// 使用示例
$lock = new SingleScript('my_task');
if (!$lock->acquire()) {
echo "脚本已在运行中,退出\n";
exit(1);
}
// 业务逻辑
echo "开始执行任务...\n";
sleep(10); // 模拟耗时的任务
echo "任务完成\n";
// 释放锁(可选,析构函数会自动释放)
$lock->release();
?>
使用数据库锁
MySQL的GET_LOCK函数
<?php
function acquireDbLock($lockName, $timeout = 0) {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
// 获取互斥锁
$stmt = $pdo->prepare("SELECT GET_LOCK(?, ?)");
$stmt->execute([$lockName, $timeout]);
$result = $stmt->fetchColumn();
if ($result == 1) {
// 锁定成功
return function() use ($pdo, $lockName) {
$stmt = $pdo->prepare("SELECT RELEASE_LOCK(?)");
$stmt->execute([$lockName]);
};
}
return null;
}
// 使用示例
$releaseLock = acquireDbLock('cron_task', 0);
if (!$releaseLock) {
echo "任务已在运行\n";
exit(1);
}
// 执行业务逻辑
try {
echo "开始任务...\n";
// 任务代码
$releaseLock(); // 释放锁
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
$releaseLock(); // 确保释放锁
}
?>
使用标志文件(简单但不够可靠)
<?php
function checkAndSetFlag($flagFile, $maxAge = 300) {
// 检查标志文件是否存在且未过期
if (file_exists($flagFile)) {
$age = time() - filemtime($flagFile);
if ($age < $maxAge) {
return false; // 任务仍在运行
}
}
// 创建标志文件
if (!file_put_contents($flagFile, date('Y-m-d H:i:s'))) {
return false;
}
return true;
}
// 使用示例
$flagFile = '/tmp/my_task.flag';
if (!checkAndSetFlag($flagFile, 300)) { // 10分钟超时
echo "任务已在运行\n";
exit(1);
}
// 任务完成后删除标志文件
register_shutdown_function(function() use ($flagFile) {
@unlink($flagFile);
});
// 业务逻辑
echo "开始执行任务...\n";
// 任务代码
?>
使用Redis锁(分布式环境下推荐)
<?php
class RedisLock {
private $redis;
private $lockKey;
public function __construct($lockKey, $host = '127.0.0.1', $port = 6379) {
$this->redis = new Redis();
$this->redis->connect($host, $port);
$this->lockKey = $lockKey;
}
public function acquire($timeout = 10, $expire = 30) {
$token = uniqid();
$result = $this->redis->set(
$this->lockKey,
$token,
['NX', 'EX' => $expire]
);
if ($result) {
$this->token = $token;
return true;
}
// 等待重试
while ($timeout > 0) {
sleep(1);
$timeout--;
$result = $this->redis->set(
$this->lockKey,
$token,
['NX', 'EX' => $expire]
);
if ($result) {
$this->token = $token;
return true;
}
}
return false;
}
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->token], 1);
}
}
// 使用示例
$lock = new RedisLock('cron_task_lock');
if (!$lock->acquire(0)) {
echo "任务已在运行\n";
exit(1);
}
// 业务逻辑
try {
echo "开始执行任务...\n";
// 任务代码
} finally {
$lock->release();
}
?>
使用命令行工具(cron场景)
#!/bin/bash
# 使用fuser检查进程是否在运行
LOCK_FILE="/tmp/my_script.lock"
if fuser "$LOCK_FILE" > /dev/null 2>&1; then
echo "脚本已在运行"
exit 1
fi
# 创建锁文件
exec 200>"$LOCK_FILE"
flock -n 200 || exit 1
# 执行PHP脚本
php /path/to/script.php
# 自动释放锁(脚本退出时)
注意事项
- 锁的过期时间:对于文件锁,考虑设置超时机制,防止死锁
- 异常处理:确保在异常情况下也能释放锁
- 并发环境:分布式环境下使用Redis或数据库锁更可靠
- 锁的粒度:确保锁的粒度合适,避免影响其他操作
推荐方案
- 单机环境:使用文件锁(flock)
- 多服务器环境:使用Redis锁或数据库锁
- 简单的cron任务:使用标志文件或PID文件
选择哪种方案取决于你的具体场景、系统架构和可靠性要求。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播