V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xuexixuexi2
V2EX  ›  程序员

请教一个 nginx 做下载服务器统计下载信息的问题。

  •  
  •   xuexixuexi2 · 2017-01-11 15:32:59 +08:00 · 4320 次点击
    这是一个创建于 2660 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求:需要知道有人开始下载,是否下载完成。
    思路:通过 php 的 fpassthru 发送实际的文件数据,并在开始前和结束后写数据库。
    代码:

    $db = new DownloadDB();
    $db->exec("INSERT INTO tblDownloads(info) VALUES('下载开始');");
    $db->close();
    
    $filepath = '../files/file1';
    @header('Content-type: application/octet-stream');
    @header('Content-Disposition: attachment; filename=' . $filename);
    @header('Accept-Ranges: bytes');
    @header('Content-Length: ' . filesize($filepath));
    @header('Cache-control: no-cache,no-store,must-revalidate');
    @header('Pragma: no-cache');
    @header('Expires: 0');
    $file = @fopen($filepath, "rb");
    @fpassthru($file);
    @fclose($file);
    
    $db = new DownloadDB();
    $db->exec("INSERT INTO tblDownloads(info) VALUES('下载完成');");
    $db->close();
    

    问题:还没有下载完,就写了“下载完成”到数据库中了,甚至是和“下载开始”同时写入数据库的。
    tblDownloads 表有时间字段,可以看到下载完成和下载开始是同时写入数据库的,也就是中间的 fopen-fpassthru-fclose 这些耗时为 0 。
    而我的理解是 fpassthru 应该将数据全部推送给用户后才返回,是不是有缓存机制? 要怎么能按我预想的进行?
    环境: Linux+nginx+fastCGI

    37 条回复    2017-01-13 21:52:46 +08:00
    aru
        1
    aru  
       2017-01-11 16:16:35 +08:00   ❤️ 1
    网页上通过 js 来实现:下载完成后触发一次 js 请求,服务器做记录
    xuexixuexi2
        2
    xuexixuexi2  
    OP
       2017-01-11 18:14:44 +08:00
    @aru 谢谢,但还是希望能在服务端实现。 js 不考虑。
    xfspace
        3
    xfspace  
       2017-01-11 18:16:38 +08:00 via Android
    不判断文件就写数据库 hhh
    xuexixuexi2
        4
    xuexixuexi2  
    OP
       2017-01-11 19:44:33 +08:00
    @xfspace 你好,是我的代码哪里有问题吗?我确实不清楚,请指教。
    但是文件../files/file1 是存在的,我在自己的电脑上测试,可以下载,下载后文件也是正确的。
    但是下载需要几分钟,而一开始浏览器弹出选择保存文件位置的时候,“下载完成”就写到数据库中了。
    aru
        5
    aru  
       2017-01-11 20:11:04 +08:00   ❤️ 1
    @xuexixuexi2
    php 将文件发给了 nginx ,完成
    nginx 慢慢的再传给客户端
    xuexixuexi2
        6
    xuexixuexi2  
    OP
       2017-01-11 20:19:28 +08:00
    @aru 跟我猜想的一样。那有什么办法可以达到我的目的吗?比如修改 PHP.ini 或者 fastCGI 或者 nginx 的配置文件。
    why1
        7
    why1  
       2017-01-11 20:26:42 +08:00 via Android   ❤️ 1
    找下 Nginx 的模块
    xuexixuexi2
        8
    xuexixuexi2  
    OP
       2017-01-11 20:50:46 +08:00
    @why1
    --prefix=/etc/nginx
    --sbin-path=/usr/sbin/nginx
    --conf-path=/etc/nginx/nginx.conf
    --error-log-path=/var/log/nginx/error.log
    --http-log-path=/var/log/nginx/access.log
    --pid-path=/var/run/nginx.pid
    --lock-path=/var/run/nginx.lock
    --http-client-body-temp-path=/var/cache/nginx/client_temp
    --http-proxy-temp-path=/var/cache/nginx/proxy_temp
    --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp
    --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp
    --http-scgi-temp-path=/var/cache/nginx/scgi_temp
    --user=nginx
    --group=nginx
    --with-http_ssl_module
    --with-http_dav_module
    --with-http_flv_module
    --with-http_mp4_module
    --with-http_gunzip_module
    --with-http_gzip_static_module
    --with-http_random_index_module
    --with-http_secure_link_module
    --with-http_stub_status_module
    --with-http_auth_request_module
    --with-mail
    --with-mail_ssl_module
    --with-file-aio
    --with-ipv6
    --with-http_spdy_module
    --with-cc-opt='-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2
    -fexceptions
    -fstack-protector
    --param=ssp-buffer-size=4
    -m32
    -march=i386
    -mtune=generic-fasynchronous-unwind-tables
    xuexixuexi2
        9
    xuexixuexi2  
    OP
       2017-01-11 20:53:34 +08:00
    --with-http_realip_module
    --with-http_addition_module
    --with-http_sub_module
    skydiver
        10
    skydiver  
       2017-01-11 20:55:48 +08:00 via Android
    不知道为什么用这么奇怪的方法来解决问题…然后用更奇怪的方法来修正奇怪的方法导致的问题……
    xuexixuexi2
        11
    xuexixuexi2  
    OP
       2017-01-11 20:57:41 +08:00
    @skydiver 哦,还有什么更好的办法吗?
    ryd994
        12
    ryd994  
       2017-01-11 22:29:59 +08:00 via Android   ❤️ 1
    说实话,我也觉得用 JS 是最简单的
    开始前统计很简单,用 auth_request 配合 PHP 就行
    但是结束后统计很难
    主要是 Nginx 之类的会有 buffer ,还有 sendfile 这类
    如果只是为了统计开始结束时间和下载的区段的话,你可以直接写 Nginx log 事后分析
    xuexixuexi2
        13
    xuexixuexi2  
    OP
       2017-01-11 22:42:54 +08:00
    sendfile 设为 off ,试过了,没用。
    代码在 apache 下是没问题的,下载成功才写入“下载完成”。
    我在这里也是问问,多学点知识。实在不行就换 apache 服务器算了。
    xuexixuexi2
        14
    xuexixuexi2  
    OP
       2017-01-11 22:44:15 +08:00
    上网查了一下,貌似和 with-file-aio 模块有关,但是 nginx 模块好像都是编译的,不能关闭?
    lslqtz
        15
    lslqtz  
       2017-01-12 01:46:47 +08:00
    @aru 其实我也这么做过,不过大部分都是 php 在卡着而不是 nginx 。

    fpassthru() 函数输出文件指针处的所有剩余数据。
    该函数将给定的文件指针从当前的位置读取到 EOF ,并把结果写到输出缓冲区。
    摘自: http://www.w3school.com.cn/php/func_filesystem_fpassthru.asp
    所以在这之前先清除缓冲并禁止。
    #设置执行时间不限时 。
    set_time_limit(0);
    #发送内部缓冲区的内容到浏览器,删除缓冲区的内容,不关闭缓冲区。
    ob_flush();
    #发送内部缓冲区的内容到浏览器,删除缓冲区的内容,关闭缓冲区。
    ob_end_flush();
    #将 ob_flush 释放出来的内容,以及不在 PHP 缓冲区中的内容,全部输出至浏览器;刷新内部缓冲区的内容,并输出。
    flush();
    所以实际上前面加几句:
    set_time_limit(0);
    ob_end_flush();
    flush();
    感觉就好了。。
    lslqtz
        16
    lslqtz  
       2017-01-12 01:52:26 +08:00   ❤️ 1
    顺便说一下,可以改用 readfile 而不用 fopen
    msg7086
        17
    msg7086  
       2017-01-12 01:54:53 +08:00
    nginx 负责发送数据,要统计开始和完成,当然依赖 nginx 了。
    manhere
        18
    manhere  
       2017-01-12 02:53:46 +08:00 via iPhone
    流输出+限速 就能统计到了吧
    xuexixuexi2
        19
    xuexixuexi2  
    OP
       2017-01-12 03:47:07 +08:00
    @lslqtz 将代码改为:
    set_time_limit(0);
    ob_end_flush();
    flush();
    $file = @fopen($filepath, "rb");
    @fpassthru($file);
    @fclose($file);
    和:
    set_time_limit(0);
    ob_end_flush();
    flush();
    @readfile($filepath);
    都试过了,还是一样。
    xuexixuexi2
        20
    xuexixuexi2  
    OP
       2017-01-12 03:47:50 +08:00
    @msg7086 那要怎么做呢?
    lslqtz
        21
    lslqtz  
       2017-01-12 04:12:22 +08:00   ❤️ 1
    我这边测试是正常的,视频在这里: http://xinchen123.oss-cn-shanghai.aliyuncs.com/o_1b67igqepga1t4g0ahkfsua.mp4

    set_time_limit(0);
    ob_end_flush();
    flush();
    @header('Content-Length:299711208');
    @header('Content-Type:application/octet-stream');
    @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe');
    @readfile('http://speed.myzone.cn/WindowsXP_SP2.exe');
    msg7086
        22
    msg7086  
       2017-01-12 04:35:08 +08:00   ❤️ 2
    挂个 inotify 在 nginx access log 上,然后写数据库?
    xuexixuexi2
        23
    xuexixuexi2  
    OP
       2017-01-12 04:40:21 +08:00
    @lslqtz 可能还是和 nginx 的模块有关吧。
    我又详细测了一下,改代码位置,改 header ,都和你的一样了,还是存在这个问题。
    xuexixuexi2
        24
    xuexixuexi2  
    OP
       2017-01-12 04:48:24 +08:00
    @msg7086 按你说的上网搜了一下,确实是个可行的思路。
    明天试试:)
    lslqtz
        25
    lslqtz  
       2017-01-12 04:57:48 +08:00
    @xuexixuexi2 php/nginx 问题?不清楚,手头是 win 下的,理论可行没错。
    ericls
        26
    ericls  
       2017-01-12 07:07:07 +08:00
    最简单的做法应该是利用 nginx 的 internel routing
    ericls
        27
    ericls  
       2017-01-12 07:07:30 +08:00
    @ericls *internal
    aru
        28
    aru  
       2017-01-12 08:23:00 +08:00 via iPhone
    @lslqtz 你换个本地文件发送试试。
    远程文件读取太耗时了,耗时就可能和客户端下载时间接近了。
    lslqtz
        29
    lslqtz  
       2017-01-12 09:05:58 +08:00
    @aru 楼主的描述是计费后才开始下载,说明这时已经执行完毕并输出缓冲。
    而我这个是立即执行的,有缓冲也应该是远程文件下完后才输出。
    aru
        30
    aru  
       2017-01-12 10:17:51 +08:00   ❤️ 1
    @lslqtz 不要说那么多。直接试试使用本地的大文件(不用太大,下载方需要 3-10 秒内下完即可),看看能不能记录到准确的下载耗时。我觉得用 apache 的 mod_php 是没问题的, nginx 的 fast_cgi 不行。
    lslqtz
        31
    lslqtz  
       2017-01-12 23:27:02 +08:00
    @aru 我试着写了写,不能计算到准确的下载耗时。
    不过之前是帮楼主解决下载之前卡住的问题。
    现在把代码改成了这样,问题在于如果 readfile 换成 sleep 就可以准确的统计到:
    <?php
    set_time_limit(0);
    ignore_user_abort(1);
    ob_end_flush();
    flush();
    $time=time();
    @header('Connection:Close');
    @header('Content-Length:299711208');
    @header('Content-Type:application/octet-stream');
    @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe');
    @readfile('1.exe');
    while (!connection_aborted()) {
    file_put_contents('1.txt',time()-$time);
    die();
    }
    ?>
    lslqtz
        32
    lslqtz  
       2017-01-12 23:30:06 +08:00
    Reply 31
    lslqtz 刚刚
    @aru 我试着写了写,不能计算到准确的下载耗时。
    不过之前是帮楼主解决下载之前卡住的问题。
    现在把代码改成了这样,问题在于如果 readfile 换成 sleep 就可以准确的统计到:
    <?php
    set_time_limit(0);
    ignore_user_abort(1);
    ob_end_flush();
    flush();
    $time=time();
    @header('Connection:Close');
    @header('Content-Length:299711208');
    @header('Content-Type:application/octet-stream');
    @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe');
    @readfile('1.exe');
    while (connection_status() != 0 || connection_aborted()) {
    file_put_contents('1.txt',time()-$time);
    die();
    }
    ?>
    突然感觉代码写错了...
    lslqtz
        33
    lslqtz  
       2017-01-12 23:32:37 +08:00
    然后又想了想,改成 while (1) {}里面加 if 会好一点。。
    如果在里面的 if 判断到连接被结束了,就断开连接。
    Reply 32
    lslqtz 1 分钟前
    Reply 31
    lslqtz 刚刚
    @aru 我试着写了写,不能计算到准确的下载耗时。
    不过之前是帮楼主解决下载之前卡住的问题。
    现在把代码改成了这样,问题在于如果 readfile 换成 sleep 就可以准确的统计到:
    <?php
    set_time_limit(0);
    ignore_user_abort(1);
    ob_end_flush();
    flush();
    $time=time();
    @header('Connection:Close');
    @header('Content-Length:299711208');
    @header('Content-Type:application/octet-stream');
    @header('Content-Disposition:attachment;filename=WindowsXP_SP2.exe');
    @readfile('1.exe');
    while (1) {
    if (connection_status() != 0 || connection_aborted()) {
    file_put_contents('1.txt',time()-$time);
    die();
    }
    }
    ?>
    aru
        34
    aru  
       2017-01-13 10:39:57 +08:00
    @lslqtz 我感觉,尝试在发送文件的代码做下载耗时统计的做法是歪门邪道(极大降低了 php 的性能)。
    js 或者 access log 或者 nginx module 才是正确的做法。
    lslqtz
        35
    lslqtz  
       2017-01-13 13:14:50 +08:00 via iPhone
    @aru 是的,很费资源。
    但我还是去实现了他 xD 。
    xuexixuexi2
        36
    xuexixuexi2  
    OP
       2017-01-13 19:46:04 +08:00
    @aru 我本来专业不是做这个的, nginx 用得更少。
    有没有现成的 nginx 模块可以实现这个?还有 nginx 模块是不是只能编译进 nginx 里才能用?
    我看 nginx 的配置没有像 apache 的配置一样设置模块。
    aru
        37
    aru  
       2017-01-13 21:52:46 +08:00
    @xuexixuexi2 似乎没有现成的。日志里面可以记录下载的大小和下载耗时,将这个弄成单独的一个日志文件,然后弄个脚本去做定时处理吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1676 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 16:51 · PVG 00:51 · LAX 09:51 · JFK 12:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.