CDN:利用 Nginx 反向代理和缓存功能自建及优化 CDN 加速节点详细教程

2024-05-24 952 0

本文所述内容不具普遍性,可能因操作环境差异而与实际有所出入,故请勿照搬照抄,仅供参考。

CDN 的相关概念可以参考之前的文章《 如何加快网站访问速度》,了解了什么是 CDN 后,我们知道 为了提高网站相关体验,网站一般都会上 CDN,但线路质量好一点的 CDN 费用一般也不低,所以就会想 着,从技术角度上看我们自己是否能够搭建 CDN?

概念

Nginx

Nginx 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 服务器,Nginx 具有很多非常优越的特性:

  • 在连接高并发的情况下,具有较好的性能,支持负载均衡,实现可扩展的浏量管理;
  • 使Web服务器更灵活,更高效,更安全;
  • 能够快速灵活且可靠的传送流视频和音频内容;
  • 支持强大的 Web 加速和移动端性能的解决方案;
  • 不仅只保护数据安全,也保证网站在面对恶意流量的攻击中能正常运行,从而保护应用安全;
  • 可管理的安全的基于 HTTP API 流量的可信平台,为 API 提供安全保障和使用策略;
  • 完整的软件应用分发平台,能够取代昂贵的 ADC 硬件负载平衡器,节省成本。

智能解析

域名智能解析是指域名解析服务器根据来访者的 IP 类型,对同一域名作出相应不同解析。

例如:

对 IP 来自电信的访问者,将域名解析到该域名对应 IP 地址为电信的服务器上;

对 IP来自联通的访问者,将域名解析到该域名对应 IP 地址为联通的服务器上;

对 IP 来自香港的访问者,将域名解析到该域名对应 IP 地址为香港的服务器上;

以保证访问者不因电信线路瓶颈而造成联通、香港的访客无法访问。

反向代理

反向代理(Reverse Proxy)是指用代理服务器来接收 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从内部服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

CDN 工作流程

CDN工作流程图

CDN工作流程图

如上图所示,这是一个完整的流程图:客户端访问域名时,先向 DNS 请求域名 IP,DNS 查询到 CNAME 记录(如果没有 DNS 直接回复源服务器 IP),则进一步解析 CNAME 智能解析服务器, 智能解析收到请求根据客户端来源按规则判断并回复 CDN 节点 IP,客户端此时访问域名就连接到了回复的 CDN 节点,如果 CDN 节点没有缓存,则 CDN 就会发起连接到负载均衡器(如果没有则直接连接源服务器,一般在有多个源服务器后端时才会有负载均衡器),然后负载均衡器根据规则分流到源服务器,将内容返回给 CDN 节点,CDN 节点再返回给客户端,完成整个访问流程。

从流程上看使用 CDN 后整个过程中增加了很多路由,看起来客户端访问速度可能会变慢,但实际上,从智能解析到负载均衡,客户端几乎是没有感知的,也就是说客户端能感受到的速度就是到 CDN 节点的速度。

搭建

通过概念讲解我们知道了 CDN 工作方式,其中智能解析、负载均衡都是可以单独拿出来长篇讲解的比较复杂的内容,我们这里先以如下简化版的流程来讲解 Nginx 搭建 CDN 节点的过程,后续有时间再一起来看看智能解析、负载均衡的内容。

CDN工作流程简化

CDN工作流程简化

编译安装

CDN 最重要的功能之一就是缓存,但像博客等内容的网站会经常更新,而 Nginx 默认是不支持删除某个文件的缓存,因此我们需要额外安装缓存清除插件 ngx_cache_purge;如果准备建多个节点,我们希望能看到是由哪个节点提供服务的,这时候就可以通过插件ngx_http_substitutions_filter_module 和 headers-more-nginx-module 来配合实现。

参考以下命令编译

# 新建 Nginx 运行用户及用户组
$ groupadd www
$ useradd -s /sbin/nologin -g www www
# 下载 Nginx 源码
$ wget https://nginx.org/download/nginx-1.14.2.tar.gz
$ tar zxf nginx-1.14.2.tar.gz && cd nginx-1.14.2/src/
# 下载并编译 pcre 运行库
$ wget https://ftp.pcre.org/pub/pcre/pcre-8.42.tar.bz2
$ tar jxf pcre-8.42.tar.bz2 && cd pcre-8.42 && ./configure
$ make && make install && cd .. && rm -rf pcre-8.42*
$ echo /usr/local/lib >> /etc/ld.so.conf.d/usrlib.conf 
$ ldconfig
# 下载并解压缓存清除插件 ngx_cache_purge
$ wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz && tar zxf 2.3.tar.gz && rm -rf 2.3.tar.gz
# 下载并解压字符串替换插件 ngx_http_substitutions_filter_module/
$ wget https://github.com/yaoweibin/ngx_http_substitutions_filter_module/archive/v0.6.4.tar.gz && tar zxf v0.6.4.tar.gz && rm -rf v0.6.4.tar.gz
# 下载并解压头部参数设置插件 headers-more-nginx-module
$ wget https://github.com/openresty/headers-more-nginx-module/archive/v0.33.tar.gz && tar zxf v0.33.tar.gz && rm -rf v0.33.tar.gz && cd ..
# 编译并安装
$ ./configure--user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --with-stream --with-stream_ssl_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre --add-module=/usr/local/nginx/src/src/ngx_cache_purge-2.3 --add-module=/usr/local/nginx/src/src/ngx_http_substitutions_filter_module-0.6.4 --add-module=/usr/local/nginx/src/src/headers-more-nginx-module-0.33
$ make && make install

配置缓存

配置

主要是为了缓存静态文件,而缓存需要指定位置及大小,参见如下配置:

......
http {
......
    proxy_cache_path /usr/local/nginx/cache levels=1:2 keys_zone=mycache:20m max_size=5g inactive=1d;
    proxy_temp_path /usr/local/nginx/cache/tmp;
.......

由于缓存是写入硬盘的,硬盘的 I/O 将会对速度产生重大影响,故建议选择 I/O 好一点的主机,比如华为云学生机

说明

  • proxy_cache_path :用于缓存的本地磁盘目录是 /usr/local/nginx/cache/
  • levels:在 /usr/local/nginx/cache/ 设置了一个两级层次结构的目录。将大量的文件放置在单个目录中会导致文件访问缓慢,所以推荐使用两级目录层次结构,如果 levels 参数没有配置,则 Nginx 会将所有的文件放到同一个目录中。
  • keys_zone:设置一个共享内存区,该内存区用于存储缓存键和元数据,有些类似计时器的用途。将键的拷贝放入内存可以使 Nginx 在不检索磁盘的情况下快速决定一个请求是 HIT 还是MISS,这样大大提高了检索速度。一个 1MB 的内存空间可以存储大约 8000 个 key,那么上面配置的 20MB内存空间可以存储差不多 160000 个 key。
  • max_size:设置了缓存的上限(在上面的例子中是 5G),这是一个可选项。如果不指定具体值,那就是允许缓存不断增长,占用所有可用的磁盘空间,当缓存达到这个上线,处理器便调用 cache manager 来移除最近最少被使用的文件,这样把缓存的空间降低至这个限制之下。
  • inactive:指定了项目在不被访问的情况下能够在内存中保持的时间。在上面的例子中,如果一个文件在 1 天之内没有被请求,则缓存管理将会自动将其在内存中删除,不管该文件是否过期。该参数默认值为 10 分钟(10m)。注意,非活动内容有别于过期内容,Nginx 不会自动删除由缓存控制头部(Cache-Control)指定的过期内容,过期内容只有在 inactive 指定时间内没有被访问的情况下才会被删除,如果过期内容被访问了,那么 Nginx 就会将其从原服务器上刷新,并更新对应的 inactive 计时器。
  • proxy_temp_path:Nginx 最初会将注定写入缓存的文件先放入一个临时存储区域, use_temp_path=off 命令指示 Nginx 将在缓存这些文件时将它们写入同一个目录下,我们强烈建议你将参数设置为 off 来避免在文件系统中不必要的数据拷贝。如果没有配置 use_temp_path=off 或者没有配置则按 proxy_temp_path 设置来给定存储目录,如果 proxy_temp_path 也没配置,则直接缓存到 proxy_cache_path 目录下 。

配置代理

配置

$ sudo  echo '1.1.1.1 www.vircloud.net' >> /etc/hosts
server {
    listen 80;
    server_name www.vircloud.net;
    more_set_headers "X-Node: CN-BJ01";

    location / {
        proxy_pass https://www.vircloud.net;
        proxy_redirect off;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_cache mycache;
        proxy_cache_key $uri$is_args$args;
    }

    location ~ /clear(/.*) {
        proxy_cache_purge mycache $1$is_args$args;
        error_page 405 =200 /clear$1;
    }
}

说明

由于 CDN 节点必须知道源站 IP,否则无法正常工作,所以我们需要先在 hosts 指定源码 IP。

这一段反代服务的配置可以说是最简单的配置了,我们来看一下各个参数的作用:

  • server_name:指定反代的域名,可以与源站不同;
  • more_set_headers:由 headers-more-nginx-module 插件提供的参数,可以自由地添加头部信息,我们这里设置了节点位置;
  • proxy_pass:要反代的域名;
  • proxy_redirect:修改从被代理服务器传来的应答头信息。比如 proxy_redirect http://localhost:8000/blog/ /; 是将源站响应的 http://localhost:8000/blog/ 替换成 /,这样就可以直接通过 https://www.vircloud.net/ 来访问 http://localhost:8000/blog/
  • proxy_set_header X-Forwarded-For:将实际访客的 IP 添加到发送给源站的请求头信息中,不然源站看到的访问 IP 都是代理服务器的 IP;
  • proxy_cache:指定缓存使用的空间,与 proxy_cache_path 配置的空间一致;
  • proxy_cache_key:指定缓存使用的 key 值,方便定位清除缓存;
  • location ~ /clear(/.*):指定访问 /clear/ 目录为清除缓存的入口;
  • proxy_cache_purge:由 ngx_cache_purge 插件提供的参数,解析要清除的缓存实际存储的位置;
  • error_page 405 =200 /clear$1:重写清除缓存的状态码。

完整配置

到这里,不出意外反代已经可以正常工作了,即已经实现了 CDN 节点的搭建。出于实际作业需求,我们可能还需要更多的配置来实现,比如源站挂了,而节点有缓存,那么我们就会希望让节点正常返回,而不是跟着源站返回错误。

下面我们就来看看 Nginx 对反代还提供了哪些参数,以实际完整配置为例:

server {
# 监听端口。
    listen  80;
    listen  443 ssl http2;
# 监听 SNI 域名。
    server_name  www.vircloud.net;
# SSL 配置。
    ssl_certificate        vircloud.net.crt;
    ssl_certificate_key    vircloud.net.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers 'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
# 获取真实 IP。
    include realip.conf;
# 反垃圾。
    include deny-ip.conf;
    include deny-bots.conf;
# SSL 跳转。
    if ($server_port !~ 443){
        rewrite ^(/.*)$ https://$host$1 permanent;
    }
# 指定错误页面,并从当前服务器取,而不向源站服务器请求。
    proxy_intercept_errors on; 
    error_page 403 404 500 502 /error.html;
    location /error.html {
        root /usr/local/nginx/html;
    }
# 添加响应头信息。
    more_set_headers "X-Node: CN-BJ01";
# 设置缓存清除入口。
    location ~ /clear(/.*) {
# 只允许特定 IP 执行清除动作。
        include allowip.conf;
        deny all;
# 缓存 key 转换。
        proxy_cache_purge mycache $1$is_args$args;
        error_page 405 =200 /clear$1;
    }
# 反代配置正式开始。
    location / {
        proxy_pass https://www.vircloud.net;
        proxy_redirect off;
# 向源站传送主机头、客户端真实 IP 等特定信息。
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
# 忽略源站响应的 Cache-Control 头信息。
        proxy_ignore_headers Cache-Control
# 与源站建立连接的超时时间,通常不要超过 75s。
        proxy_connect_timeout 60;
# 与源站通信时节点发送请求的超时时间,超时只在两次连续的写入操作之间作用, 而不是用于传输整个请求,如果源站在此时间内没有收到任何内容,则连接将关闭。
        proxy_send_timeout 60;
# 与源站通信时源站响应数据的超时时间,超时只在两次连续的读操作之间起作用,而不是用于传输整个响应,如果源站在此时间内没有传输任何内容,则连接将关闭。
        proxy_read_timeout 60;
# 开启代理缓冲区。
        proxy_buffering on;
# 响应头的缓冲区大小。
        proxy_buffer_size 128k;
# 网页内容缓冲区大小(4*256k)。
        proxy_buffers 4 256k;
# Nginx 会在没有完全读完后端响应的时候就开始向客户端传送数据,所以它会划出一部分缓冲区来专门向客户端传送数据,然后它继续从后端取数据,缓冲区满了之后就写到磁盘的临时文件中。
        proxy_busy_buffers_size 512k;
# 指定当响应内容大于 proxy_buffers 指定的缓冲区时,写入硬盘的临时文件的大小,如果超过了这个值,Nginx 将与源服务器同步的传递内容,而不再缓冲到硬盘,设置为 0 时则直接关闭硬盘缓冲。
        proxy_max_temp_file_size 512k;
# 一次访问能写入的临时文件的大小。
        proxy_temp_file_write_size 512k;
# 源站返回错误信息自动重试。
        proxy_next_upstream error timeout invalid_header http_500 http_503; 
# 若后端返回错误则返回已缓存的。
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; 
# 指定缓存使用的空间。
        proxy_cache mycache;
# 缓存支持的方法。
        proxy_cache_methods GET HEAD;
# 添加 If-Modified-Since 头信息,如果客户端的请求项已经被缓存过了,但是在缓存控制头部中定义为过期,那么 Nginx 就会在向源站 GET 请求中包含 If-Modified-Since 字段,源站只会在该文件请求头中 Last-Modified 记录的时间内被修改时才将全部文件一起发送。
        proxy_cache_revalidate on;
# 多个 MISS 只有第一个会连接服务器。
        proxy_cache_lock on; 
# 客户端设置 Pragma:no-cache 时节点应当直接请求源站,添加此配置可使 Nginx 支持该请求(默认忽略)。
        proxy_cache_bypass $http_pragma $cookie_nocache; 
# 指定对 200、301 或者 302 等有效代码缓存的时间长度,特定参数 any 表示对任何响应都缓存一定时间长度。
        proxy_cache_valid 200 24d;
        proxy_cache_valid 301 24h;
        proxy_cache_valid 302 304 403 1h;
        proxy_cache_valid any 1s;
# 指定缓存使用的 key 值,方便定位清除缓存。
        proxy_cache_key        $uri$is_args$args;
# 响应头信息添加当前请求资源的缓存状态,如命中 HIT、没有缓存 MISS、过期 UPDATING 等。
        add_header X-Cache    $upstream_cache_status;
# 指定请求多少次才开始缓存。
        proxy_cache_min_uses 1;
# 统一添加请求的资源过期时间头信息
        expires max;
    }

真实 IP

使用反代功能后,实际上对于源站来说,访客是代理服务器而不是真实的访客,Nginx 针对这种情况也给了解决方案,在源站 Nginx 配置中添加如下配置:

......
set_real_ip_from 反代服务器 IP;
real_ip_recursive on;
......

    相关文章

    php中使用 Symfony DomCrawler 来获取 HTML 内容中的 token 值
    PHP请求库Guzzle配置代理
    越速云:简单好用的稳定高性价比的云服务器云手机服务商
    php中检查文本内容中是否包某个字符或者名字
    php8中使用正则匹配,不区分大小写的方法
    皓量云擎:上新游戏面板服务器首月5折低至20元/月,加入推广者计划,返佣高达30%

    发布评论