搭建反向代理降低网站网络延迟

最近我通过在香港机房搭建反向代理,将 Geeknote 的国内访问延迟从 300ms 降低到 50ms,以下分享解决过程。

问题产生

Geeknote 一开始部署在 Fly.io 的香港机房。Fly.io 的特色是在全球提供多个区域机房,通过任播网络使访问者访问靠近自己的边缘节点从而降低访问延迟。理想情况下,中国大陆访客的请求应该到达香港机房,获得最低访问延迟。

可惜的是,Fly 的任播网络优化并没有覆盖中国大陆,中国大陆访问可能会被路由至美国西部。跨太平洋的延迟在 170ms 以上,从大陆到美国再返回香港延迟就要 340ms 以上。即使我在应用层优化得再好,顶着 340ms 的 debuff 也会觉得网站很慢,我决心解决这个问题。

截屏2025-04-03 17.34.25.png

探索过程

我试过套一层 Cloudflare,但实际效果更差。Cloudflare 也基于任播网络,免费版也没有对中国大陆优化,而中国大陆优化网络只对企业用户服务。

后来我给 Fly 应用申请了一个非任播网络的香港区域 IP,实际测试效果不良。请求被路由到了日本然后返回香港,延迟大约在 260ms。

再后来想到搭个香港反向代理,人工选择让请求路由到香港。实测某 A 厂的香港服务器内地延迟 40ms+,很优秀,但是到 Fly 共享 IP 的延迟 260ms+。我这时有点崩溃。

最后灵机一动,发现 A 厂服务器到 Fly 香港区域 IP 的延迟不到 2ms。我终于找到了最短路径,那就是:中国大陆 -> A 厂香港服务器 -> Fly 香港区域 IP -> Fly 香港机房。

截屏2025-04-03 17.34.47.png

路线已经找到,接下来是搭建代理的过程。

解决过程

准备一台到内地和到 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 将来能为客户解决这个问题,免去这番折腾。

总结

通过选择优化线路搭建代理服务器的方式,我降低了网站的访问延迟。

有疑问欢迎在评论区指出。

2
所有评论 0
@Rei
Ruby 程序员,Ruby China 管理员,GeekNote 创建者。
准则 博客 联系 反馈 © 2025 Geeknote