搭建反向代理降低网站网络延迟
最近我通过在香港机房搭建反向代理,将 Geeknote 的国内访问延迟从 300ms 降低到 50ms,以下分享解决过程。
问题产生
Geeknote 一开始部署在 Fly.io 的香港机房。Fly.io 的特色是在全球提供多个区域机房,通过任播网络使访问者访问靠近自己的边缘节点从而降低访问延迟。理想情况下,中国大陆访客的请求应该到达香港机房,获得最低访问延迟。
可惜的是,Fly 的任播网络优化并没有覆盖中国大陆,中国大陆访问可能会被路由至美国西部。跨太平洋的延迟在 170ms 以上,从大陆到美国再返回香港延迟就要 340ms 以上。即使我在应用层优化得再好,顶着 340ms 的 debuff 也会觉得网站很慢,我决心解决这个问题。
探索过程
我试过套一层 Cloudflare,但实际效果更差。Cloudflare 也基于任播网络,免费版也没有对中国大陆优化,而中国大陆优化网络只对企业用户服务。
后来我给 Fly 应用申请了一个非任播网络的香港区域 IP,实际测试效果不良。请求被路由到了日本然后返回香港,延迟大约在 260ms。
再后来想到搭个香港反向代理,人工选择让请求路由到香港。实测某 A 厂的香港服务器内地延迟 40ms+,很优秀,但是到 Fly 共享 IP 的延迟 260ms+。我这时有点崩溃。
最后灵机一动,发现 A 厂服务器到 Fly 香港区域 IP 的延迟不到 2ms。我终于找到了最短路径,那就是:中国大陆 -> A 厂香港服务器 -> Fly 香港区域 IP -> Fly 香港机房。
路线已经找到,接下来是搭建代理的过程。
解决过程
准备一台到内地和到 Fly 香港区域网络都好的服务器,我用的是国内某 A 厂的香港服务器(BGP 多线,非精品线路)。
根据个人偏好,我用到 docker 运行 caddy 搭建反向代理。
首先在服务器安装 Docker。
创建并进入工作目录,例如 ~/caddy
。
创建文件 Dockerfile
,内容为:
FROM caddy:2.9.1-builder AS builder
RUN xcaddy build \
--with github.com/caddyserver/cache-handler
FROM caddy:2.9.1
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
这里编译安装了一个缓存模块,用于缓存 assets,如果不需要可以直接用官方镜像。
创建文件 compose.yml
,内容为:
services:
caddy:
build: .
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./config:/etc/caddy
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
注意这里挂载 etc/caddy
的方式与 caddy 文档的方式不同,直接挂载 Caddyfile
文件将不能热重启 caddy,需要重启容器,挂载目录才可以使用热重启。
创建文件 config/Caddyfile
,内容为:
{
# 开启缓存模块
cache
}
geeknote.net {
# 仅缓存 assets 目录
@assets path /assets/*
handle @assets {
cache
}
# 上游设置
reverse_proxy <fly-regional-ip>:443 {
header_up Host {upstream_hostport}
transport http {
# 设定上游 TLS 的 SNI
tls_server_name geeknote.net
}
}
}
注意 Fly 的区域 IP 其实也是一台代理,需要设置正确的 Host 和 SNI 才能实现 TLS 握手并转发至相应的应用。
用以下命令启动 caddy:
docker compose up -d
需要的时候用以下命令重启 caddy:
docker compose exec -w /etc/caddy caddy caddy reload
查看日志:
docker compose logs caddy -n=1000 -f
最后修改 DNS,将域名指向 Caddy 服务器。
如果配置正确,DNS 生效后请求将到达 caddy,并降低网络延迟。
其他问题
应用层设置可信代理 IP
如果你的应用需要识别客户端 IP(例如频率限制),你可能留意到添加反向代理后,客户的 IP 都被识别成了代理服务器的 IP。这时候需要将代理 IP 添加到可信列表中。
以 Rails 为例,需要添加以下配置:
if ENV["TRUSTED_PROXIES"].present?
config.action_dispatch.trusted_proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES + ENV["TRUSTED_PROXIES"].split(",").map { |proxy| IPAddr.new(proxy) }
end
然后添加环境变量:
TRUSTED_PROXIES=proxy_ip_1,proxy_ip_2
在这个案例下 caddy 和 Fly 区域 IP 都是可信代理,所以在这个列表内添加这两个服务器的 IP。
为什么不直接部署在 A 厂?
虽然 Fly 的网络状况不好,但我依然十分喜欢它管理 App 的方式,这让我节省了很多时间,所以我打算继续部署在 Fly。
代价是部署架构变得复杂,需要额外支出费用:Fly 区域 IP、A 厂服务器、A 厂流量费。
我希望 Fly 将来能为客户解决这个问题,免去这番折腾。
总结
通过选择优化线路搭建代理服务器的方式,我降低了网站的访问延迟。
有疑问欢迎在评论区指出。