没有不值得去解决的问题,也没有不值得去学习的技术!

A Tour of Go 中文版手机端运行与格式化偶发失败排查:从间歇性报错到补强 Nginx 可观测性

【图 1:微信中运行 Go 示例时出现 Error communicating with remote server.】

作者:

系列帖子

图 4:发布生产并刷新 EdgeOne 缓存后,再次通过真实手机访问,顶栏项目名称已经完整保持在一行,最终移动端验收通过。

(1) A Tour of Go 多语言翻译项目:修复手机端首页顶栏标题换行,并完成真机验证

图 1:新生产域名 go-dev.shuijingwanwq.com 已正常访问,Go 示例远程运行成功

(2) A Tour of Go 多语言翻译项目:从 go-tour 到 go-dev,完成 EdgeOne、Nginx 与 HTTPS 生产域名迁移

图3:访问过去的 Go Tour 简体中文站 tour.go-zh.org 时,当前已经无法正常建立连接。

(3) 从「A Tour of Go 中文版」这个搜索词开始:我决定做一个持续维护的 Go Tour 中文版

本地运行的 A Tour of Go「Methods continued」课程页面,左侧为课程说明,右侧为 Go 代码编辑器及运行结果。

(4) A Tour of Go 中文版项目设计冻结:从 101 页到 103 页,从 Gin 转向 Cobra CLI

A Tour of Go 中文版开发实战:英文基线、在线运行与 101 页上游同步体系

(5) A Tour of Go 中文版开发实战:英文基线、在线运行与 101 页上游同步体系

go-tour-i18n 项目完成首个简体中文课程页面 welcome/1 的整页翻译、结构校验和本地预览。

(6) A Tour of Go 多语言翻译项目实录:完成首个 zh-CN 页面翻译闭环

图 1:DeepSeek 官方更新日志显示,本次只更新 DeepSeek-V4-Flash,DeepSeek-V4-Pro API 与 APP、Web 模型均未更新

(7) A Tour of Go 中文翻译项目进展:完成前 8 页、修复 present 语法,并暂缓 DeepSeek 对比

图 1:generics/1 简体中文页面本地预览

(8) 一个页面重试五次:使用 GLM-5.2 翻译 A Tour of Go 时遇到的 Token 与 Present 结构问题

图 1:methods/16 简体中文页面浏览器预览

(9) A Tour of Go 中文翻译实录:如何只翻译教学代码注释,而不破坏 Go 代码

图 2:A Tour of Go methods/20「练习:错误」中文候选页面最终渲染效果

(10) A Tour of Go 多语言翻译:当正确的中文语序被受保护标记顺序校验误判

图 3:methods/24 中文页面,静态代码、Bounds 行内代码和链接内的 image.Rectangle 均正确渲染

(11) A Tour of Go 中文翻译完成 7 个代表页校准:最后 3 页又发现了哪些真实问题

图 2:首批 10 个普通页面经过试跑与流程校准后全部进入 ready

(12) A Tour of Go 多语言翻译项目:首批 10 个普通页面试跑,从 2 个 blocked 到全部 ready

图 2:flowcontrol/6 最终中文页面,return 与 v 为适应自然中文语序发生整体换位

(13) A Tour of Go 中文翻译第二批实跑:从 Inline Code 顺序误判到历史响应重新验证

图 1:flowcontrol/6 首次原始输入实验直接通过统一自动校验

(14) A Tour of Go 多语言翻译项目:从 swap 问题重新审视 Token 保护,原始输入与最小保护的一次真实实验

图 7:moretypes/1 最终中文页面预览。左侧课程正文、静态代码块、行内代码和教学注释均正常渲染,右侧官方 Go 示例继续保持原样。

(15) A Tour of Go 多语言翻译实战:第三批 10 页全部 Ready,继续校准 Protected Token 的结构角色

图 4:上游源码确认共有 103 个课程页面,zh-CN 最终达到 ready=103、pending=0、blocked=0

(16) A Tour of Go 多语言翻译项目:zh-CN 103 个课程页面全部 ready,课程正文翻译阶段完成

图 4:GitHub 与 ChatGPT 已成功连接

(17) ChatGPT 连接 GitHub 实测:让 AI 直接读取 go-tour-i18n 仓库参与译文审核

图 3:中文 Go 术语校准完成后,将确定的译法写入 zh-CN glossary

(18) 为什么 channel 最终选择“通道”:A Tour of Go 中文术语统一的一次实践

图 1:技术含义基本正确,但“返回一个返回……”已经明显影响阅读,因此仍被列入 C 类修订

(19) 103 页逐页审核后,只返修 16 页:A Tour of Go 中文正文发布前质量审计

图 2:中文课程页面以及已经完成本地化的编辑器控制区

(20) A Tour of Go 多语言翻译项目:公共 UI 本地化完成,从课程译文走向完整中文界面

图 2:完整 zh-CN 正式投影成功生成,103 个课程页面被重新组装为 7 个 .article

(21) 从 103/103 ready 到完整站点验收:A Tour of Go 中文版补齐正式投影与课程元数据本地化

图 3:从 production bundle 独立启动的 A Tour of Go 简体中文页面,课程正文、公共 UI 和官方 Go 示例均已进入最终发布形态。

(22) A Tour of Go 多语言翻译项目:103 页全部完成后,我终于生成了可独立部署的 zh-CN Production Bundle

图 1:A Tour of Go 简体中文版正式生产页面,远程运行已经成功输出“Hello, 世界”。

(23) A Tour of Go 多语言翻译项目:zh-CN 正式上线,从生产发布到 go.dev Playground 的完整部署记录

图 1:A Tour of Go 多语言翻译项目正式生产首页

(24) A Tour of Go 多语言翻译项目正式上线:从项目首页、公共 Footer 到生产发布元数据的完整收尾

图 3:GA4 g/collect 请求返回 HTTP 204,并携带当前课程页面地址,确认 Google Analytics 已产生真实事件上报

(25) A Tour of Go 多语言翻译项目:接入 Google Analytics 与百度统计,从 systemd 注入到 EdgeOne 与真实上报验证

图 1:生产环境 robots.txt 与 sitemap.xml 已正确对外提供,Sitemap 共包含 104 个规范 URL。

(26) A Tour of Go 中文站上线后:补齐 robots.txt、Sitemap,并完成五大搜索引擎提交

图 3:生产自动部署脚本第一次真实运行时,systemd 已经显示 active,但首次 localhost 探测仍然得到 HTTP 000;随后连续 3 次同时满足 active + HTTP 200 后,脚本才判定部署成功,并继续完成公网 HTTP 200 验收。这次真实运行验证了连续应用层健康检查的必要性。

(27) A Tour of Go 多语言翻译项目:从一次 203/EXEC 故障到生产自动部署脚本落地

图 1:阿里云生产环境访问 go.dev Playground 时出现 TLS 握手超时

(28) A Tour of Go 多语言翻译项目:为 Playground 搭建独立的 ZgoCloud 执行代理

图 3:清除 /tour/script.js 缓存后,Run 已直接请求 ZgoCloud /compile 并返回 HTTP 200

(29) A Tour of Go 多语言翻译项目:将生产 Run / Format 切换到 ZgoCloud 执行节点

【图 1:微信中运行 Go 示例时出现 Error communicating with remote server.】

(30) A Tour of Go 中文版手机端运行与格式化偶发失败排查:从间歇性报错到补强 Nginx 可观测性

前一天,我刚完成 A Tour of Go 中文版生产环境 Playground 执行链路的一轮调整,让浏览器中的“运行”和“格式化”请求直接访问部署在 ZgoCloud 服务器上的 Playground 代理。

此前的部署和调整过程,可以参考:

ZgoCloud 服务器与 Playground 代理部署相关记录

A Tour of Go Playground 浏览器直连代理相关记录

调整完成以后,电脑端“运行”和“格式化”一直正常,手机端此前也做过真实测试并成功。

但第二天再次使用手机访问课程页面时,却出现了一个更麻烦的问题:

手机端的“运行”和“格式化”开始偶发失败,而且问题无法稳定复现。

相比持续失败,这种低频、间歇性的故障反而更难定位。

手机端首先出现运行失败

我最早是在微信中打开 A Tour of Go 中文版课程页面:

https://go-dev.shuijingwanwq.com/tour/welcome/1

点击“运行”以后,没有得到 Go 程序正常输出,而是出现:

Plaintext
Error communicating with remote server.
【图 1:微信中运行 Go 示例时出现 Error communicating with remote server.】
【图 1:微信中运行 Go 示例时出现 Error communicating with remote server.

这时候首先容易怀疑的是:

  • 微信内置浏览器兼容性;
  • 手机网络;
  • Wi-Fi 与 5G 的差异;
  • 浏览器直接访问 ZgoCloud 8443 端口失败;
  • ZgoCloud 到官方 Playground 的代理异常。

但随后出现的情况,让这些判断都没有那么简单。

格式化也会偶发失败

同一台手机中,“格式化”同样出现过失败。

不过它显示的错误不是运行时的通信提示,而是:

Plaintext
[object Object]
【图 2:手机端点击“格式化”以后显示 [object Object]】
【图 2:手机端点击“格式化”以后显示 [object Object]

这至少说明两件事情。

第一,“运行”和“格式化”共同依赖的远程 Playground 请求链路值得重点检查。

第二,前端对于格式化失败的错误处理本身也不够友好。即使网络真的出现异常,最终用户也不应该直接看到 JavaScript 对象转换出来的 [object Object]

不过在根因还没有确定之前,我暂时没有急着修改前端错误提示。

先把通信问题查清楚更重要。

奇怪的是,很快又恢复正常了

随后我重新测试微信,运行又恢复正常。

接着继续测试:

  • 手机自带浏览器;
  • QQ 浏览器;
  • 百度 APP;
  • Wi-Fi;
  • 5G。

这些环境都出现过成功的情况。

例如后续再次修改示例代码,然后运行:

Go
fmt.Println("Hello, 世界rrrrr")

能够正常得到:

Plaintext
Hello, 世界rrrrr
【图 3:手机浏览器中再次运行修改后的 Go 代码成功】
【图 3:手机浏览器中再次运行修改后的 Go 代码成功】

这时候基本可以排除一种很简单的结论:

问题不是“微信一定失败”,也不是“某一个手机浏览器一定失败”。

同样也不能归结为:

Plaintext
Wi-Fi 失败,5G 正常

或者:

Plaintext
5G 失败,Wi-Fi 正常

两种网络环境都能够成功。

电脑端也做了额外验证。

由于这台电脑平时可能使用自己的网络服务,我一度怀疑电脑端之所以始终正常,是不是因为网络路径不同。

于是主动断开相关网络服务后重新测试。

结果电脑端“运行”和“格式化”仍然正常。

所以问题越来越像一种真正的间歇性网络或客户端通信异常

开始从 ZgoCloud Nginx 日志寻找证据

既然浏览器直接请求 ZgoCloud Playground 代理,下一步就直接检查服务器日志。

当前生产请求主要是:

Plaintext
浏览器
→ play.go-dev.shuijingwanwq.com:8443
→ ZgoCloud Nginx
→ play.golang.org

其中:

Plaintext
POST /compile

负责运行代码,

Plaintext
POST /fmt

负责格式化代码。

先统计当天所有相关请求,得到:

Plaintext
52 POST /compile -> 200
11 POST /fmt -> 200
5 OPTIONS /fmt -> 204
2 GET /compile -> 403
1 GET /fmt -> 403
【图 4:当天 /compile 和 /fmt 请求状态统计】
【图 4:当天 /compile/fmt 请求状态统计】

这组结果很有意思。

真正来自浏览器正常业务流程的:

Plaintext
POST /compile
POST /fmt
OPTIONS /fmt

全部成功。

其中:

Plaintext
52 次 POST /compile

全部返回:

Plaintext
200
Plaintext
11 次 POST /fmt

也全部返回:

Plaintext
200

CORS 预检:

Plaintext
5 次 OPTIONS /fmt

全部是:

Plaintext
204

剩下的 3 次 403 并不是实际用户运行代码产生的请求,而是 GoogleOther 使用 GET 方式访问 /compile/fmt

当前 Nginx 配置本来就只允许正常的 POST 和 OPTIONS 请求,所以这种 GET 返回 403 属于预期行为。

也就是说:

当天只要正常 Playground 请求真正进入了 Nginx access log,就没有发现一次 4xx 或 5xx 的实际业务失败。

但最早的失败请求甚至没有进入 Nginx

继续按照手机截图时间回查日志。

最早一次明显失败发生在大约 14:07。

检查 14:05 到 14:10 的 Playground access log,却只找到电脑 Firefox 在 14:05:50 发出的一次正常 /compile

Plaintext
POST /compile?backend=
200

没有找到手机端对应的 /compile/fmt

这意味着至少这一轮失败可能发生在:

Plaintext
手机
→ 建立到 play.go-dev.shuijingwanwq.com:8443 的连接
→ Nginx

真正进入 Nginx 之前。

但事情还没有这么简单。

另一次前端失败附近,Nginx 却记录了 200

在大约 14:26 的另一轮测试中,百度 APP 页面显示过:

Plaintext
Error communicating with remote server.

而服务器日志中同一时间附近却存在:

Plaintext
POST /compile?backend=
HTTP/2.0
200

并且 User-Agent 明确来自百度 APP。

所以目前至少存在两种不同的现象:

Plaintext
第一种:
手机端显示失败
→ Nginx 没有对应请求

以及:

Plaintext
第二种:
手机端显示失败
→ Nginx 附近却存在 HTTP 200 请求

不过因为当前 access log 没有请求 ID,也没有更详细的上游耗时信息,所以还不能百分之百确认第二条 200 就一定对应页面上那一次失败操作。

这也是继续增强日志的原因。

不急着改代理架构,先提高可观测性

排查到这里,我没有马上:

  • 把 8443 改成 443;
  • 修改 CORS;
  • 修改代理目标;
  • 调整超时时间;
  • 修改前端请求代码。

因为现在并没有证据证明这些配置本身存在稳定错误。

如果为了“解决问题”而直接修改生产架构,很可能只是把一个偶发问题变成另外一个问题。

当前真正欠缺的是:

下一次故障出现时,能够获得足够完整的证据。

原来的 Nginx combined 日志只能看到:

Plaintext
请求
状态码
响应大小
User-Agent

但看不到一些非常关键的信息,例如:

Plaintext
$request_time
$upstream_response_time
$upstream_status
$upstream_addr

幸运的是,这台 OneinStack 服务器的 nginx.conf 中本来就已经定义了 JSON 日志格式,其中已经包括:

Plaintext
request_time
upstream_time
upstream_host
upstream_status

所以并不需要重新修改全局日志格式。

只需要给 Playground 的 8443 虚拟主机增加一份独立的详细日志。

为 Playground 增加独立调试日志

在不改变原有代理逻辑的情况下,增加:

Plaintext
/data/wwwlogs/play.go-dev.shuijingwanwq.com_debug.log

同时增加 Playground 专用错误日志:

Plaintext
/data/wwwlogs/play.go-dev.shuijingwanwq.com_debug_error.log

原来的:

Plaintext
/data/wwwlogs/play.go-dev.shuijingwanwq.com_nginx.log

继续保留。

也就是说,这次调整只是增加观测能力,没有修改:

Plaintext
8443

没有修改:

Plaintext
proxy_pass

没有修改 CORS,也没有影响服务器上的其他网络服务。

修改完成后先执行 Nginx 配置检查:

Plaintext
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

随后重新加载 Nginx。

验证新的详细日志

最后主动发送一条测试请求:

Go
package main

func main() {
    println("DEBUG-OK")
}

返回结果正常:

JSON
{"Errors":"","Events":[{"Message":"DEBUG-OK\n","Kind":"stderr","Delay":0}],"Status":0,"IsTest":false,"TestsFailed":0,"VetOK":true}

新的详细日志同时记录:

Plaintext
"request_time":0.652
"status":"200"
"upstream_time":"0.652"
"upstream_host":"142.251.45.17:443"
"upstream_status":"200"
【图 5:Playground 调试日志首次验证,记录请求耗时和上游状态】
【图 5:Playground 调试日志首次验证,记录请求耗时和上游状态】

这意味着现在已经可以看到完整的代理执行结果。

这次测试中:

Plaintext
客户端
→ ZgoCloud Nginx
→ play.golang.org

整个请求耗时:

Plaintext
0.652 秒

上游响应耗时同样是:

Plaintext
0.652 秒

官方 Playground 返回:

Plaintext
200

ZgoCloud 最终返回客户端的状态同样是:

Plaintext
200

专用错误日志没有产生异常记录。

这一次没有强行宣布“问题已修复”

这次排查最终没有得到一个能够稳定复现的根因。

如果只为了文章结尾好看,也完全可以挑一个最像的问题修改掉,然后写成“故障已经修复”。

但对于这种低频、间歇性的生产问题,我更愿意保留当前结论:

问题目前已经恢复正常,但还不能证明根因已经找到。

目前已经确认的事实包括:

  • 手机端确实出现过“运行”失败;
  • 手机端也出现过“格式化”失败;
  • 微信、自带浏览器、QQ 浏览器和百度 APP 后续都能够正常运行;
  • Wi-Fi 与 5G 都能够成功;
  • 电脑无论是否使用原来的网络服务都可以正常运行;
  • 当天进入 Nginx 的正常 /compile/fmt 请求全部返回成功;
  • 一次早期手机失败没有对应的 Nginx access log;
  • 另一轮失败附近存在手机请求返回 200 的记录;
  • 当前还没有足够证据把问题归因于浏览器、8443、CORS、ZgoCloud 或官方 Playground 中的任何一层。

因此这次最重要的成果不是“修改了多少代码”,而是把生产环境从:

Plaintext
出了问题以后只能猜

推进到了:

Plaintext
下一次问题出现时,可以直接看到请求和上游链路的详细证据

后续

接下来不再继续高频人工点击测试。

让真实用户访问和日常使用自然产生请求即可。

如果以后再次出现“运行”或者“格式化”失败,只需要记录故障发生时间,再检查新的 Playground 专用日志,就可以同时确认:

Plaintext
请求是否进入 Nginx
Plaintext
最终 HTTP 状态
Plaintext
上游实际状态
Plaintext
完整请求耗时
Plaintext
上游响应耗时
Plaintext
实际连接到哪个上游地址

如果下一次能够捕获到真正的异常请求,才有足够依据决定是否需要进一步调整:

Plaintext
端口
代理
TLS
CORS
超时
前端请求逻辑

对于这种偶发故障,与其凭一次截图快速修改生产环境,不如先让系统具备回答问题的能力。

这一次先把“为什么失败”变成下一次可以通过日志直接验证的问题,再等待真正的异常样本出现。

A Tour of Go 多语言翻译项目:将生产 Run / Format 切换到 ZgoCloud 执行节点

A Tour of Go 多语言翻译项目

本系列完整记录 A Tour of Go 多语言翻译项目从架构设计、整页翻译、结构保护、自动校验,到生产发布与后续维护的实际开发过程。

项目入口:
✅ 在线学习:A Tour of Go 简体中文版
✅ 项目源码:GitHub:shuijingwan/go-tour-i18n

当前第一阶段已完成简体中文版本。项目属于非官方社区多语言翻译项目,与 Go 官方无隶属或授权关系。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理