除了某些土豪站用全闪以外,大多数开源镜像站都是以机械盘为主,当带宽充裕的时候IO显然会成为瓶颈,那么采用少量的SSD来提升性能几乎是唯一的办法。加SSD的方法有很多种:
- lvmcache bcache bcachefs:因在用ZFS所以没有测试这类方案,看上去比较复杂;
- ZFS L2ARC:实际测试基本无效果,zpool iostat 看L2ARC读取很少;
- nginx proxy_cache:效果显著,但是必须用反代。
proxy_cache配置在前端nginx上,前端nginx访问SSD,前端nginx需要编译安装ngx_cache_purge以便主动清除缓存。前端nginx反代给后端nginx,后端nginx访问机械盘提供实际的数据。当然也可以一个nginx同时访问SSD和机械盘,这个nginx自己反代给自己,因为只有反代了才能缓存,前端nginx缓存配置如下:
http {
……
map $http_origin $good_origin {
~*([.|/]|^)mirrorz.org$ 1;
~*(/|^)mirrors.cernet.edu.cn$ 1;
default 0;
}
upstream zfs_server {
server 192.168.10.10:80;
keepalive 300;
}
proxy_cache_path /cache levels=1:2 use_temp_path=off keys_zone=mirror:128m inactive=28d max_size=8192g;
……
}
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
listen *:443 quic;
listen [::]:443 quic;
server_name mirror.nju.edu.cn mirrors.nju.edu.cn;
……
location / {
include conf.d/proxy_zfs;
}
location /.mirrorz/ {
if ($good_origin) {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Vary' "Origin";
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-TUNA-MIRROR-ID';
add_header X-Cache-Status $upstream_cache_status;
}
include conf.d/proxy_zfs;
}
……
}
proxy_pass http://zfs_server;
proxy_redirect ~*^http://[^/]+(:\d+)?(/.*)$ $2;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_cache mirror;
proxy_cache_key $uri;
proxy_cache_valid 200 7d;
proxy_cache_valid 301 302 1d;
proxy_cache_valid any 1m;
proxy_cache_lock on;
proxy_cache_lock_timeout 600s;
proxy_cache_lock_age 600s;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
proxy_cache_revalidate on;
cache_purge_response_type text;
proxy_cache_purge PURGE from 127.0.0.1 192.168.10.10;
add_header X-Cache-Status $upstream_cache_status;
- proxy_cache_path:
use_temp_path=off 临时文件直接放到 cache 目录中,防止文件在 temp_path 和 cache_path 之间移动。缓存文件会先放在 temp_path 中,下载完了再移动到 cache_path 下,设定成 off 比较简单安全。
keys_zone=mirror:128m 内存缓存区名称是 mirror 容量是128MB,每MB内存可以缓存八千个key,可以估算一下key的数量设定缓存区大小。
inactive=28d 超过28天没有访问的缓存会被删除,无论是否 valid,防止缓存被长期未用的数据占用。
max_size=8192g 要比缓存目录 /cache 容量小,如果缓存用尽 nginx 会报错 [crit] …… failed (2: No such file or directory) 而且会直接断开客户端链接。
- proxy_cache mirror:匹配前面 proxy_cache_path 的内存缓存区名称。
- proxy_cache_key $uri:请求的URL,用这个URL用作缓存的key,去掉了参数后的真实文件路径。只要这个key样则nginx用缓存中一样的数据回复。
- proxy_cache_valid 200 7d; proxy_cache_valid 301 302 1d; proxy_cache_valid any 1m;:HTTP状态码200的缓存7天、301/302的缓存24小时、其它的缓存1分钟,缓存达到这个时间就会被抛弃,防止数据被长期缓存不能得到更新。
- proxy_cache_lock on:当有多个请求同一个文件时(同一个key),只有第一个请求从反代中获取,通过加锁让后续的请求等待第一个请求完成后从缓存中获取,防止并发从后端中请求同一个文件,加重后端负载。
- proxy_cache_lock_age 600s:第一个请求锁定600秒,到期后就释放锁,下一个请求发送到后端。
- proxy_cache_lock_timeout 600s:后续的请求如果等待600秒还未从缓存中获取,就直接从后端获取,但是不缓存。
- proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504:当请求后端时出现错误(error)或超时(timeout)时使用过期缓存,出现一些5xx错误也用过期缓存,过期的缓存总比没有好。
- proxy_cache_revalidate on:更新缓存时使用 If-Modified-Since 和 If-None-Match 请求后端,后端可以返回304而非200减少传输。
- cache_purge_response_type text:清除缓存后返回文本格式的结果。
- proxy_cache_purge PURGE from 127.0.0.1 192.168.10.10:使用PURGE这个HTTP方法清除缓存,允许本地和192.168.10.10来清除。
- add_header X-Cache-Status $upstream_cache_status:添加HTTP头X-Cache-Status以便客户端知道缓存情况。
后端nginx可能需要设定某些文件的不缓存:
server {
listen 80;
root /mirror;
……
location ~ ^/no_cache/ {
expires -1;
……
}
……
}
- expires -1:负值禁用缓存,通过添加HTTP头 Cache-Control: no-cache 和 Expires,Expires被设定为当前时间之前,这样前端nginx就不会缓存 mirrorz 的URL了。
编辑 tunasync worker 的配置文件添加一个脚本,使得每个同步作业结束后均清除前端上这个镜像的所有缓存:
#tunasync worker的配置文件
[global]
……
exec_on_success = [ "/home/mirror/postexec.sh" ]
exec_on_failure = [ "/home/mirror/postexec.sh" ]
……
#上述的postexec.sh脚本内容
#!/bin/bash
MIRROR_DIR="/mirror/"
DIR="${TUNASYNC_WORKING_DIR/"$MIRROR_DIR"/}"
/usr/bin/curl --silent --output /dev/null --request PURGE "https://mirror.nju.edu.cn/$DIR/*"
镜像站某些状态文件会频繁的周期更新,每次更新后都用 curl --silent --output /dev/null --request PURGE "https://mirror.nju.edu.cn/status/*" 清除一下缓存。