Go服务器性能调优:避坑选型,QPS翻倍实战
|
去年寒假那会儿,监控突然炸锅——QPS从两万八直接掉到八千,告警灯闪得跟迪厅似的。我原以为又是哪个依赖服务挂了,结果登录跳板机一看,好家伙,Go服务进程还在,CPU占用率却卡在15%纹丝不动。屏上并排开着三个终端,一个盯top,一个查日志,还有一个在试curl——你猜怎么着?所有请求都卡在`ReadFrom`那行代码,像被施了定身咒。 老张叼着泡面凑过来:“不会是GC又抽风了吧?”我盯着火焰图直摇头:“GC频率才0.3%,这货在等什么?”正说着,隔壁运维小李突然喊:“磁盘inode满了!”我抄起鼠标点开`df -i`,果然,`/var/lib/docker`下的inode用得干干净净——敢情是日志轮转没配置好,小文件把inode啃光了。可这能解释QPS暴跌?我原以为只是写入阻塞,结果重启服务后,QPS勉强爬回一万二,离翻倍差得远。 椅子轮子卡着一根网线,我蹲下去拔的时候突然想起:上周为了省磁盘,把日志级别从`debug`调成了`info`。可`info`日志量也不该把inode吃光啊?小李在旁边翻工单:“你们这服务没开异步日志吧?”一语点醒梦中人——默认的`log.Output`是同步写,每个请求都得等日志落盘才返回。去年刚接手时吃过这亏,后来明明改成了异步,怎么又回去了? 翻代码历史才发现,三个月前为了“优化性能”,有人把`log.New`的`Output`参数从`bufio.NewWriter`换成了`os.File`——说是减少一层缓冲能更快。我盯着那行注释直乐:“减少一层缓冲,增加一百倍延迟,这买卖划算。”老张在旁边补刀:“上次他优化数据库连接池,把最大连接数从200改成20,结果DB告警响了三天。” 改回异步日志后,QPS蹭蹭涨到一万八,可离翻倍还差四千。这时候监控显示,`net.http.server.Requests`的计数器在涨,但`latency`曲线却像被拦腰砍了一刀——200ms以上的请求突然多了30%。我抓了个慢请求的trace,发现卡在`database/sql`的`QueryContext`上。可数据库监控显示,QPS才八千,负载才15%,这不对啊? 小李突然说:“你们是不是用了连接池?”我点头:“当然,`maxOpenConns`设了50。”他翻出数据库的`SHOW PROCESSLIST`:“看,全是`Sleep`状态的连接。”我盯着屏幕愣了两秒,突然想起:Go的`database/sql`连接池有个坑——如果连接数超过`maxOpenConns`,新请求会排队;但如果连接数没超过,可所有连接都在`Sleep`(比如因为设置了`SetConnMaxLifetime`),新请求还是会等连接“醒来”。 我们设的`SetConnMaxLifetime`是5分钟,而`maxIdleConns`是10——这意味着,当负载低时,连接会慢慢积压到10个;负载高时,新请求得等这些连接“超时”或被复用。我把`maxIdleConns`改成和`maxOpenConns`一样(50),再重启服务,QPS直接冲到两万五。老张在旁边拍桌子:“这不就优胜劣汰吗?把错的配置淘汰掉,对的自然就冒头了。”
文章配图,仅供参考 可这还没完。第二天早上,老板冲进值班室:“怎么QPS到两万五就不涨了?不是说能翻倍吗?”我翻出监控:“CPU才60%,内存才40%,网络带宽才30%,按理说还能扛。”老张突然说:“会不会是Go的调度器卡脖子了?”他抓过键盘敲了行命令:`GODEBUG=schedtrace=1000 ./server`。我们盯着输出,发现`P`的数量是8(默认等于CPU核心数),可`M`的数量(实际工作线程)却经常卡在4-5个。我原以为多核机器能自动满载,结果Go的调度器在低负载时会把`M`“偷懒”收回去——可我们的服务明明是高并发场景啊!翻文档才发现,可以通过`runtime.GOMAXPROCS`强制设`P`的数量,或者用`GODEBUG=scheddetail=1`看更细的调度日志。最后我们选了更粗暴的办法:启动时加`-cpu 16`(服务器有16核),强制让Go用满所有核心。这一改,QPS终于突破三万,稳稳停在三万二。 后来复盘时,小李说:“你们这调优跟打地鼠似的,一个问题刚解决,另一个又冒出来。”我摸着键盘苦笑:“可不就是优胜劣汰吗?每个坑都是错的配置在‘淘汰’正确配置,我们得比它更快找到对的。”老板在旁边插话:“下次别等QPS掉了才调,提前压测不行吗?”我低头看屏幕上的火焰图——这会儿它终于不闪了,安静得像台刚做完手术的病人。 现在回头看,那两周最崩溃的不是改代码,而是每次以为找到根因时,监控又蹦出新的异常。比如有次把日志改异步后,QPS涨了但错误率也涨了——原来是异步写日志时,服务重启导致部分日志丢失,客户端重试把错误率顶上去了。最后我们加了`sync.Once`保证日志文件关闭,才彻底解决。 至于“优胜劣汰”到底站不站得住?我觉得得看怎么定义“劣”——是配置错了,还是对业务场景理解错了?就像那次数据库连接池的坑,表面看是配置问题,深层次是没搞清`Sleep`连接和活跃连接的区别。所以调优这事儿,没有一劳永逸的“最优解”,只有不断被业务“淘汰”的“次优解”。下次再遇到QPS上不去,我打算先查调度器日志——毕竟,连CPU核心都用不满,还调什么优? (编辑:草根网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |






浙公网安备 33038102330469号