Go语言:构建高性能HTTP服务器

Go的net/http标准库可能是所有语言中最好用的HTTP库。不需要任何第三方框架,几行代码就能写出一个生产级别的HTTP服务器。

最简HTTP服务器

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
    })
    http.ListenAndServe(":8080", nil)
}

这8行代码启动的服务器自带:连接复用(HTTP/1.1 Keep-Alive)、并发处理(每个请求一个goroutine)、优雅的超时处理。放在Java里,光Tomcat配置就不止8行。

Handler接口

Go HTTP服务器的核心是Handler接口:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

只有一个方法。任何实现了ServeHTTP的类型都可以处理HTTP请求。这个设计极度简洁,也是Go中间件模式的基础。

// 用struct实现Handler
type apiHandler struct {
    db *sql.DB
}

func (h *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 可以访问h.db
    users, _ := h.db.Query("SELECT name FROM users")
    // ...
}

func main() {
    db, _ := sql.Open("mysql", "user:pass@/dbname")
    http.Handle("/api/", &apiHandler{db: db})
    http.ListenAndServe(":8080", nil)
}

ServeMux路由

默认的http.DefaultServeMux是一个简单的路由器,支持精确匹配和前缀匹配:

mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)           // 兜底路由
mux.HandleFunc("/api/users", usersHandler) // 精确匹配
mux.HandleFunc("/api/users/", userHandler) // 前缀匹配(注意末尾的/)

DefaultServeMux的路由能力有限——不支持路径参数(如/users/:id)、不支持按HTTP方法区分。这是很多人转向第三方路由库(chi、gorilla/mux)的原因。但对于简单的API服务,DefaultServeMux完全够用。

中间件模式

Go的中间件就是一个接收Handler返回Handler的函数:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if !validateToken(token) {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// 组合中间件
func chain(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/data", dataHandler)

    server := chain(mux, loggingMiddleware, authMiddleware)
    http.ListenAndServe(":8080", server)
}

这个模式没有任何魔法,就是函数组合。跟Java的Filter链或Node.js的Express中间件本质相同,但Go的实现更直白。

生产级配置

裸写http.ListenAndServe不适合生产环境,需要配置超时和连接池:

server := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  5 * time.Second,   // 读取请求的超时
    WriteTimeout: 10 * time.Second,  // 写入响应的超时
    IdleTimeout:  120 * time.Second, // Keep-Alive连接的空闲超时
    MaxHeaderBytes: 1 << 20,         // 请求头最大1MB
}

// 优雅关闭
go func() {
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    <-sigCh

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    server.Shutdown(ctx)
}()

log.Fatal(server.ListenAndServe())

几个关键超时的含义:

  • ReadTimeout:从accept连接到读完请求体的总时间。防止慢速客户端占用连接。
  • WriteTimeout:从读完请求头到写完响应的总时间。防止慢速客户端导致goroutine堆积。
  • IdleTimeout:Keep-Alive连接的空闲时间。超时后关闭连接释放资源。

不设置这些超时,你的服务器可能因为慢客户端或恶意连接耗尽资源。

性能分析:pprof

Go内置了性能分析工具,启用只需要一行import:

import _ "net/http/pprof"

// 如果用了自定义的ServeMux,需要手动注册
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)

然后用go tool pprof分析:

# CPU分析(采集30秒)
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30

# 内存分析
go tool pprof http://localhost:8080/debug/pprof/heap

# goroutine分析(排查泄漏)
go tool pprof http://localhost:8080/debug/pprof/goroutine

在pprof交互界面中,top显示热点函数,web生成调用图,list funcName显示逐行耗时。

性能优化要点

  1. 连接复用:确保客户端读完了response body(io.Copy(io.Discard, resp.Body)),否则连接不会被放回连接池
  2. JSON序列化:标准库encoding/json性能一般,高频场景考虑json-iteratorsonic
  3. sync.Pool复用对象:减少GC压力,特别是频繁创建的临时buffer
  4. 避免goroutine泄漏:给所有网络操作设置context超时,定期用pprof检查goroutine数量

Go的HTTP服务器开箱即用的性能已经很好了。在大多数业务场景下,瓶颈不在HTTP层而在数据库和业务逻辑。先用pprof找到真正的热点,再有针对性地优化。