我们一直敦促新成员完成 discobot 教程,但有不少人遇到了我们在之前的(staging)Discourse 实例中已经遇到过的问题:最后一篇帖子中的证书生成和实际显示需要很长时间,且没有任何加载或生成的提示。所谓“很长时间”,指的是 10-20 秒的延迟——这足以让人放弃等待。我在 meta 上测试了一下,似乎在那里是即时完成的。
我注意到可以通过 /discobot/certificate.svg?date=Oct+28+2020\u0026user_id=123 这个 URL 按需生成证书,因此我做了一些测试。起初是即时的,但一旦我使用那些最近没有完成教程的用户 ID,就需要等待 10-20 秒(一旦首次生成后速度就很快,所以我猜测某处有缓存)。在等待期间,我还遇到了一些 500 错误,我推测是某种超时,因为刷新后就能正常工作。
在 /logs 中,我看到一条警告,不确定是否与此相关:
Failed to process hijacked response correctly : Net::ReadTimeout
traceback
/usr/local/lib/ruby/2.6.0/net/protocol.rb:217:in `rbuf_fill'
/usr/local/lib/ruby/2.6.0/net/protocol.rb:191:in `readuntil'
/usr/local/lib/ruby/2.6.0/net/protocol.rb:201:in `readline'
/usr/local/lib/ruby/2.6.0/net/http/response.rb:40:in `read_status_line'
/usr/local/lib/ruby/2.6.0/net/http/response.rb:29:in `read_new'
/usr/local/lib/ruby/2.6.0/net/http.rb:1509:in `block in transport_request'
/usr/local/lib/ruby/2.6.0/net/http.rb:1506:in `catch'
/usr/local/lib/ruby/2.6.0/net/http.rb:1506:in `transport_request'
/usr/local/lib/ruby/2.6.0/net/http.rb:1479:in `request'
rack-mini-profiler-2.0.2/lib/patches/net_patches.rb:9:in `block in request_with_mini_profiler'
rack-mini-profiler-2.0.2/lib/mini_profiler/profiling_methods.rb:39:in `step'
rack-mini-profiler-2.0.2/lib/patches/net_patches.rb:8:in `request_with_mini_profiler'
/var/www/discourse/lib/final_destination.rb:370:in `block in safe_get'
/var/www/discourse/lib/final_destination.rb:414:in `block in safe_session'
/usr/local/lib/ruby/2.6.0/net/http.rb:920:in `start'
/usr/local/lib/ruby/2.6.0/net/http.rb:605:in `start'
/var/www/discourse/lib/final_destination.rb:411:in `safe_session'
/var/www/discourse/lib/final_destination.rb:362:in `safe_get'
/var/www/discourse/lib/final_destination.rb:131:in `get'
/var/www/discourse/lib/final_destination.rb:152:in `get'
/var/www/discourse/lib/file_helper.rb:55:in `download'
/var/www/discourse/plugins/discourse-narrative-bot/plugin.rb:113:in `fetch_avatar'
/var/www/discourse/plugins/discourse-narrative-bot/plugin.rb:98:in `block in generate'
/var/www/discourse/lib/hijack.rb:56:in `instance_eval'
/var/www/discourse/lib/hijack.rb:56:in `block in hijack'
/var/www/discourse/lib/scheduler/defer.rb:94:in `block in do_work'
rails_multisite-2.3.0/lib/rails_multisite/connection_management.rb:68:in `with_connection'
/var/www/discourse/lib/scheduler/defer.rb:89:in `do_work'
/var/www/discourse/lib/scheduler/defer.rb:79:in `block (2 levels) in start_thread'
我理解证书是在本地生成的,不需要任何外部网络请求?
该机器是 t3a.medium(4GB 内存),论坛并不特别繁忙(负载约 0.4,仍有大量可用内存)。在等待证书生成的过程中,CPU 使用率似乎没有任何变化,因此这似乎不是瓶颈。
我们并不是唯一 遇到此问题 的人(不确定 这个 是否相关),但我没看到那里有任何能帮上忙的信息。
不确定我是否发现了什么,但如果我多次刷新证书 URL,最终会收到一个提示让我放慢速度的错误。是否存在某种 URL 限流机制,在许多人同时通过同一 URL 路径生成证书时产生干扰?可能不是,但我实在想不出为什么会这样。欢迎提供任何提示。
Falco
(Falco)
3
该路线确实较慢,因为它是新的。
除了尝试加快其速度外,我们是否可以在消息中添加一段文案,告知用户 Discobot 正在准备中,这将需要一分钟?
我此前并不知道它已知速度较慢——至少,我想在帖子中更新说明这一点可能会有所帮助,但我仍然认为,如果等待时间过长,一些人可能会放弃使用。
我必须说,生成一个(我假设是)已存在的、填充了某些文本的 SVG 文件,竟然需要 10 到 20 秒,这似乎异常漫长。据我所见,这似乎并非受限于 CPU。那么瓶颈究竟在哪里呢?
我正在考虑编辑这里的文案,在上一步中加入一句“请稍候,我正在为您生成一些很酷的东西!”。但在 try.discourse.org 上,证书生成速度非常快:
discobot 最后一步时间戳:11 月 2 日,下午 6:46 1604371566089
discobot 证书生成步骤时间戳:11 月 2 日,下午 6:46 1604371567470
我不太清楚底层时间戳的单位是什么,但差异仅为 1381。
我在同一话题上尽可能快地回复,得到了以下时间戳,间隔轻松不到几秒:
1604373488630
1604373492182
差异为 3552……而我几乎是粘贴加键盘快捷键,以最快的速度发布的!
所以……你们的服务器是不是……有点问题?
我确实是在谈论证书的生成过程——而不是嵌入该证书的回复帖子(后者很快)。这就是该问题给部分会员带来的影响:
不,t3a.medium 属于中等规格实例,且负载并不高。
正如我所说,这似乎不是 CPU 瓶颈问题,因为在我等待例如 /discobot/certificate.svg?date=Oct+28+2020&user_id=123 这样的请求完成时,CPU 的繁忙程度并不比平时更高(平时本身就很空闲)。
问题似乎与某个网络请求超时有关:
证书上的头像似乎是一个 data:image/png;base64 格式的数据块。据我所见,系统尝试下载头像时发生了超时?也许这就是导致此处变慢的原因?
作为背景说明,该论坛启用了 SSO,头像为存储在 S3 上的 HTTPS URL。我尝试在 Docker 容器内部获取其中一些头像,结果成功且几乎是瞬间完成——没有防火墙问题等。论坛的其他功能运行都很迅速,唯独生成证书耗时很长。
如果需要进一步调试,我很乐意配合,只要有人能指点我应该检查哪些方面。
对我来说,在 try.discourse.org 上图片几乎是瞬间出现的。我没有察觉到超过半秒的明显延迟,甚至可能都没有?如果你不相信,不妨自己试试!
所以我回到最初的说法:为什么在你的服务器上这么慢,而在 try.discourse.org 上却这么快?
确实如此……这正是我寻求支持的原因。
我明白这属于“在我机器上能运行”的情况,按理说不应该在 Docker 环境中出现,但无奈现实如此,而且我似乎并非唯一遇到此问题的人。
如果有任何关于如何在本地调试此问题的建议,我将不胜感激。
我花了一些时间调查这个问题,似乎问题出在 discourse-narrative-bot 在获取用户头像以包含在证书时发生了阻塞。具体位置如下:
该论坛使用 SSO,头像链接是 Amazon S3 的 URL——这些链接速度快且不受 Amazon 的速率限制。不过,该函数获取的头像 URL 格式如下:
https://forum.tld/user_avatar/forum.tld/username/240/18705_2.png
导致证书生成变慢的超时错误,是否可能是由于触发了 Discourse 的 速率限制?例如,从 Discourse 实例本身请求头像的速度过快,或者当多个用户同时进行机器人教程时,对 Amazon S3 的访问过于频繁?
Falco
(Falco)
13
有意思。我们在其中内嵌了一个 base64 图片。我在想,为什么我们不在 image SVG 标签中直接链接到外部资源呢:thinking:
我深入研究了该插件的历史,一直追溯到我们首次添加此功能的地方:
看起来我们一直使用 base64 内嵌的头像。
编辑:经过一番研究,我们使用 base64 内嵌是因为证书是通过 img HTML 标签加载的。如果要使用更便宜的头像,我们需要改用 object 标签,但这在我们的 CSP 中默认是被阻止的。或者将整个 SVG 内嵌到帖子内容中。我认为 iframe 是我们最好的选择。
我已为 @tgxworld 的评论创建了 https://github.com/discourse/discourse/pull/11344。
我原本以为,将 base64 编码的头像嵌入 SVG 中,原因与它“对徽标所做的处理”相同:这样证书就能成为某一时刻的快照——获奖者或许会将其保存下来,无需依赖任何外部资源,从而确保日后仍能完全一致地渲染。
而使用 iframe 的方式会阻碍用户轻松地在帖子中分享并炫耀该证书。我不知道这在其他论坛上是否常见,但我见过一些用户通过直接复制图片 URL 来实现这一目的。
如果采用链接到外部资源的方式,使用 <image> 元素是否可行?这样或许就能避免修改 allowed_iframes 的需求。
Falco
(Falco)
15
如果你仔细阅读上面的 PR,就会发现它正是这样做的 
不过,如果我们像现在这样通过 HTML 的 img 标签加载 SVG,那么 SVG 中 <image> 标签所引用的外部图片将无法加载。要实现这一点,必须使用 object 或 iframe 进行加载。
显然我对 SVG 的了解还不够。iframe 方法虽然阻止了证书被引用或复制,但我还是很高兴这个问题正在以某种方式得到解决,因为它将解决我们的问题,同时也能修复证书上缺失的徽标的问题。
Falco
(Falco)
17
@mentalstring 我刚合并了新的证书生成代码。现在应该会快很多。
很高兴看到这个被合并了。
我们目前处于 stable 版本,现在没有好的方法来测试这一点。但如果 fetch_avatar 不再被使用,我预计我们的 Net::ReadTimeout 错误会随之消失。我将此标记为已解决,如果在我们更新时因某种原因再次遇到相同问题,我会重新打开。谢谢!