• 请不要在回答技术问题时复制粘贴 AI 生成的内容
Liang
V2EX  ›  程序员

如何实现请问 A 服务器当文件不存在时向 B 下载,并在下一次请求时使用本地文件

  •  
  •   Liang · Jul 17, 2019 · 3462 views
    This topic created in 2668 days ago, the information mentioned may be changed or developed.
    背景是这样的:
    公司业务的文件存储是用 OSS 的,奈何 OSS 一时爽,后面的流量费用准备回家找房产证了。

    公司最近托管了一台服务器 A,流量不限,打算暂时用 A 做“ CDN ”,只在 A 本地不存在请求文件的时候,去 OSS 拿,拿完后存在本地,下次再请求时,直接使用即可。

    这样考虑是为了不影响现有的文件上传业务,也不需要一下次把所有的 OSS 文件拉取回来,托管的服务器如果也可以随时下掉。

    当然如果有更优的方案就更好了,目前想解决标题的问题,貌似 nginx 的 proxy_cache 可以做,但是是缓存,不是文件的真实地址,求各位大神指路。
    Supplement 1  ·  Jul 18, 2019

    感谢各位,已逐一发感谢。

    最后还是通过PHP实现了,遇到未缓存的大文件,第一次比较慢,后面就没问题了。后面有空了再优化这个问题。

    代码如下:

    Nginx

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    

    PHP

    <?php
    
    const SOURCE = 'OSS PATH';
    
    $host = sprintf('%s://%s', $_SERVER['REQUEST_SCHEME'], SOURCE);
    $root = $_SERVER['DOCUMENT_ROOT'];
    $uri = $_SERVER['REQUEST_URI'];
    
    $extension = pathinfo($uri, PATHINFO_EXTENSION);
    if (in_array($extension, ['jpg', 'png', 'gif', 'mp4', 'txt'])) {
        if (FALSE !== ($content = @file_get_contents($host . $uri))) {
    
            $parts = explode('/', $root . $uri);
            $file = array_pop($parts);
            $dir = '';
            foreach ($parts as $part)
                if (!is_dir($dir .= "/$part")) mkdir($dir);
            file_put_contents("$dir/$file", $content);
    
            $finfo = new finfo(FILEINFO_MIME_TYPE);
            $mime_type = $finfo->buffer($content);
    
            header("Content-Type: $mime_type");
            echo $content;
            exit;
    
        }
    }
    
    header("Location: $host$uri");
    
    20 replies  •  2019-07-18 15:21:38 +08:00
    37Y37
    37Y37
    Jul 17, 2019❤️1
    1
    try_file  试试这个模块
    cjpjxjx
    cjpjxjx
    Jul 17, 2019 via iPhone❤️1
    2
    我也在考虑一个类似的问题,在家里的服务器上搭建网站,有公网 IP,带宽大还不限流量,但是家里的服务器不可能有云服务那么稳定,想实现当家里停电或断网时,自动切换到云服务器顶上,而且数据是保持同步的
    Liang
    Liang
    OP
    Jul 17, 2019
    3
    @cjpjxjx 我倒没想自动切。文件服务器的域名是配置的,可以随时改,这段时间用 A,如果 A 要用于正式业务再改个域名切回 B 就行了。目前而言不至于把 B 所有的文件都下回 A,流量费用又是一大笔
    chinvo
    chinvo
    Jul 17, 2019 via iPhone❤️1
    4
    try_files 最后写个 rewrite,302 到另一个 url
    wewall
    wewall
    Jul 17, 2019❤️1
    5
    php 是世界上最好的语言
    wewall
    wewall
    Jul 17, 2019❤️1
    6
    @chinvo 这样并没有达到缓存一份到本地的需求啊
    liang96
    liang96
    Jul 17, 2019❤️1
    7
    @cjpjxjx 家里的网络都没开 80 和 443 吧
    runtu2019
    runtu2019
    Jul 17, 2019❤️1
    8
    https://www.fikker.com/

    有个硬盘缓存的功能,可以将文件缓存在内存和硬盘中,应该可以满足你的需求
    chinvo
    chinvo
    Jul 17, 2019❤️1
    9
    @wewall 那就做个 proxy_pass,然后 proxy_cache
    MonoLogueChi
    MonoLogueChi
    Jul 17, 2019 via Android❤️1
    10
    自己写个程序,404 的时候 302 跳过去,然后后台下载回来?第一想法是这样
    Edward4074
    Edward4074
    Jul 17, 2019 via iPhone❤️1
    11
    如果服务器也是阿里的话,oss 走内网是免流量费的
    runtu2019
    runtu2019
    Jul 17, 2019❤️1
    12
    Apache Traffic Server
    忽然想到这个这个也是可以做持久化缓存的!
    gamexg
    gamexg
    Jul 17, 2019❤️1
    13
    服务器 A 404 时重定向到 oss。
    然后定期拉去 nginx 日志,找到 302 的条目下载。
    KasuganoSoras
    KasuganoSoras
    Jul 17, 2019❤️1
    14
    <?php
    $api = "https://example.com/";
    $file = __DIR__ . "/cache/" . md5($_GET['file']);
    $real = realpath($file);
    if($real == "" || !file_exists($real)) {
    $data = file_get_contents("{$api}{$_GET['file']}");
    if(strlen($data) > 0) {
    @file_put_contents($file, $data);
    } else {
    exit("Cannot fetch data!");
    }
    }
    $real = realpath($file);
    $fi = new finfo(FILEINFO_MIME_TYPE);
    $mime = $fi->file($real);
    $file_name = basename($real);
    Header("Content-Type: {$mime}");
    Header("Content-Length: " . filesize($real));
    Header("Content-Disposition: attachment; filename={$file_name}");
    readfile($real);
    exit;

    随手写的,不知道能用不(
    laozhoubuluo
    laozhoubuluo
    Jul 18, 2019❤️1
    15
    但是是缓存,不是文件的真实地址?? 这句没看懂。

    1.图省事的做法就是 OSS 配好缓存时间,完了 nginx 的 proxy_cache 或者 squid 解决。
    好处就是缓存什么文件 nginx 代劳了,不用考虑什么是热点内容。
    缺点就是所有流量经过 A,浪费双份流量,并且 A 断了会引起业务中断。当然如果有逐步下掉 OSS 的计划,这样做最好不过。

    2. 要么就是 nginx 的 try_files+302 解决。
    好处就是客户端配置一个域名可以搞定,并且不会浪费流量。
    缺点就是 A 断了会引起业务中断,并且需要定期分析日志决定什么内容放在 A 上,否则可能不优化。

    3. 如果能接受客户端发版,可以考虑改造 HTTP 请求相关代码,第一次请求走 ServerA,如果返回代码!=200 或者请求失败,第二次请求走 OSS。
    优点是这样比较强健,ServerA 断了不影响业务,并且不会浪费流量。
    缺点就是要改造客户端代码,另外也需要定期分析日志决定什么内容放在 A 上,否则可能不优化。
    laozhoubuluo
    laozhoubuluo
    Jul 18, 2019❤️1
    16
    我重新看了一下需求,您希望 ServerA 100%缓存流经的内容的话,就用 proxy_cache 吧,完了 OSS 侧给个巨大的缓存头解决问题。
    goodryb
    goodryb
    Jul 18, 2019❤️1
    17
    贵司不会是直接提供 oss 来直接下载文件吧,oss 是做存储的,如果是下载类的,前面必须要套个 CDN,这样就算回源到 OSS,访问的流量也会非常少。

    如果觉得回源流量还是太大,可以考虑用你中间这台服务器做个二级缓存。CDN 一般都会有源站检测功能,把你的服务器和 oss 都配置成源站,优先走你的服务器,服务器要是挂了,自动走 oss 回源。
    ryd994
    ryd994
    Jul 18, 2019❤️1
    18
    如果是想缓存,但是上游内容可能变,proxy_cache
    如果是想镜像,上游内容不变,proxy_store + try_files
    官方文档里就有例子,我个人建议第二种用 @ named location 的
    Liang
    Liang
    OP
    Jul 18, 2019
    20
    @laozhoubuluo 比如我是 picture/123.jpg ,proxy_cache 在本地不会写入 picture/123.jpg ,而是 cache/01,cache/02
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1257 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 21:00 · PVG 05:00 · LAX 13:00 · JFK 16:00
    ♥ Do have faith in what you're doing.