上周三下午两点十七分,网站报错我盯着屏幕上那个白色底色的网站报错页面,正中间一行黑色小字:“500 Internal Server Error”。网站报错刷新了六次,网站报错每次都是网站报错同样的结果。隔壁工位的网站报错同事凑过来看了一眼,说:“哦,网站报错又崩了。网站报错”语气平淡得像在说楼下便利店关东煮卖完了。网站报错
这不是网站报错我第一次遇到网站报错。但那次印象特别深,网站报错因为当时正在给客户演示一个后台系统。网站报错鼠标点下去,网站报错页面没跳转,网站报错反而弹出一串红色警告。网站报错客户没说话,我假装镇定地按了F5,心里已经在盘算怎么解释。后来发现是数据库连接池满了——一个我三天前就该处理的问题。
大部分人看到报错,第一反应是“网站坏了”。但网站很少真的“坏掉”。它更像一个传话游戏,前端把请求递给后端,后端去问数据库,数据库再去找硬盘。中间任何一环卡住,最终呈现在你面前的都是一句含糊的“出错了”。
我见过最离谱的一次,是某电商网站的商品详情页全部显示“系统繁忙”。排查了两小时,发现是一个运维人员在凌晨改了一条Nginx配置,把某个参数从1024改成了102。就这么一个数字,导致所有带图片的请求被截断。用户看到的不是图片加载失败,而是一个通用的错误提示。没人会想到是运维的锅。
还有一次,朋友公司的官网首页突然变成一片空白。技术团队查了DNS、查了服务器负载、查了CDN,最后发现是设计师上传了一张2.3MB的PNG作为背景图,而他们的服务器带宽只有1Mbps。图片加载超时,整个页面渲染被阻塞。设计师觉得委屈:“我电脑上看没问题啊。”
我现在养成了一个习惯:看到报错,先看状态码。404是“找不到”,403是“没权限”,502是“网关挂了”,504是“后端超时”。这些数字比任何文字描述都诚实。但普通用户不需要知道这些,他们只需要一个能用的页面。
去年帮一个公益组织修网站,他们的捐赠页面每隔几天就报一次“连接超时”。我查了日志,发现每次报错都发生在上午十点左右。后来问了一圈,原来那个时间段保洁阿姨会推着吸尘器经过服务器所在的储物间,插头被震松了。服务器断电重启,数据库连接自然就断了。解决方案很简单:换了个带锁的插座。
这件事让我意识到,很多报错根本不是代码问题。可能是网线被老鼠咬了,可能是空调漏水滴到了交换机上,也可能是某个程序员在凌晨三点改了一行配置然后忘了自己改过什么。技术问题往往不技术。
用户看到的报错页面通常很简洁:“哎呀,出错了,请稍后重试。”工程师看到的是一长串堆栈跟踪,里面夹杂着NullPointerException和TimeoutException。这两种视角之间的鸿沟,就是为什么有些网站修得快,有些修得慢。
我认识一个独立开发者,他的个人博客每次报错都会显示一张手绘的猫,旁边写着“服务器正在挠墙”。用户觉得有趣,甚至有人截图发社交媒体。但他后台的监控系统会立刻给他发短信,附带完整的错误日志。幽默归幽默,该修的还是得修。
另一个极端是某银行APP。转账失败时只弹一个“操作异常,错误代码:9999”。打客服电话,客服也说不知道9999是什么意思。这种报错等于没报,用户除了骂两句然后卸载,什么也做不了。
我现在的原则是:报错信息里至少要让用户知道两件事——发生了什么,以及他能做什么。比如“登录失败,请检查密码是否包含特殊字符”,比“认证错误”有用得多。“图片上传失败,文件需小于5MB”,比“请求被拒绝”更直接。
当然,安全是个例外。登录页面不能提示“用户名不存在”,否则等于告诉攻击者哪些用户名是有效的。这时候含糊一点反而合理。但含糊不等于敷衍,你可以说“用户名或密码错误”,而不是“系统异常”。
前几天又遇到那个500错误。这次我没刷新,直接打开了开发者工具。Network面板里,那个API请求的响应头写着X-Debug-Error: Redis connection refused。你看,报错页面什么都没说,但网络层说了实话。我截图发给了运维,十分钟后网站恢复。隔壁同事又凑过来:“修好了?”我说:“嗯,Redis挂了。”他点点头,继续吃他的关东煮。