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

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

  •  
  •   Liang · 2019-07-17 18:11:34 +08:00 · 2229 次点击
    这是一个创建于 1734 天前的主题,其中的信息可能已经有所发展或是发生改变。
    背景是这样的:
    公司业务的文件存储是用 OSS 的,奈何 OSS 一时爽,后面的流量费用准备回家找房产证了。

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

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

    当然如果有更优的方案就更好了,目前想解决标题的问题,貌似 nginx 的 proxy_cache 可以做,但是是缓存,不是文件的真实地址,求各位大神指路。
    第 1 条附言  ·  2019-07-18 15:19:12 +08:00

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

    最后还是通过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 条回复    2019-07-18 15:21:38 +08:00
    37Y37
        1
    37Y37  
       2019-07-17 18:39:37 +08:00   ❤️ 1
    try_file  试试这个模块
    cjpjxjx
        2
    cjpjxjx  
       2019-07-17 18:51:31 +08:00 via iPhone   ❤️ 1
    我也在考虑一个类似的问题,在家里的服务器上搭建网站,有公网 IP,带宽大还不限流量,但是家里的服务器不可能有云服务那么稳定,想实现当家里停电或断网时,自动切换到云服务器顶上,而且数据是保持同步的
    Liang
        3
    Liang  
    OP
       2019-07-17 18:59:57 +08:00
    @cjpjxjx 我倒没想自动切。文件服务器的域名是配置的,可以随时改,这段时间用 A,如果 A 要用于正式业务再改个域名切回 B 就行了。目前而言不至于把 B 所有的文件都下回 A,流量费用又是一大笔
    chinvo
        4
    chinvo  
       2019-07-17 19:28:09 +08:00 via iPhone   ❤️ 1
    try_files 最后写个 rewrite,302 到另一个 url
    wewall
        5
    wewall  
       2019-07-17 19:29:00 +08:00   ❤️ 1
    php 是世界上最好的语言
    wewall
        6
    wewall  
       2019-07-17 19:29:43 +08:00   ❤️ 1
    @chinvo 这样并没有达到缓存一份到本地的需求啊
    liang96
        7
    liang96  
       2019-07-17 19:36:54 +08:00   ❤️ 1
    @cjpjxjx 家里的网络都没开 80 和 443 吧
    runtu2019
        8
    runtu2019  
       2019-07-17 19:45:14 +08:00   ❤️ 1
    https://www.fikker.com/

    有个硬盘缓存的功能,可以将文件缓存在内存和硬盘中,应该可以满足你的需求
    chinvo
        9
    chinvo  
       2019-07-17 19:46:44 +08:00   ❤️ 1
    @wewall 那就做个 proxy_pass,然后 proxy_cache
    MonoLogueChi
        10
    MonoLogueChi  
       2019-07-17 19:52:05 +08:00 via Android   ❤️ 1
    自己写个程序,404 的时候 302 跳过去,然后后台下载回来?第一想法是这样
    Edward4074
        11
    Edward4074  
       2019-07-17 20:02:55 +08:00 via iPhone   ❤️ 1
    如果服务器也是阿里的话,oss 走内网是免流量费的
    runtu2019
        12
    runtu2019  
       2019-07-17 20:10:55 +08:00   ❤️ 1
    Apache Traffic Server
    忽然想到这个这个也是可以做持久化缓存的!
    gamexg
        13
    gamexg  
       2019-07-17 20:35:20 +08:00   ❤️ 1
    服务器 A 404 时重定向到 oss。
    然后定期拉去 nginx 日志,找到 302 的条目下载。
    KasuganoSoras
        14
    KasuganoSoras  
       2019-07-17 20:42:20 +08:00   ❤️ 1
    <?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
        15
    laozhoubuluo  
       2019-07-18 00:05:14 +08:00   ❤️ 1
    但是是缓存,不是文件的真实地址?? 这句没看懂。

    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
        16
    laozhoubuluo  
       2019-07-18 00:08:27 +08:00   ❤️ 1
    我重新看了一下需求,您希望 ServerA 100%缓存流经的内容的话,就用 proxy_cache 吧,完了 OSS 侧给个巨大的缓存头解决问题。
    goodryb
        17
    goodryb  
       2019-07-18 00:31:40 +08:00   ❤️ 1
    贵司不会是直接提供 oss 来直接下载文件吧,oss 是做存储的,如果是下载类的,前面必须要套个 CDN,这样就算回源到 OSS,访问的流量也会非常少。

    如果觉得回源流量还是太大,可以考虑用你中间这台服务器做个二级缓存。CDN 一般都会有源站检测功能,把你的服务器和 oss 都配置成源站,优先走你的服务器,服务器要是挂了,自动走 oss 回源。
    ryd994
        18
    ryd994  
       2019-07-18 06:17:41 +08:00   ❤️ 1
    如果是想缓存,但是上游内容可能变,proxy_cache
    如果是想镜像,上游内容不变,proxy_store + try_files
    官方文档里就有例子,我个人建议第二种用 @ named location 的
    ryd994
        19
    ryd994  
       2019-07-18 06:18:51 +08:00   ❤️ 1
    Liang
        20
    Liang  
    OP
       2019-07-18 15:21:38 +08:00
    @laozhoubuluo 比如我是 picture/123.jpg ,proxy_cache 在本地不会写入 picture/123.jpg ,而是 cache/01,cache/02
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3667 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 04:45 · PVG 12:45 · LAX 21:45 · JFK 00:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.