加入收藏 | 设为首页 | 会员中心 | 我要投稿 站长网 (https://www.0313zz.cn/)- AI硬件、数据采集、AI开发硬件、建站、智能营销!
当前位置: 首页 > 服务器 > 系统 > 正文

Go服务器性能调优:6年实战QPS翻倍之路

发布时间:2026-10-21 14:20:16 所属栏目:系统 来源:DaWei
导读:  去年7月份,服务器突然开始吐痰似的掉QPS——不是报警,是那种凌晨三点你盯着grafana看,发现请求延迟曲线像被狗啃过的锯齿,而监控没响一声的掉。我原以为又是某次k8s节点漂移惹的祸,结果查完pod日志、查etcd租约、查ipt

  去年7月份,服务器突然开始吐痰似的掉QPS——不是报警,是那种凌晨三点你盯着grafana看,发现请求延迟曲线像被狗啃过的锯齿,而监控没响一声的掉。我原以为又是某次k8s节点漂移惹的祸,结果查完pod日志、查etcd租约、查iptables conntrack表,最后蹲在nginx access log里翻到第十七页,才看见一串重复的User-Agent里混着个“Baiduspider/2.0”,但后面跟着个奇怪的query:?utm_source=seo_warmup_2023。这玩意儿连着刷了四小时,没触发任何限流规则,却把后端goroutine池塞得比春运火车站还满。


  群里阿哲发了个龇牙笑表情包,配字:“收录掉了三百七十二个词,快照停在六月十九。”我回他“等会儿”,手还在tail -f /var/log/goapp/error.log。烟灰缸里躺着半截没掐灭的中南海,滤嘴那头焦黑卷曲,跟上周三爬梯子修机房空调时蹭到的锈迹一个色儿。阳台晾衣绳上挂的两件衬衫还没收,风一吹就贴在防盗网铁丝上,啪嗒啪嗒,像在敲倒计时。


  隔壁装修的电钻声从下午两点准时开嗓,钻头啃混凝土的声音闷得人太阳穴跳。我一边调pprof火焰图一边听客户老陈电话里喊:“李工!百度搜我们公司名,前五页全是你家域名,第三页那个‘联系我们’页面点进去是404!”我没说话,只把电话搁在桌上,听筒里传来他咳嗽两声、撕开一包糖的窸窣响。我原以为是Nginx配置漏了location匹配,结果发现Go HTTP server用的http.Server.ReadTimeout设成了30秒——可那段时间所有超时请求都卡在conn.Read()里,压根没进handler。这个我敢打包票:ReadTimeout不等于timeout,它只是read系统调用的deadline,而TCP握手后的SYN-ACK重传窗口里,客户端早睡着了,服务器还在举着socket等。我们六年踩过的坑里,八成是把timeout当麻醉剂用。


  那天改了read timeout到5秒,重启服务,QPS升了两成,但第二天中午,索引量又掉了一半。不是波动,是直线坠落,像抽掉地板下所有垫片。SEO服务商小张发来截图,说“抓取频次已提至每小时一次”,我点开他们给的“诊断报告”,PDF第11页写着“建议升级HTTPS协议版本至TLS1.3”。我直接关了PDF,打开curl -v https://xxx.com 一看——果然,server hello返回的是TLS1.2,但go net/http默认开启HTTP/2,而某家CDN厂商的HTTP/2支持层在handshake时会偷偷缓存ALPN协商结果,只要第一次协商失败,后续半年都拒绝升级。到底是哪次更新的事我说不准,但去年六月那次CDN控制台灰度开关,绝对动过alpn-list参数。


  说到CDN……去年做站群那会儿,我用Go写了个自动换UA的爬虫调度器,叫“麻雀”。白天喂给百度蜘蛛真流量,夜里往自家几百个二级域里灌内容,每个页面都塞三个nofollow外链指向友站。后来其中两个站被牵连,不是因为权重传递——是因为所有页面底部加了同一段JS,用来加载一个叫“seo-monitor”的字体图标CSS。结果百度蜘蛛在解析时卡死在那个font-face的src里,返回HTTP 503错误,整个子域名被判为不可访问。我们删掉那段JS,三天后恢复;但有个兄弟坚持说“字体不影响收录”,硬扛着不删,现在他那批站,移动端首页还挂着三个月前的快照。这个教训太实:算法不认道理,只认你吐出来的东西有没有断气。


  我翻出六年前的第一版go server代码,main.go里还写着runtime.GOMAXPROCS(4)。现在跑着的版本连GOMAXPROCS都不设了——Linux内核自己扒拉cgroup里CPU quota,Go runtime比你还懂怎么分配P。但有个事儿很反常识:我们把GC停顿从25ms压到3ms之后,QPS反而跌了。为啥?因为新版本runtime把gc trigger threshold从内存占用60%调到了85%,看起来更“省”了,可一旦突发流量涌进来,瞬间heap暴涨,runtime狂扫对象图,goroutine排队等scan lock,这时候哪怕P再多也没用。不是并发不够,是锁争抢太狠。我昨天拆开gctrace看到一堆"mark assist"日志,就知道——又有人把吞吐当延时治了。


  老王上个月问我:“你们QPS翻倍是不是换了新框架?”我叼着烟没吭声。其实我们连http.ServeMux都没换,就是把所有handler函数里用了defer的panic recovery全干掉了——不是去掉错误处理,是改成先check err再return,把defer那句扔进专门的recover middleware里统一管。为啥?因为Go 1.19以后,defer在栈上预分配帧开销变大了,而我们每天三百万请求里,八成是正常响应,压根不触发recover。这点小账,攒起来就是十万次/秒的额外指令周期。但没人测,谁测啊?你说测,运维小刘在工位上抬头看了我一眼:“测了能多发奖金?还是能少加班?”说完转身去处理另一个报警——客户投诉友链页面打开慢,查出来是对方站点DNS TTL设成86400,自己主站IP换了一周,他们那边CNAME还指着三年前的老IP。


文章配图,仅供参考

  我原以为优化是叠buff,结果发现更像是拔刺:拔掉一个旧习惯,才能腾出一个goroutine给真正需要的地方。所谓优胜劣汰,不是服务器淘汰低效代码,是活人淘汰自己十年前写的那一套逻辑。现在我看到any类型参数就反胃,看到json.RawMessage就手抖,看到ctx.Background()出现在handler开头就想去砸键盘。


  明天先把手上那批页面删一半试试——不是删内容,是删页面末尾那个统计pv的异步JS请求。它已经埋了四年,没几个人点,但它每次加载都强制建立一条新的HTTP/2 stream,占着连接不放。就删它。别的,等电钻声停了再说。

(编辑:站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!