
本文探讨了在javascript频繁向php服务器传输数据时,因并发写入同一文件导致的竞态条件和数据丢失问题。通过引入php文件锁机制,确保数据写入的原子性,即在同一时间只有一个进程能修改文件,从而有效防止数据丢失,保障数据完整性。
在现代Web应用中,客户端(如JavaScript)向服务器频繁发送数据是常见操作。当多个客户端或单个客户端在短时间内连续向服务器发送数据,并且服务器端需要将这些数据写入同一个文件时,就可能出现数据丢失的问题。这通常是由“竞态条件”(Race Condition)引起的。
考虑以下场景:一个JavaScript客户端通过XHR请求向PHP后端发送数据。PHP脚本接收数据后,会读取一个JSON文件,将新数据追加进去,然后将更新后的数据写回文件。
JavaScript 客户端代码示例:
const XHR = new XMLHttpRequest();
function sendData(data) {
  XHR.open('POST', 'savedata.php');
  XHR.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
  XHR.send('data=' + JSON.stringify(data));
}
// 假设在短时间内多次调用 sendData(someObject);
// 例如:
// sendData({ id: 1, value: 'test1' });
// sendData({ id: 2, value: 'test2' });原始的 PHP 服务器端处理逻辑:
立即学习“PHP免费学习笔记(深入)”;
if (isset($_POST['data'])) {
    if (file_exists('data.json')) {
        $file = file_get_contents('data.json'); // 进程A读取文件
        $accumulatedData = json_decode($file); // 进程A解码数据
        $data = json_decode($_POST['data']);
        array_push($accumulatedData, $data); // 进程A修改数据
        $encodedAccumulatedData = json_encode($accumulatedData);
        file_put_contents('data.json', $encodedAccumulatedData); // 进程A写入文件
    }
}上述PHP代码存在一个严重的问题。如果两个或多个PHP进程几乎同时执行这段代码:
结果是,进程A提交的数据会丢失,因为进程B基于一个旧版本的文件内容进行了修改并最终覆盖了文件。
为了解决这种竞态条件导致的数据丢失问题,我们需要确保对共享资源的访问是原子性的,即在任何给定时间,只有一个进程能够修改文件。PHP提供了 flock() 函数来实现文件锁定。
flock() 函数允许你对一个打开的文件句柄进行读锁或写锁。当一个进程获得文件的独占写锁时,其他试图获取锁的进程将被阻塞,直到当前锁被释放。
使用 flock() 改进的 PHP 服务器端代码:
<?php
if (isset($_POST['data'])) {
    $filePath = 'data.json';
    // 检查文件是否存在,如果不存在则创建空JSON数组
    if (!file_exists($filePath)) {
        file_put_contents($filePath, json_encode([]));
    }
    // 以读写模式打开文件
    $fp = fopen($filePath, "r+");
    if ($fp === false) {
        // 文件打开失败,可能是权限问题
        error_log("Error: Could not open file for locking: " . $filePath);
        http_response_code(500); // Internal Server Error
        echo "Server error: Could not process data.";
        exit;
    }
    // 尝试获取独占锁(LOCK_EX)。如果文件已被锁定,此调用将阻塞直到获取锁。
    if (flock($fp, LOCK_EX)) {
        // 成功获取锁,现在可以安全地读取和修改文件
        // 读取文件当前内容
        // 注意:在获取锁后重新读取文件内容至关重要,以确保获取的是最新数据
        $fileContent = stream_get_contents($fp, -1, 0); // 从文件开头读取所有内容
        if (empty($fileContent)) {
            $accumulatedData = [];
        } else {
            $accumulatedData = json_decode($fileContent, true); // true表示返回关联数组
            if (json_last_error() !== JSON_ERROR_NONE) {
                // JSON解析错误,可能是文件损坏
                error_log("Error: data.json contains invalid JSON. Resetting file. Error: " . json_last_error_msg());
                $accumulatedData = [];
            }
        }
        $newData = json_decode($_POST['data'], true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            // 客户端发送的JSON数据无效
            error_log("Error: Invalid JSON data received from client. Data: " . $_POST['data']);
            http_response_code(400); // Bad Request
            echo "Invalid data format.";
            // 释放锁并关闭文件
            flock($fp, LOCK_UN);
            fclose($fp);
            exit;
        }
        // 确保 $accumulatedData 是一个数组
        if (!is_array($accumulatedData)) {
            $accumulatedData = [];
        }
        // 追加新数据
        array_push($accumulatedData, $newData);
        $encodedAccumulatedData = json_encode($accumulatedData);
        // 写入之前,将文件指针移到开头并截断文件,清除旧内容
        rewind($fp); // 将文件指针移到文件开头
        ftruncate($fp, 0); // 截断文件,清除所有内容
        // 将新JSON数据写入文件
        fwrite($fp, $encodedAccumulatedData);
        // 释放锁
        flock($fp, LOCK_UN);
        echo "Data saved successfully.";
    } else {
        // 理论上,由于LOCK_EX是阻塞的,此分支很少执行,除非发生系统级错误。
        // 但为了健壮性,仍应处理。
        error_log("Error: Could not acquire file lock for " . $filePath);
        http_response_code(503); // Service Unavailable
        echo "Server is busy, please try again later.";
    }
    // 关闭文件句柄
    fclose($fp);
} else {
    http_response_code(400); // Bad Request
    echo "No data received.";
}
?>虽然文件锁对于中低并发场景有效,但在极高并发环境下,频繁的文件锁定和释放可能会成为性能瓶颈。对于需要处理大量并发写入的场景,以下是更优的替代方案:
在服务器端处理并发数据写入时,防止数据丢失是确保数据完整性的关键。通过理解竞态条件,并利用PHP的 flock() 函数实现文件锁定,我们可以有效地避免在文件操作过程中出现数据覆盖和丢失的问题。对于更高级的并发需求,考虑采用数据库或消息队列等成熟的解决方案,以构建更健壮、可扩展的系统。始终记住,在处理共享资源时,原子性操作是保障数据一致性的基石。
以上就是PHP并发数据写入:使用文件锁防止数据丢失的教程的详细内容,更多请关注php中文网其它相关文章!
                        
                        PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号