【问题标题】:Fastest way to check for remote file (image) existence检查远程文件(图像)是否存在的最快方法
【发布时间】:2021-10-13 20:25:05
【问题描述】:

我在运行商家应用程序的本地服务器和托管商店 eshop 的远程 Web 服务器之间编写了一个产品同步脚本...

对于完全同步选项,我需要同步大约 5000 多种产品,包括它们的图像等...即使相同产品的尺寸变化(不同的产品尺寸 - 例如鞋子)共享相同的产品图像,我需要检查是否存在大约 3500 张图片...

所以,在第一次运行时,我通过 FTP 上传了所有产品图片,除了其中几个,然后让脚本运行以检查它是否会上传这两个丢失的图片...

问题是脚本运行了 4 个小时,这是不可接受的...我的意思是,我没有重新上传每张图片...它只是检查了每张图片以确定是跳过还是上传(通过ftp_put())。

我正在执行这样的检查:

if (stripos(get_headers(DESTINATION_URL . "{$path}/{$file}")[0], '200 OK') === false) {

这相当快,但显然不够快,无法同步运行一段合理的时间......

你们是如何处理这种需要检查大量远程文件是否存在的情况的?


作为最后的手段,我已经离开使用ftp_nlist() 来下载远程文件列表,然后编写一个算法来或多或少地在本地文件和远程文件之间进行文件比较......

我试过了,递归算法构建文件列表需要很长时间,实际上是 30 多分钟......你看,我的文件不在一个文件夹中......整个树跨越 1,956 个文件夹,并且文件列表由 3,653 个产品图像文件组成,并且还在不断增长……另外请注意,我什至没有使用大小“技巧”​​(与 ftp_nlist() 一起使用)来确定文件是文件还是文件夹,但是而是使用较新的ftp_mlsd(),它显式返回包含该信息的类型参数...您可以在此处阅读更多信息:PHP FTP recursive directory listing

【问题讨论】:

    标签: php ftp get-headers


    【解决方案1】:

    curl_multi 可能是最快的方法。不幸的是 curl_multi 很难使用,一个例子对 imo 有很大帮助。检查加拿大 2 个不同数据中心的 2x 1gbps 专用服务器之间的 url,此脚本设法每秒检查大约 3000 个 url,使用 500 个并发 tcp 连接(并且可以进行通过重新使用 curl 句柄而不是打开+关闭更快)

    <?php
    declare(strict_types=1);
    $urls=array();
    for($i=0;$i<100000;++$i){
        $urls[]="http://ratma.net/";
    }
    validate_urls($urls,500,1000,false,false,false);    
    // if return_fault_reason is false, then the return is a simple array of strings of urls that validated.
    // otherwise it's an array with the url as the key containing  array(bool validated,int curl_error_code,string reason) for every url
    function validate_urls(array $urls, int $max_connections, int $timeout_ms = 10000, bool $consider_http_300_redirect_as_error = true, bool $return_fault_reason) : array
    {
        if ($max_connections < 1) {
            throw new InvalidArgumentException("max_connections MUST be >=1");
        }
        foreach ($urls as $key => $foo) {
            if (!is_string($foo)) {
                throw new \InvalidArgumentException("all urls must be strings!");
            }
            if (empty($foo)) {
                unset($urls[$key]); //?
            }
        }
        unset($foo);
        // DISABLED for benchmarking purposes: $urls = array_unique($urls); // remove duplicates.
        $ret = array();
        $mh = curl_multi_init();
        $workers = array();
        $work = function () use (&$ret, &$workers, &$mh, &$return_fault_reason) {
            // > If an added handle fails very quickly, it may never be counted as a running_handle
            while (1) {
                curl_multi_exec($mh, $still_running);
                if ($still_running < count($workers)) {
                    break;
                }
                $cms=curl_multi_select($mh, 10);
                //var_dump('sr: ' . $still_running . " c: " . count($workers)." cms: ".$cms);
            }
            while (false !== ($info = curl_multi_info_read($mh))) {
                //echo "NOT FALSE!";
                //var_dump($info);
                {
                    if ($info['msg'] !== CURLMSG_DONE) {
                        continue;
                    }
                    if ($info['result'] !== CURLM_OK) {
                        if ($return_fault_reason) {
                            $ret[$workers[(int)$info['handle']]] = array(false, $info['result'], "curl_exec error " . $info['result'] . ": " . curl_strerror($info['result']));
                        }
                    } elseif (CURLE_OK !== ($err = curl_errno($info['handle']))) {
                        if ($return_fault_reason) {
                            $ret[$workers[(int)$info['handle']]] = array(false, $err, "curl error " . $err . ": " . curl_strerror($err));
                        }
                    } else {
                        $code = (string)curl_getinfo($info['handle'], CURLINFO_HTTP_CODE);
                        if ($code[0] === "3") {
                            if ($consider_http_300_redirect_as_error) {
                                if ($return_fault_reason) {
                                    $ret[$workers[(int)$info['handle']]] = array(false, -1, "got a http " . $code . " redirect, which is considered an error");
                                }
                            } else {
                                if ($return_fault_reason) {
                                    $ret[$workers[(int)$info['handle']]] = array(true, 0, "got a http " . $code . " redirect, which is considered a success");
                                } else {
                                    $ret[] = $workers[(int)$info['handle']];
                                }
                            }
                        } elseif ($code[0] === "2") {
                            if ($return_fault_reason) {
                                $ret[$workers[(int)$info['handle']]] = array(true, 0, "got a http " . $code . " code, which is considered a success");
                            } else {
                                $ret[] = $workers[(int)$info['handle']];
                            }
                        } else {
                            // all non-2xx and non-3xx are always considered errors (500 internal server error, 400 client error, 404 not found, etcetc)
                            if ($return_fault_reason) {
                                $ret[$workers[(int)$info['handle']]] = array(false, -1, "got a http " . $code . " code, which is considered an error");
                            }
                        }
                    }
                    curl_multi_remove_handle($mh, $info['handle']);
                    assert(isset($workers[(int)$info['handle']]));
                    unset($workers[(int)$info['handle']]);
                    curl_close($info['handle']);
                }
            }
            //echo "NO MORE INFO!";
        };
        foreach ($urls as $url) {
            while (count($workers) >= $max_connections) {
                //echo "TOO MANY WORKERS!\n";
                $work();
            }
            $neww = curl_init($url);
            if (!$neww) {
                trigger_error("curl_init() failed! probably means that max_connections is too high and you ran out of resources", E_USER_WARNING);
                if ($return_fault_reason) {
                    $ret[$url] = array(false, -1, "curl_init() failed");
                }
                continue;
            }
            $workers[(int)$neww] = $url;
            curl_setopt_array($neww, array(
                CURLOPT_NOBODY => 1,
                CURLOPT_SSL_VERIFYHOST => 0,
                CURLOPT_SSL_VERIFYPEER => 0,
                CURLOPT_TIMEOUT_MS => $timeout_ms
            ));
            curl_multi_add_handle($mh, $neww);
            //curl_multi_exec($mh, $unused_here); LIKELY TO BE MUCH SLOWER IF DONE IN THIS LOOP: TOO MANY SYSCALLS
        }
        while (count($workers) > 0) {
            //echo "WAITING FOR WORKERS TO BECOME 0!";
            //var_dump(count($workers));
            $work();
        }
        curl_multi_close($mh);
        return $ret;
    }
    

    【讨论】:

    • 我会研究代码,我会尝试在我的案例中实现它,并会报告回来。如果要完成的整个工作有点矫枉过正,我可能最终会使用ftp_nlist(),但我希望我能设法使用您建议的方式!非常感谢!
    • 为了让脚本按预期方式工作,我必须将所有 3500 多个图像 URL 收集到一个数组中,然后让您的脚本进行检查,对吗?否则,如果我在我的主要产品循环运行时调用它,它会运行 3500 多次而没有及时的好处,对吗?
    • @FayeD。正确。
    • 好的,我将对我的代码进行必要的修改,然后回来做决定!手指交叉! :D 非常感谢!
    • @FayeD。 glhf,顺便说一句,您可能不应该以 500x 连接开始测试,有些服务器无法处理它。特别是,如果您的目标服务器是共享虚拟主机,您将面临被自动 IP 禁止的风险。如果您的目标服务器是 nginx 或 lighthttpd,您可能会没事,但 apache 或 IIS 可能会阻塞。尝试从 50 或 100 开始,而不是 500(除非你知道它是安全的:P)
    猜你喜欢
    • 2011-11-29
    • 2015-08-15
    • 1970-01-01
    • 2012-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    相关资源
    最近更新 更多