1. 背景
Go初学者学习Go时,在编写了经典的“hello, world”程序之后,可能会迫不及待的体验一下Go强大的标准库,比如:用几行代码写一个像下面示例这样拥有完整功能的web server:
// 来自https://tip.golang.org/pkg/net/http/#example_ListenAndServe
package main
import (
"io"
"log"
"net/http"
)
func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
go net/http包是一个比较均衡的通用实现,能满足大多数gopher 90%以上场景的需要,并且具有如下优点:
- 标准库包,无需引入任何第三方依赖;
- 对http规范的满足度较好;
- 无需做任何优化,即可获得相对较高的性能;
- 支持HTTP代理;
- 支持HTTPS;
- 无缝支持HTTP/2。
不过也正是因为http包的“均衡”通用实现,在一些对性能要求严格的领域,net/http的性能可能无法胜任,也没有太多的调优空间。这时我们会将眼光转移到其他第三方的http服务端框架实现上。
而在第三方http服务端框架中,一个“行如其名”的框架fasthttp被提及和采纳的较多,fasthttp官网宣称其性能是net/http的十倍(基于go test benchmark的测试结果)。
fasthttp采用了许多性能优化上的最佳实践,尤其是在内存对象的重用上,大量使用sync.Pool以降低对Go GC的压力。
那么在真实环境中,到底fasthttp能比net/http快多少呢?恰好手里有两台性能还不错的服务器可用,在本文中我们就在这个真实环境下看看他们的实际性能。
2. 性能测试
我们分别用net/http和fasthttp实现两个几乎“零业务”的被测程序:
- nethttp:
// github.com/bigwhite/experiments/blob/master/http-benchmark/nethttp/main.go
package main
import (
_ "expvar"
"log"
"net/http"
_ "net/http/pprof"
"runtime"
"time"
)
func main() {
go func() {
for {
log.Println("当前routine数量:", runtime.NumGoroutine())
time.Sleep(time.Second)
}
}()
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go!"))
}))
log.Fatal(http.ListenAndServe(":8080", nil))
}
- fasthttp:
// github.com/bigwhite/experiments/blob/master/http-benchmark/fasthttp/main.go
package main
import (
"fmt"
"log"
"net/http"
"runtime"
"time"
_ "expvar"
_ "net/http/pprof"
"github.com/valyala/fasthttp"
)
type HelloGoHandler struct {
}
func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintln(ctx, "Hello, Go!")
}
func main() {
go func() {
http.ListenAndServe(":6060",