最近需要将所在Team的一个静态资源库迁移到阿里云OSS,但在迁移过程中发现大量业务直接通过文件写入的方式向该静态资源库追加文件,这导致了静态资源库的迁移异常困难,而OSS的同步工具ossutil也不能做到实时同步。我灵机一动,想到使用Nginx的Fallback策略来变相完成无缝迁移,即在不影响业务的情况下,使用单点访问多地存储的文件。

0x01

业务架构如图所示:

看着这样一个架构,我遇到了一个难题:分散在两处的文件如何在不修改业务的情况下,合并在一起,以实现100%完美兼容?

这时候,我想到了Nginx的反向代理。

0x02

Nginx反向代理可以由服务器代理用户访问以实现中转效果,但此处我们需要实现的是让用户对新文件的访问依旧指向旧服务器,而用户对旧文件的访问指向阿里云OSS。

但是Nginx如何判断文件的新旧呢?正则表达式很显然无法进行时间判断,并且最致命的是静态资源库中很大一部分文件是按照md5前两位作为目录名进行存储,这造成了更大的困扰。

0x03

那么换一个思路,我们是否可以利用Nginx在遇到错误时的Fallback策略来实现自动切换反向代理呢?

我们都知道,Nginx允许自定错误码对应的页面,例如自定义404页面可以进行如下配置:

error_page 404 = /404.html;

但当仔细查阅Nginx手册之后,我们会发现,除了这种简单的Inline配置外,Nginx还提供了另一种更为灵活的配置方式,成为命名地址(Named Location):

The “@” prefix defines a named location. Such a location is not used for a regular request processing, but instead used for request redirection. They cannot be nested, and cannot contain nested locations.

命名地址可以理解为内部方法,无法被外部访问,但可以用作条件重定向,例如下面的两条配置:

配置1

error_page 404 = @fallback;
location @fallback {
    proxy_pass https://www.example.com;
}

配置2

location / {
    try_files $uri @fallback;
}
location @fallback {
    proxy_pass https://www.example.com;
}

配置1首先按照默认location访问本地数据,若本地数据不存在,则触发自定义404错误页面,并跳转到@fallback中的策略。

配置2与配置1思路一致,但采用try_files,相对来说更为简洁,而且可不光可以实现两个地区,还能实现三个、四个甚至更多地区的文件存储,直到最后找不到任何一个地点存储有所需文件,再返回一个文件找不到的页面。

这也就是本文重点所提到的单点访问多地存储文件的Fallback策略。

0x04

但其实Nginx的Fallback策略还有很多灵活用途。除了上文单点访问多地存储资源外,还可以用来进行反向代理服务器的缓存。

其原理为Nginx存储反向代理的所有文件,当用户访问的文件在本地找不到时再向后端请求,若找到缓存,直接返回:

location ~ .*\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|3gp|css|js)$ {
    root /var/lib/nginx/tmp/proxy/proxy_temp_path;

    proxy_set_header Host $host;

    proxy_set_header X-Real-IP $remote_addr;

    proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;

    if (!-e $request_filename) {
        # 上游服务器地址
        proxy_pass http://upstream;
    }

    add_header Cache-Status "on";

    # 配置CDN缓存
    proxy_store on;
    proxy_store_access user:rw group:rw all:rw;
    proxy_temp_path /var/lib/nginx/tmp/proxy/proxy_temp_path;

    # 配置浏览器端缓存
    # 缓存一个月
    expires 30d;
}

尽管实现方式与上文两种方法不相同,但思路是完全一致的,即实现单点访问多地存储资源(一地缓存、一地后端存储),若在缓存端使用内存文件系统(例如Linux的ramfs / tmpfs),可以实现极强性能。