Go服务器性能调优:避坑选型,QPS翻倍实战
|
会议室空调在滴水,哒、哒、哒,像在给我说话打节拍。我迟到八分钟——地铁扶梯卡了人,出站时瞥见前台在大声打电话:“…对,就那个‘生态协同体’,不是‘生态圈’,字儿不能错!”她手边一盆绿萝叶子卷边了,没人管。 对面坐仨:CTO老周,穿灰卫衣,袖口起球;后端组长小陈,把我的简历在桌上转了半圈,停在“QPS翻倍”那行;还一个戴耳钉的架构师,一直转笔,笔帽掉过一次,他弯腰捡,没捡到,也没再找。 老周开口就问:“你说‘避坑选型’,避的是哪个坑?K8s?gRPC?还是自己写的中间件?” 我没接。先喝了口凉透的咖啡,纸杯上有个咖啡渍,拖得像条虫。我原以为他们想听etcd对比Consul或者Gin换Echo的压测数据——结果小陈掏出一台iPad,调出一张表格,第三列标着“人才画像匹配度”,第十二行写着“抓手是否清晰”。我当场没忍住笑出声,他抬眼看了我两秒,转笔回去,笔尖戳破了屏幕保护膜。 去年10月份,我们上线一个实时账单服务。Go写,用的默认http.Server,加了pprof,连了Prometheus。QPS峰值320,毛刺高得像心电图室里被电击的老鼠。运维说CPU跑不满,GC停顿才3ms,怎么就卡? 我们全盯着GC——改GOGC、调heap目标、上zap代替logrus。干了一个月,QPS涨到347。庆功宴上点了黄酒,我敬小陈,他回敬我一句:“兄弟,你真信GC是瓶颈?”我那时没答好。现在知道,他早把net/http.DefaultServeMux换成自定义Mux——因为有个同事写路由注册,用for循环拼字符串,然后strings.HasPrefix()扫路径,12层嵌套。那是真坑。不是技术选型坑,是人肉坑。 走廊有股消毒水味,混着隔壁茶水间蒸馏水机烧糊的塑料味。我忽然想起另一个候选人,面完出来蹲在消防通道啃冷包子,说是刚从杭州赶过来,高铁晚点两小时,简历被包浆了。他聊协程泄漏聊得贼细,但被问“你删过几版gomod vendor”直接卡住。我后来翻他GitHub,发现他半年前提过PR修了一个context.WithTimeout漏cancel的bug——修对了,但注释里写了句:“这里不会泄露,除非超时时间设为0。”其实会。我看到那行注释,后背一紧。 小陈推iPad过来,指表格最后一行:“闭环节奏”。我指着旁边一行空白处说:“你们这儿没填‘谁敢动default net/http’。”他手顿住,笔停在半空。老周忽然说:“上周,测试环境崩了两次,日志里全是‘http: server gave up accepting connections’。”我接口就回:“不是server给了,是listener.accept()被堵死了。” 没人说话。耳钉架构师把笔收进兜里,掏出手机刷了十秒,又放回去。我接着说:“Go 1.21以后ListenConfig.Control函数能干啥,你们查过没?不用改代码,只改启动参数——Listener套一层file-descriptor复用,QPS直接到680。不是翻倍,是2.1倍。但我们先干了三件事:换掉所有runtime.GC()显式调用、禁用HTTP/2的server push(压根没人用)、把healthz路径从/health挪到/.well-known/healthz——就因为istio sidecar在疯狂GET /health,触发了path cleanup里的string.ToLower。” 小陈低头在表格上划了一道线,划掉了“人才画像匹配度”。老周起身去关空调——没关成,遥控器电池漏液了,黑乎乎一片。他捏着遥控器站在那儿,没发火,也没笑。我补了一句:“优胜劣汰不是淘汰慢的,是淘汰还觉得‘差不多就行’的。” 他点头,转身开门,门撞上走廊通风口,哗啦一声响。我闻到那股消毒水味更重了。 我忘带U盘,临时用手机投屏,结果连错三次——第一次连到隔壁会议室PPT播放器,第二回连上行政部钉钉直播,第三回终于连上,但桌面壁纸露了半张:去年在乌镇拍的,天光蓝得假,下面一行字“Go is not about being fast, it’s about being unblocked.” 我没关,就让它挂着。 耳钉架构师忽然问:“你那个680 QPS,压测工具用的wrk还是hey?” “都不是,用的内部脚本,fork了137个goroutine,每个循环50次请求,间隔17ms——因为我们上游限流就是按这个粒度配的。” 他愣住。我补:“所以不是工具决定QPS,是你的限流策略决定了你敢不敢让服务器喘气。” 小陈突然抬头:“你们当时为什么没换fasthttp?” 我笑了:“我们试过。换了三天,QPS涨到401,然后发现所有trace span全丢了,OpenTelemetry SDK不认它。一个礼拜后回滚,顺手把otlp exporter从JSON换成了Protobuf。那会儿我才看明白:性能不是单点优化,是松动耦合。你敢换fasthttp,就得先有人敢把全链路trace切成离散消息队列,再串回来。没人干,就别碰。” 老周回来坐下,袖口沾了点黑灰。他问:“那翻倍,到底靠什么?” “三个动作:第一,把read/write timeout从30秒砍到3.8秒——我们99.9%请求1.2秒内完事,剩下那0.1%该死就死;第二,关掉所有gzip compress,客户端自己解;第三,把redis pipeline从每次10条改成每次37条——因为Redis TCP MSS是1448,加上协议头,37条刚好塞满一个包,不拆包。”
文章配图,仅供参考 我顿了顿,“没有新框架,没上eBPF,也没改调度器。就这三刀,下去,血没喷,肉却分开了。” 小陈把iPad推远一点,摸出支烟,发现会议室禁烟,又塞回去。他手指在桌沿敲了三下,停住。 我其实漏讲了一点:上线那天凌晨三点,我改完timeout,没跑测试就推了——太困,想着“反正3.8秒只是个数字”。结果早上七点报警,订单回调失败率跳到7%,一看日志,三方支付网关响应要4.1秒。我立刻改回4.2秒。他们不知道这事。我自己也不打算说第二次。 耳钉架构师忽然站起身,说了句:“下次聊Service Mesh的熔断降级吧,我带了个demo。”他朝门口走,路过我手机投屏,盯着那张乌镇壁纸看了两秒,没说话,出门时踢到了门槛。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


iOS转PHP:Xcode到Laravel的性能调优实战