一、背景:一个“看起来没问题”的 Go 服务
这是一个非常常见的业务场景:
-
Gin + MySQL
-
提供一个查询接口
-
QPS 上不去,但 CPU、内存看着也不算满
初始架构
Client
↓
Gin HTTP Server
↓
Service Layer
↓
MySQL
初始代码(问题版本)
func GetUser(c *gin.Context) {
id := c.Query("id")
user, err := queryUserFromDB(id)
if err != nil {
c.JSON(500, gin.H{"err": err.Error()})
return
}
c.JSON(200, user)
}
压测结果(wrk)
#wrk -t线程数 -c连接数 -d压测时间 URL
wrk -t8 -c200 -d30s http://localhost:8080/user?id=1
wrk
是一款 高性能 HTTP 压测工具,常用于压测 Web / API 服务(Go、Nginx、Spring Boot、Gin 等),特点是 轻量、并发高、支持 Lua 脚本,非常适合做 Go 高并发服务 / 云原生 场景 👍
📉 结果
-
QPS:≈ 2000
-
P99 延迟:120ms
-
CPU:40% 左右
-
DB 连接数:经常打满
问题来了:
资源没跑满,QPS 却上不去。
二、第一步:pprof 定位性能瓶颈
1️⃣ 开启 pprof
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-contrib/pprof"
)
func main() {
r := gin.New()
// 业务路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "pong"})
})
// 注册 pprof(默认路径 /debug/pprof)
pprof.Register(r)
// 启动 Gin
r.Run(":8080")
}
2️⃣ CPU Profiling
go tool pprof http://localhost:8080/debug/pprof/profile
热点函数
queryUserFromDB
database/sql.(*DB).QueryContext
结论很清晰:
瓶颈在数据库 & 每次请求都直连 DB
三、第二步:数据库连接池参数错误(经典坑)
❌ 错误配置(默认)
sql.Open("mysql", dsn)
Go 的 database/sql默认连接池非常保守:
|
参数
|
默认值
|
| — | — |
|
MaxOpenConns
|
0(无限,但受 MySQL 限制)
|
|
MaxIdleConns
|
2
|
|
ConnMaxLifetime
|
无
|
✅ 正确配置
db.SetMaxOpenConns(200)
db.SetMaxIdleConns(100)
db.SetConnMaxLifetime(time.Minute * 5)
📈 优化后压测
-
QPS:≈ 4500
-
P99:80ms
👉 第一条经验:Go 性能问题,80% 从连接池开始
四、第三步:热点数据不该打 DB(引入缓存)
这是最关键的一步。
问题本质
-
大量请求访问 同一用户
-
每次都打 DB
-
DB QPS 成为硬瓶颈
优化策略
-
本地缓存(示例)
-
Redis 也可以
缓存版本代码
var userCache sync.Map
func GetUser(c *gin.Context) {
id := c.Query("id")
if v, ok := userCache.Load(id); ok {
c.JSON(200, v)
return
}
user, err := queryUserFromDB(id)
if err != nil {
c.JSON(500, gin.H{"err": err.Error()})
return
}
userCache.Store(id, user)
c.JSON(200, user)
}
📈 压测结果
-
QPS:≈ 12000
-
P99:25ms
-
DB QPS:骤降
五、第四步:JSON 编码成为新瓶颈
pprof 再看一次 👇
encoding/json.(*encodeState).marshal
问题原因
-
Gin 默认使用
encoding/json -
高 QPS 下 CPU 消耗巨大
优化方案
-
使用
jsoniter -
或者
sonic(字节系常用)
示例(jsoniter)
var json = jsoniter.ConfigFastest
c.Data(200, "application/json", json.MarshalToString(user))
📈 效果
-
QPS:≈ 17000
-
CPU 使用率明显下降
六、第五步:减少系统调用 & 上下文切换
1️⃣ Gin 默认 Logger 很重
r := gin.New()
r.Use(gin.Recovery())
// 不使用 gin.Logger()
2️⃣ HTTP Server 参数优化
server := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
MaxHeaderBytes: 1 << 20,
}
📈 最终压测
-
QPS:≈ 21000
-
P99:18ms
-
CPU:≈ 75%
-
服务稳定运行
七、最终对比总结
|
阶段
|
QPS
|
| — | — |
|
初始版本
|
2,000
|
|
连接池优化
|
4,500
|
|
加缓存
|
12,000
|
|
JSON 优化
|
17,000
|
|
Server & Gin 优化
| 21,000+ |
八、Go 性能优化的「黄金法则」
✅ 永远先 pprof,再优化✅ 连接池 > 缓存 > 编码 > 框架细节✅ 没有 DB 扛得住无脑高并发✅ QPS 上不去,多半不是 CPU 的问题
加班费计算器:(vx小程序 “加班计”)
*源码地址*
1、vx“Codee君”回复“源码”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!

666

被折叠的 条评论
为什么被折叠?



