性能优化 #
一、性能优化概述 #
1.1 优化方向 #
| 方向 | 说明 |
|---|---|
| 连接池 | 数据库、Redis连接池 |
| 中间件 | 减少不必要的中间件 |
| 内存 | 减少内存分配 |
| 并发 | 合理使用goroutine |
1.2 性能指标 #
| 指标 | 说明 |
|---|---|
| QPS | 每秒请求数 |
| 延迟 | 请求响应时间 |
| 内存 | 内存使用量 |
| CPU | CPU使用率 |
二、连接池优化 #
2.1 数据库连接池 #
go
func initDB() *gorm.DB {
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
// 连接池配置
sqlDB.SetMaxIdleConns(10) // 最大空闲连接数
sqlDB.SetMaxOpenConns(100) // 最大打开连接数
sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活时间
sqlDB.SetConnMaxIdleTime(10 * time.Minute) // 连接最大空闲时间
return db
}
2.2 Redis连接池 #
go
func initRedis() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
PoolSize: 100, // 连接池大小
MinIdleConns: 10, // 最小空闲连接数
MaxRetries: 3, // 最大重试次数
DialTimeout: 5 * time.Second, // 连接超时
ReadTimeout: 3 * time.Second, // 读取超时
WriteTimeout: 3 * time.Second, // 写入超时
PoolTimeout: 4 * time.Second, // 连接池超时
})
}
2.3 HTTP客户端连接池 #
go
var httpClient *http.Client
func initHTTPClient() {
httpClient = &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 10 * time.Second,
}
}
三、中间件优化 #
3.1 减少中间件 #
go
func main() {
r := gin.New()
// 只使用必要的中间件
r.Use(gin.Recovery())
// 日志中间件只在需要时使用
if gin.Mode() != gin.ReleaseMode {
r.Use(gin.Logger())
}
r.Run()
}
3.2 条件中间件 #
go
func main() {
r := gin.New()
// 只对API路由使用中间件
api := r.Group("/api")
api.Use(AuthMiddleware(), RateLimitMiddleware())
{
api.GET("/users", getUsers)
}
// 健康检查不需要中间件
r.GET("/health", healthCheck)
r.Run()
}
3.3 中间件性能 #
go
// 避免在中间件中做耗时操作
func BadMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 不要这样做:每次请求都查询数据库
var user User
DB.First(&user, 1)
c.Next()
}
}
// 使用缓存
func GoodMiddleware() gin.HandlerFunc {
cache := make(map[string]User)
var mu sync.RWMutex
return func(c *gin.Context) {
mu.RLock()
user, exists := cache["user_1"]
mu.RUnlock()
if !exists {
// 缓存不存在时查询
DB.First(&user, 1)
mu.Lock()
cache["user_1"] = user
mu.Unlock()
}
c.Next()
}
}
四、内存优化 #
4.1 对象复用 #
go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func handler(c *gin.Context) {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.WriteString("Hello")
c.String(200, buf.String())
}
4.2 预分配切片 #
go
// 不好的做法
func bad() []int {
var result []int
for i := 0; i < 1000; i++ {
result = append(result, i)
}
return result
}
// 好的做法
func good() []int {
result := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
result = append(result, i)
}
return result
}
4.3 字符串拼接 #
go
// 不好的做法
func badConcat(parts []string) string {
var result string
for _, part := range parts {
result += part
}
return result
}
// 好的做法
func goodConcat(parts []string) string {
var builder strings.Builder
for _, part := range parts {
builder.WriteString(part)
}
return builder.String()
}
五、并发优化 #
5.1 并发处理 #
go
func batchHandler(c *gin.Context) {
ids := []int{1, 2, 3, 4, 5}
var wg sync.WaitGroup
results := make([]User, len(ids))
for i, id := range ids {
wg.Add(1)
go func(index, id int) {
defer wg.Done()
results[index] = getUser(id)
}(i, id)
}
wg.Wait()
c.JSON(200, results)
}
5.2 使用channel #
go
func processConcurrently(items []Item) []Result {
ch := make(chan Result, len(items))
for _, item := range items {
go func(item Item) {
ch <- processItem(item)
}(item)
}
var results []Result
for i := 0; i < len(items); i++ {
results = append(results, <-ch)
}
return results
}
5.3 限制并发数 #
go
func processWithLimit(items []Item, limit int) []Result {
sem := make(chan struct{}, limit)
var wg sync.WaitGroup
results := make([]Result, len(items))
for i, item := range items {
wg.Add(1)
go func(index int, item Item) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
results[index] = processItem(item)
}(i, item)
}
wg.Wait()
return results
}
六、缓存优化 #
6.1 内存缓存 #
go
var cache = make(map[string]interface{})
var cacheMu sync.RWMutex
func getWithCache(key string, fn func() (interface{}, error)) (interface{}, error) {
cacheMu.RLock()
if val, exists := cache[key]; exists {
cacheMu.RUnlock()
return val, nil
}
cacheMu.RUnlock()
val, err := fn()
if err != nil {
return nil, err
}
cacheMu.Lock()
cache[key] = val
cacheMu.Unlock()
return val, nil
}
6.2 Redis缓存 #
go
func getWithRedisCache(key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) {
// 尝试从Redis获取
val, err := redis.Get(key).Result()
if err == nil {
return val, nil
}
// 缓存不存在,执行函数
result, err := fn()
if err != nil {
return nil, err
}
// 存入Redis
redis.Set(key, result, ttl)
return result, nil
}
七、性能监控 #
7.1 pprof #
go
import _ "net/http/pprof"
func main() {
r := gin.Default()
// 注册pprof路由
r.GET("/debug/pprof/*action", func(c *gin.Context) {
c.Param("action", c.Param("action")[1:])
http.HandlerFunc(pprof.Index).ServeHTTP(c.Writer, c.Request)
})
r.Run()
}
7.2 Prometheus #
bash
go get github.com/gin-contrib/prometheus
go
import "github.com/gin-contrib/prometheus"
func main() {
r := gin.Default()
p := prometheus.NewPrometheus("gin")
p.Use(r)
r.Run()
}
7.3 自定义指标 #
go
var (
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{.001, .005, .01, .05, .1, .5, 1, 5},
},
[]string{"method", "path", "status"},
)
)
func init() {
prometheus.MustRegister(requestDuration)
}
func MetricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start).Seconds()
requestDuration.WithLabelValues(
c.Request.Method,
c.Request.URL.Path,
strconv.Itoa(c.Writer.Status()),
).Observe(duration)
}
}
八、性能测试 #
8.1 基准测试 #
go
func BenchmarkHandler(b *testing.B) {
r := gin.New()
r.GET("/test", func(c *gin.Context) {
c.String(200, "ok")
})
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/test", nil)
r.ServeHTTP(w, req)
}
}
8.2 压力测试 #
bash
# 使用wrk
wrk -t12 -c400 -d30s http://localhost:8080/
# 使用ab
ab -n 10000 -c 100 http://localhost:8080/
九、总结 #
9.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 连接池 | 合理配置连接池参数 |
| 中间件 | 减少不必要的中间件 |
| 内存 | 复用对象,预分配 |
| 并发 | 合理使用goroutine |
9.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 监控 | 使用Prometheus监控 |
| 测试 | 进行压力测试 |
| 优化 | 根据瓶颈优化 |
| 验证 | 验证优化效果 |
9.3 下一步 #
现在你已经掌握了性能优化,接下来让我们学习 单元测试,了解Gin的测试方法!
最后更新:2026-03-28