头像迁移到 S3 兼容的 R2 后加载时间过长

你好,

我刚迁移到 R2,一切都非常顺利。所有图片都使用了 S3 CDN 链接。但我发现了一个问题:头像加载时间很长。平均需要 3 到 4 秒,无论我是点击用户头像还是查看帖子内部。这是否正常?

嗯,我怀疑可能是以下三种问题之一,但对我来说最有可能的是即时调整大小。

1. 即时头像调整大小

当你将上传迁移到 R2 时,它移动了原始图像;然而,Discourse 使用多种不同尺寸的头像(例如帖子中为 45px,用户卡片中为 120px)。

如果这些特定优化尺寸迁移不完全,或者尚未生成,Discourse 必须在用户点击时同步生成它们:

  1. Discourse 从 R2 下载原始头像到本地服务器
  2. 使用 ImageMagick 调整大小
  3. 将新尺寸上传回 R2
  4. 将浏览器重定向到新 URL,此过程需要 3 到 4 秒

验证方法: 硬刷新页面——如果头像第一次加载需要 3-4 秒,但第二次加载立即显示,那么这正是正在发生的事情。

修复方法: 随着用户浏览和尺寸生成,这将自然修复。但你可以通过强制服务器在后台预生成所有头像来立即修复它,方法是 SSH 进入你的服务器并运行:

./launcher enter app
rake avatars:refresh

2. 3 秒 IPv6 超时

如果头像在多次刷新后每次仍然需要 3-4 秒,它们可能遇到了网络超时。

Cloudflare R2 API 端点是双栈的,即它们同时使用 IPv4 和 IPv6。如果你的服务器 Droplet 分配了 IPv6 地址,但主机的 IPv6 网关路由不正确,Ruby 内部连接到 R2 存储桶时将首先尝试 IPv6,挂起 3 秒(这是 Linux TCP 的默认超时),失败,然后立即使用 IPv4 成功。

验证方法: SSH 进入服务器并运行:

curl -I -6 https://cloudflare.com

如果它挂起几秒钟并失败,则服务器的 IPv6 已损坏,导致每个内部 S3 API 检查都遭受 3 秒延迟。

修复方法: 你需要修复主机控制面板中的 IPv6 路由,或者甚至完全禁用 Droplet 上的 IPv6。

3. Gravatar 延迟

如果你的网站配置为检查 Gravatar 更新,它可能在渲染头像之前会 ping Gravatar 的外部服务器。如果服务器的出站连接缓慢(通常也与 DNS 或 IPv6 有关),它可能会阻止头像渲染。

验证方法: 在服务器上运行此命令
curl -I -6 https://gravatar.com
如果它挂起 3 秒,则 IPv6 已损坏(见上文)

与 Gravatar 相关的修复: 在你的 Discourse 设置中,转到 automatically download gravatars(自动下载 Gravatar),暂时关闭它,看看是否能修复——我认为这不是问题,但如果是,你可以保持设置关闭,或者如上所述修复 IPv6 路由,或者更改 DNS 解析器。

感谢您的快速回复。我想我之前已经尝试过 rake avatars:refresh 了,但我不完全确定。

以前对我有效、能让头像立即打开的方法是:第一次点击时它会加载,第二次点击时就能瞬间打开。但这可能是由于缓存导致的。另外,我刚刚测试了您的第二个建议,返回的是“HTTP/2 301”,并附带其他几行信息。第三个建议的情况也相同。由于我需要恢复快照,我将在几天后再次运行 avatars:refresh。再次感谢!

Gravatar

server: nginx
date: Mon, 22 Jun 2026 19:29:00 GMT
content-type: text/html; charset=utf-8
content-length: 0
content-language: en
expires: Wed, 11 Jan 1984 05:00:00 GMT
cache-control: no-cache, must-revalidate, max-age=0
x-redirect-by: Gravatar
location: https://en.gravatar.com/
alt-svc: h3=":443"; ma=86400
strict-transport-security: max-age=31536000; includeSubdomains; preload

CF

HTTP/2 301
date: Mon, 22 Jun 2026 19:27:00 GMT
content-type: text/html
content-length: 167
location: https://www.cloudflare.com/
cache-control: max-age=3600
expires: Mon, 22 Jun 2026 20:26:59 GMT
set-cookie: __cf_bm=eBP2aJ7Eg30nHPuvMMNxxKrgNtcNwKs0WDgnYyONeus-1782156420-1.0.1.1-sXpW27iuhGDF615cOfwNFybH4IMxgvZy3uA_3X_o..402T_3KSgT7CSymipL5RjdpGe3raWEqsVxQFFLPKRoDjfoT7B.0rqyDt.osbkOF98; path=/; expires=Mon, 22-Jun-26 19:57:00 GMT; domain=.cloudflare.com; HttpOnly; Secure; SameSite=None
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=QfYqSekEDPJHC2k%2BMjHN0cGjz172tmUWe2GSR8EgwNLh3TGjFYkQ0vwPxlzY1NcBcKFOMaAi4FlgjqjhETOOtHf%2BH9KdQSvqN3OME2Uh1i4nHIw%2Fy1qkvSpf4jxDchM7CaDW80tJkjBV4OqF"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
strict-transport-security: max-age=15780000; includeSubDomains
server: cloudflare
cf-ray: a0fda5d8ecd6b26d-LAX
alt-svc: h3=":443"; ma=86400

是的,根据你的回复,我几乎可以确定是问题 #1,因为针对 Cloudflare 和 Gravatar 的 curl 命令结果看起来都符合预期。方便的时候请尝试运行 rake avatars:refresh,并告诉我是否有效。

嗨,Lilly,我仍然遇到同样的问题。即使执行了 rake avatars:refresh 也一样。/latest 页面也有同样的问题。我尝试清理了浏览器和 Cloudflare 的缓存,但仍未成功。也许我需要再等一段时间?我目前是在一个有 4500 名用户的论坛上进行测试。

不要再清除浏览器或 Cloudflare 的缓存了——当你为大量用户运行 rake avatars:refresh 时,操作并不会立即完成。相反,它会将成千上万个任务放入 Sidekiq 后台处理,具体耗时取决于服务器的 CPU 性能,可能需要数小时。抱歉,我本应该提到 Sidekiq 以及根据用户数量的不同,处理可能需要一段时间。

请访问 your-forum.com/sidekiq/queues 并观察队列。等待队列完全清空。一旦 Sidekiq 处理完毕,所有头像尺寸都应永久存储在您的 R2 存储桶中,我认为您的头像加载速度应该会恢复正常。

好的,我觉得是别的地方出了问题。我的队列里什么也没有。但是,如果我点击任何用户的头像,在 tail -f log/production.log 中会出现以下内容:Sent file /var/www/discourse/tmp/avatar_proxy/3689d91eb5e1013beef831c585b5e62edeeecbd6.jpeg (0.2ms)

哇,好的。这很可能就是关键线索,指向了另一个问题。

日志中出现 avatar_proxy 通常意味着 Discourse 拒绝直接从 Cloudflare R2 CDN 提供头像。相反,Discourse 会积极拦截请求,将图片从 R2 下载到本地服务器的 /tmp 文件夹,然后使用 Ruby 将图片提供给浏览器。因此,我认为这完全绕过了 CDN,也解释了 3 秒的延迟——我怀疑服务器是在每次请求时都手动获取并加载文件:grimacing:

Discourse 仅在少数非常特定的场景下使用 avatar_proxy,通常是因为隐私或安全设置强制服务器屏蔽外部 URL。

请在“管理”>“站点设置”中检查以下设置:

找到 external system avatars url(外部系统头像 URL)——如果该框中有任何内容(例如 /letter_avatar_proxy/v4/...),请将其清空。这应该能阻止 Discourse 代理默认的字母头像。同时也值得检查 uploaded avatars allowed groups(允许上传头像的组),确保其设置为 TL_0

另外,请再次检查 DISCOURSE_S3_CDN_URL,确保其正确无误,没有尾随斜杠或拼写错误?

重新映射自定义头像:
看起来您的数据库中仍然包含原始的 R2 存储桶 URL,而不是新的 CDN URL;由于它们不匹配,您的论坛可能出于安全原因正在代理它们。

在 Rails 控制台中检查,看看 Discourse 到底在应对什么:

./launcher enter app
rails c

选择一个头像加载缓慢的用户名

u = User.find_by_username("the_selected_username")
u.user_avatar.custom_upload.url

如果输出返回的是原始存储桶 URL,说明您之前的重新映射没有覆盖所有内容(也许遗漏了子域名或协议)。

要修复此问题,请 SSH 登录到您的服务器,再次进入容器(不是 Rails)(./launcher enter app),然后运行重新映射工具(再次运行,哈哈)以将原始 URL 替换为您的 CDN URL:

discourse remap "https://<your-raw-cloudflare-url>.r2.cloudflarestorage.com" "https://cdn.your-domain.com"

然后,以防万一,使用 // 代替 https:// 再运行一次。

顺便问一下,出于好奇,您使用的是哪家主机服务?我有和您类似的通用设置,但尚未遇到此问题。所以我也很感兴趣您的配置,并想尝试复现一下这个问题。

我得到的 URL 显示的是 S3 CDN 的链接,我可以在浏览器中打开这张图片。

暂时先不用 S3 了,因为我现在确实不需要它。

而且我已经使用 Advinservers 很长时间了。

谢谢你的帮助,非常感谢。