上周三凌晨两点半,手机在床头嗡嗡嗡的震。我第一反应是诈骗电话,摸过来一看,是老客户王总。接起来那边声音很急:小蒋,我们会员群炸了,半夜两点全在收到提醒,说九点的课要开始了。
我脑子还没转过来,先说了句不好意思我马上看。挂了电话坐床边愣了十几秒才想起来是哪个项目——健身房那个预约小程序,上个月刚上线,我给他们加了个“课前两小时提醒”的功能。
说白了这功能本身不值钱,报价的时候是打包在维护费里的,一年两千四。结果它半夜给我上了一课。
打开电脑连上服务器,日志刷了几百页。我先怀疑是时区,因为小程序后台存的是 UTC,前台按北京时间渲染,这坑我踩过不止一次。翻了半天代码,把 new Date() 那几处全加了偏移量,重启服务,心想完事了。
结果三点十分又来一批。
这下醒了。我把发送提醒那段单独拎出来看,是个 for 循环,里面套一个 await 发短信,再套一个更新数据库状态。问题就在这——外层循环我没加 await,等于三百多个会员同时触发发送,短信接口直接甩回来一堆 429,然后重试机制又把失败的重新塞回队列,越滚越多。
说白了就是一行 async 写漏了。
改完四点多,重新部署,盯着日志看了二十分钟没再冒出来,才回去睡。第二天早上王总发微信说会员群安静了,末了问要不要给我发个红包。我说不用,心想你半夜两点打电话这个事咱回头再说。
这里面有个账得算:短信接口一条五分,那一晚上白烧出去两百多条,十块钱不到,钱不多,但是接口被限流之后账号还被降了级,第二天正常的验证码短信延迟十几秒,有个新会员注册卡了半天,王总又来找我。
其实我本来可以不接这个活的。维护费一年两千四,摊到每个月两百,说白了就是白送的价,正常这种半夜 oncall 的单子,报价里都得单列一条。当时想着老客户,加了就加了。现在想想,这账不难算——两百块一个月买我半夜爬起来两次,划不来。
不过话说回来,那行漏掉的 await 确实是我自己写的,怪不了别人。写那段的时候十一点多,赶着交付,测试环境里就三个测试账号,循环跑三下当然不出问题,上生产三百多个人同时点,立马原形毕露。
跑题了说回来。第二天我把提醒功能整个重写了,改成队列一个一个发,加了限速,顺便把重试次数从无限改成三次。改完在自己服务器上挂假数据跑了一晚上,才敢跟王总说没问题。
对了,最后那个红包他还是发了,两百。我收了。