455 lines
12 KiB
Go
455 lines
12 KiB
Go
// Package http2 提供 HTTP/2 服务器基准测试。
|
||
//
|
||
// 该文件测试 HTTP/2 服务器的性能,包括:
|
||
// - 服务器创建开销
|
||
// - HTTP/2 帧编码性能
|
||
// - HPACK 头部压缩性能
|
||
// - 流创建和管理开销
|
||
// - 并发流处理吞吐量
|
||
// - 完整请求往返延迟
|
||
//
|
||
// 作者:xfy
|
||
package http2
|
||
|
||
import (
|
||
"bytes"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"golang.org/x/net/http2"
|
||
"golang.org/x/net/http2/hpack"
|
||
"rua.plus/lolly/internal/config"
|
||
)
|
||
|
||
// BenchmarkHTTP2ServerStart 测试 HTTP/2 服务器启动开销
|
||
//
|
||
// 该基准测试测量从配置创建到 http2.Server 实例化的完整开销,
|
||
// 包括默认值填充、对象池初始化和结构体分配。
|
||
func BenchmarkHTTP2ServerStart(b *testing.B) {
|
||
cfg := &config.HTTP2Config{
|
||
Enabled: true,
|
||
MaxConcurrentStreams: 100,
|
||
MaxHeaderListSize: 1048576,
|
||
PushEnabled: false,
|
||
}
|
||
handler := func(_ *fasthttp.RequestCtx) {}
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
for b.Loop() {
|
||
_, err := NewServer(cfg, handler, nil)
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// BenchmarkHTTP2FrameEncoding 测试 HTTP/2 帧编码性能
|
||
//
|
||
// 该基准测试测量使用 golang.org/x/net/http2 的 Framer
|
||
// 编码不同类型 HTTP/2 帧的开销,包括 DATA、HEADERS 和
|
||
// SETTINGS 帧。
|
||
func BenchmarkHTTP2FrameEncoding(b *testing.B) {
|
||
// 创建 Framer 实例(复用写入缓冲区)
|
||
var buf bytes.Buffer
|
||
framer := http2.NewFramer(&buf, nil)
|
||
|
||
settings := []http2.Setting{
|
||
{ID: http2.SettingMaxConcurrentStreams, Val: 100},
|
||
{ID: http2.SettingInitialWindowSize, Val: 65535},
|
||
{ID: http2.SettingMaxFrameSize, Val: 16384},
|
||
}
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
b.Run("SettingsFrame", func(b *testing.B) {
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteSettings(settings...); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("DataFrame", func(b *testing.B) {
|
||
// 预创建帧载荷
|
||
payload := make([]byte, 1024)
|
||
for i := range payload {
|
||
payload[i] = byte('a' + (i % 26))
|
||
}
|
||
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteData(1, false, payload); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("DataFrame_Small", func(b *testing.B) {
|
||
payload := []byte(`{"id":1,"name":"test"}`)
|
||
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteData(1, false, payload); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("DataFrame_Large", func(b *testing.B) {
|
||
payload := bytes.Repeat([]byte("X"), 16384)
|
||
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteData(1, false, payload); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("PingFrame", func(b *testing.B) {
|
||
data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WritePing(false, data); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("RSTStreamFrame", func(b *testing.B) {
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteRSTStream(1, http2.ErrCodeCancel); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("WindowUpdateFrame", func(b *testing.B) {
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteWindowUpdate(1, 65535); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("GoAwayFrame", func(b *testing.B) {
|
||
b.ResetTimer()
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
if err := framer.WriteGoAway(1, http2.ErrCodeNo, []byte("graceful shutdown")); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkHTTP2HeadersEncoding 测试 HPACK 头部压缩性能
|
||
//
|
||
// 该基准测试测量使用 HPACK 编码器压缩 HTTP/2 请求头部的开销,
|
||
// 模拟不同头部数量和大小场景下的压缩性能。
|
||
func BenchmarkHTTP2HeadersEncoding(b *testing.B) {
|
||
// 模拟真实 HTTP/2 请求头部
|
||
commonHeaders := []hpack.HeaderField{
|
||
{Name: ":method", Value: "GET"},
|
||
{Name: ":scheme", Value: "https"},
|
||
{Name: ":path", Value: "/api/v1/users"},
|
||
{Name: ":authority", Value: "api.example.com"},
|
||
{Name: "user-agent", Value: "Mozilla/5.0 (X11; Linux x86_64) Chrome/120.0"},
|
||
{Name: "accept", Value: "application/json, text/plain, */*"},
|
||
{Name: "accept-encoding", Value: "gzip, deflate, br"},
|
||
{Name: "accept-language", Value: "en-US,en;q=0.9"},
|
||
}
|
||
|
||
authHeaders := append([]hpack.HeaderField(nil), commonHeaders...)
|
||
authHeaders = append(authHeaders,
|
||
hpack.HeaderField{Name: "authorization", Value: "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0"},
|
||
hpack.HeaderField{Name: "x-request-id", Value: "req-abc123-def456"},
|
||
hpack.HeaderField{Name: "x-trace-id", Value: "trace-789xyz"},
|
||
hpack.HeaderField{Name: "cookie", Value: "session=abc123; _ga=GA1.2.123456; _gid=GA1.2.789012"},
|
||
)
|
||
|
||
bodyHeaders := []hpack.HeaderField{
|
||
{Name: ":method", Value: "POST"},
|
||
{Name: ":scheme", Value: "https"},
|
||
{Name: ":path", Value: "/api/v1/upload"},
|
||
{Name: ":authority", Value: "api.example.com"},
|
||
{Name: "content-type", Value: "multipart/form-data; boundary=----WebKitFormBoundary"},
|
||
{Name: "content-length", Value: "1048576"},
|
||
}
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
b.Run("CommonHeaders", func(b *testing.B) {
|
||
var buf bytes.Buffer
|
||
encoder := hpack.NewEncoder(&buf)
|
||
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
for _, hf := range commonHeaders {
|
||
if err := encoder.WriteField(hf); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("CommonHeaders_Parallel", func(b *testing.B) {
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
var buf bytes.Buffer
|
||
encoder := hpack.NewEncoder(&buf)
|
||
for _, hf := range commonHeaders {
|
||
if err := encoder.WriteField(hf); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
})
|
||
|
||
b.Run("AuthHeaders", func(b *testing.B) {
|
||
var buf bytes.Buffer
|
||
encoder := hpack.NewEncoder(&buf)
|
||
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
for _, hf := range authHeaders {
|
||
if err := encoder.WriteField(hf); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("BodyHeaders", func(b *testing.B) {
|
||
var buf bytes.Buffer
|
||
encoder := hpack.NewEncoder(&buf)
|
||
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
for _, hf := range bodyHeaders {
|
||
if err := encoder.WriteField(hf); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
b.Run("RepeatedHeaders", func(b *testing.B) {
|
||
// 重复发送相同头部,测试 HPACK 静态表和动态表的效果
|
||
var buf bytes.Buffer
|
||
encoder := hpack.NewEncoder(&buf)
|
||
|
||
for b.Loop() {
|
||
buf.Reset()
|
||
for _, hf := range commonHeaders {
|
||
if err := encoder.WriteField(hf); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkHTTP2StreamCreate 测试流创建开销
|
||
//
|
||
// 该基准测试测量 HTTP/2 流创建和请求适配的核心开销,
|
||
// 包括 RequestCtx 从池中获取、请求转换和响应返回。
|
||
// 使用适配器直接模拟流处理流程,避免网络 I/O 不确定性。
|
||
func BenchmarkHTTP2StreamCreate(b *testing.B) {
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
ctx.WriteString("OK")
|
||
}
|
||
|
||
adapter := NewFastHTTPHandlerAdapter(handler)
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
for b.Loop() {
|
||
req := httptest.NewRequest(http.MethodGet, "/stream/test", nil)
|
||
req.Header.Set(":authority", "localhost")
|
||
|
||
rec := httptest.NewRecorder()
|
||
adapter.ServeHTTP(rec, req)
|
||
}
|
||
}
|
||
|
||
// BenchmarkHTTP2ConcurrentStreams 测试并发流处理吞吐量
|
||
//
|
||
// 该基准测试使用 b.RunParallel 模拟多个客户端并发发送请求的场景,
|
||
// 测量服务器在多路复用下的吞吐量,通过复用适配器实例
|
||
// 模拟 HTTP/2 多流并发处理的实际开销。
|
||
func BenchmarkHTTP2ConcurrentStreams(b *testing.B) {
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||
ctx.WriteString(`{"status":"ok"}`)
|
||
}
|
||
|
||
adapter := NewFastHTTPHandlerAdapter(handler)
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
req := httptest.NewRequest(http.MethodGet, "/api/concurrent", nil)
|
||
req.Header.Set("Accept", "application/json")
|
||
req.Header.Set("X-Stream-ID", "stream")
|
||
|
||
rec := httptest.NewRecorder()
|
||
adapter.ServeHTTP(rec, req)
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkHTTP2RequestRoundTrip 测试完整请求往返性能
|
||
//
|
||
// 该基准测试模拟真实的 HTTP/2 请求处理流程:
|
||
// 1. 客户端创建连接并发送 HTTP/2 前导
|
||
// 2. 服务器接收并处理请求(通过适配器转换为 fasthttp)
|
||
// 3. 服务器返回响应
|
||
// 4. 客户端接收响应
|
||
//
|
||
// 注意:由于 Serve 方法会阻塞在 Accept 循环上,
|
||
// 本测试使用适配器层直接模拟完整流程,避免实际网络 I/O。
|
||
func BenchmarkHTTP2RequestRoundTrip(b *testing.B) {
|
||
// 使用适配器直接测试完整的请求转换流程
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||
ctx.Response.Header.Set("X-Request-ID", "test-id")
|
||
ctx.WriteString(`{"message":"hello","timestamp":1234567890}`)
|
||
}
|
||
|
||
adapter := NewFastHTTPHandlerAdapter(handler)
|
||
|
||
// 模拟经过 HPACK 编码后的 HTTP/2 请求
|
||
req := httptest.NewRequest(http.MethodGet, "/api/v1/test", nil)
|
||
req.Header.Set("Accept", "application/json")
|
||
req.Header.Set("User-Agent", "BenchmarkClient/1.0")
|
||
req.Header.Set("X-Request-ID", "bench-req-001")
|
||
|
||
rec := httptest.NewRecorder()
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
for b.Loop() {
|
||
rec.Body.Reset()
|
||
adapter.ServeHTTP(rec, req)
|
||
}
|
||
}
|
||
|
||
// BenchmarkHTTP2RequestRoundTrip_WithBody 测试带请求体的完整往返
|
||
//
|
||
// 模拟 POST 请求,包含 JSON 请求体。
|
||
func BenchmarkHTTP2RequestRoundTrip_WithBody(b *testing.B) {
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||
ctx.Write(ctx.PostBody())
|
||
}
|
||
|
||
adapter := NewFastHTTPHandlerAdapter(handler)
|
||
|
||
body := []byte(`{"action":"update","data":{"id":12345,"name":"benchmark test","values":[1,2,3,4,5]}}`)
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
for b.Loop() {
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/update", bytes.NewReader(body))
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.ContentLength = int64(len(body))
|
||
|
||
rec := httptest.NewRecorder()
|
||
adapter.ServeHTTP(rec, req)
|
||
}
|
||
}
|
||
|
||
// BenchmarkHTTP2RequestRoundTrip_WithBody_Parallel 测试并发带请求体的完整往返
|
||
//
|
||
// 使用 b.RunParallel 模拟高并发 API 请求场景。
|
||
func BenchmarkHTTP2RequestRoundTrip_WithBody_Parallel(b *testing.B) {
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||
ctx.Write(ctx.PostBody())
|
||
}
|
||
|
||
adapter := NewFastHTTPHandlerAdapter(handler)
|
||
|
||
body := []byte(`{"action":"batch","items":[{"id":1},{"id":2},{"id":3}]}`)
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/batch", bytes.NewReader(body))
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.ContentLength = int64(len(body))
|
||
|
||
rec := httptest.NewRecorder()
|
||
adapter.ServeHTTP(rec, req)
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkHTTP2SettingsValidation 测试设置验证性能
|
||
//
|
||
// 测量 ValidateSettings 函数的开销,用于了解
|
||
// 配置验证阶段的性能成本。
|
||
|
||
// BenchmarkHTTP2AdapterWithHPACKHeaders 测试带 HPACK 编码头部的适配器性能
|
||
//
|
||
// 模拟 HTTP/2 场景下经过 HPACK 压缩和解压缩后的头部转换开销。
|
||
func BenchmarkHTTP2AdapterWithHPACKHeaders(b *testing.B) {
|
||
handler := func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
ctx.WriteString("OK")
|
||
}
|
||
|
||
adapter := NewFastHTTPHandlerAdapter(handler)
|
||
|
||
// 预编码头部(模拟 HPACK 解码后)
|
||
methods := []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
||
paths := []string{"/api/v1/users", "/api/v1/orders", "/api/v1/products", "/api/v1/settings"}
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
var idx int
|
||
for b.Loop() {
|
||
method := methods[idx%len(methods)]
|
||
path := paths[idx%len(paths)]
|
||
idx++
|
||
|
||
req := httptest.NewRequest(method, path, nil)
|
||
req.Header.Set(":authority", "api.example.com")
|
||
req.Header.Set("x-http2-stream-id", "1")
|
||
|
||
rec := httptest.NewRecorder()
|
||
adapter.ServeHTTP(rec, req)
|
||
}
|
||
}
|