fasthttp特性(相比net/http)
1. 极致的对象重用
net/http:每个 HTTP 请求都会在堆上分配新的 *http.Request和 *http.Response对象,请求处理完毕后,这些对象会被垃圾回收(GC)掉。在高并发下,这会创建海量的临时对象,给 GC 带来巨大压力。
fasthttp:使用一个 *fasthttp.RequestCtx对象池。当一个请求到来时,从池中获取一个 RequestCtx对象;请求处理完毕后,重置该对象并放回池中。这几乎完全消除了每个请求的内存分配,极大地减轻了 GC 的负担。
2. 避免 []byte到 string的转换
HTTP 协议本质上是字节流([]byte)。
net/http:为了易用性和安全性,API 大量使用 string类型。例如 req.URL.Path、req.Header.Get(“User-Agent”)都返回 string。每次转换都需要分配新的内存并拷贝数据。
fasthttp:API 几乎完全基于 []byte。例如 ctx.Path()、ctx.Request.Header.Peek(“User-Agent”)都直接返回底层字节切片。它鼓励用户在整个请求处理周期内直接操作 []byte,避免了无数微小的内存分配和拷贝。
3. 连接级别的优化
net/http:每个连接由一个独立的 goroutine 处理。对于海量并发连接,goroutine 的调度开销虽然小,但依然存在。
fasthttp:使用了更高效的 epoll(Linux)/ kqueue(BSD) 事件驱动机制。它使用较少数量的 worker goroutine 来轮询多个连接,减少了 goroutine 的调度和上下文切换开销。这种模式更适合维持数十万计的并发空闲连接。
fasthttp劣势以及不适用场景
API不兼容net/http: 不能将基于 net/http的中间件或框架(如 Gin, Echo)直接用于 fasthttp,导致生态被割裂。
极易误用:由于对象重用,如果你在请求处理函数之外保存了RequestCtx或其中任何字段(如 ctx.Request.URI()的指针,将会导致数据竞争和内存污染,这是极其危险的Bug。
功能更简单:它可能不支持 net/http中一些不常用或复杂的特性以获得高性能。比如:Request|Response|Handler等核心对象完全重写、http.Handler接口不支持、context.Context集成方式不同(影响值传递)、Body是否允许多次读取、不支持HTTP2等
fasthttp使用示例
[]byte数据需要逃逸出当前请求处理函数的生命周期,必须进行复制
错误示例(未拷贝body,直接传递body指针到异步处理函数中):
func handler(ctx *fasthttp.RequestCtx) {
// 这只是获取切片,数据会被重用
body := ctx.PostBody()
// 启动一个 goroutine 或切片地址传递给异步操作
go handleBody(body)
// 响应成功
ctx.Success("", nil)
// 此时 handler 函数返回,ctx 被重置并用于下一个请求
// 问题:body 切片指向的内存即将被下一个请求的数据覆盖,可能导致handleBody读取数据异常!
}
正确示例(对body进行拷贝,传递副本指针到异步处理函数中):
func handler(ctx *fasthttp.RequestCtx) {
// 这只是获取切片,数据会被重用
body := ctx.PostBody()
// 创建一个body副本
bodyCopy := make([]byte, len(body))
copy(bodyCopy, body)
// 启动一个 goroutine 或将其传递给异步操作
go handleBody(bodyCopy)
// 响应成功
ctx.Success("", nil)
}
fasthttp获取请求信息都是返回[]byte,通常使用Peek()获取
func fasthttpHandler(ctx *fasthttp.RequestCtx) {
// 获取请求路径 - 返回 []byte
path := ctx.Path()
// 获取请求方式 - 返回 []byte
method := ctx.Method()
// 获取查询参数 - 返回 []byte,需手动转换
nameBytes := ctx.QueryArgs().Peek("name")
name := string(nameBytes) // 必须转换!且要注意生命周期
// 获取请求头 - 返回 []byte
userAgent := ctx.Request.Header.Peek("Customer-Header")
// 读取请求体 - 直接返回 []byte,返回底层缓冲区的引用!
body := ctx.PostBody()
// 设置响应头
ctx.Response.Header.SetContentType("application/json")
// 写入响应 - 只接受 []byte
ctx.Write([]byte(`{"name": "` + name + `"}`))
// 更高效的写法:
ctx.SetBodyString(`{"name": "` + name + `"}`)
}
fasthttp大量使用[]byte而不是 string来避免内存分配,所以减少直接转换,避免失去性能优势。
func handler(ctx *fasthttp.RequestCtx) {
// 错误:每次都会分配新的字符串
if string(ctx.Method()) == "POST" {
}
// 正确:尽可能直接使用 []byte 操作
if bytes.Equal(ctx.Method(), []byte("POST")) {
}
// 或者对于确定需要字符串的情况,在必要时才转换
if needStringRepresentation {
pathStr := string(ctx.Path())
// 使用 pathStr
}
}
类似于其他 HTTP 库,请求体只能读取一次
func handler(ctx *fasthttp.RequestCtx) {
// 错误:多次读取请求体
body1 := ctx.PostBody()
// ... 一些处理
body2 := ctx.PostBody() // 可能不是期望的值
// 如果中间有其他操作可能修改了上下文,会导致问题
}
fasthttp的中间件需要显式调用 ctx.Next()
// 日志中间件
func loggingMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
start := time.Now()
path := string(ctx.Path())
method := string(ctx.Method())
// 调用下一个处理程序
next(ctx)
duration := time.Since(start)
fmt.Printf("%s %s - %d - %v\n", method, path, ctx.Response.StatusCode(), duration)
}
}
// 认证中间件
func authMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
token := string(ctx.Request.Header.Peek("X-Auth-Token"))
if token != "my-secret-token" {
ctx.Error("Unauthorized", fasthttp.StatusUnauthorized)
return // 重要:认证失败时直接返回,不调用 next
}
next(ctx)
}
}
// 链式使用中间件
func main() {
handler := func(ctx *fasthttp.RequestCtx) {
ctx.WriteString("Protected content!")
}
// 中间件链:先认证,后记录日志
wrappedHandler := loggingMiddleware(authMiddleware(handler))
fasthttp.ListenAndServe(":8080", wrappedHandler)
}
fasthttp客户端内置了连接池,需要正确配置
package main
import (
"fmt"
"time"
"github.com/valyala/fasthttp"
)
func main() {
// 配置高性能客户端
client := &fasthttp.Client{
Name: "MyClient",
MaxConnsPerHost: 100, // 每个主机的最大连接数
// 读写超时
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
// 长连接配置
MaxIdleConnDuration: 5 * time.Minute,
}
// 使用客户端(注意重用 Request 和 Response 对象)
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}()
req.SetRequestURI("http://127.0.0.1/api")
req.Header.SetMethod("GET")
if err := client.Do(req, resp); err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Status: %d\n", resp.StatusCode())
fmt.Printf("Body: %s\n", resp.Body())
}
fasthttp流式返回数据
func streamResponseHandler(ctx *fasthttp.RequestCtx) {
// 这只是获取切片,数据会被重用
body := ctx.PostBody()
// 创建一个body副本
bodyCopy := make([]byte, len(body))
copy(bodyCopy, body)
// 使用SetBodyStreamWriter返回流式数据
ctx.Response.SetBodyStreamWriter(sendStreamRequest(bodyCopy))
}
func sendStreamRequest(body byte[]) func(w *bufio.Writer) {
return func(w *bufio.Writer) {
var err error
// 业务处理.....
// 返回错误信息
if err != nil {
// 拼接SSE格式失败信息
fmt.Fprintf(w, "data: [ERROR] %s\n\n", fmt.Sprintf("请求后端失败:%s", err.Error()))
w.Flush()
return
}
// 写入数据
w.Write([]byte(fmt.Sprintf("data: %s\n\n", string("xxxx"))))
w.Flush()
}
}
fasthttp作为代理客户端转发请求,接收并返回流式数据
func streamResponseHandler(ctx *fasthttp.RequestCtx) {
// 这只是获取切片,数据会被重用
body := ctx.PostBody()
// 创建一个body副本
bodyCopy := make([]byte, len(body))
copy(bodyCopy, body)
ctx.Response.SetBodyStreamWriter(sendStreamRequest(bodyCopy))
}
func sendStreamRequest(body byte[]) func(w *bufio.Writer) {
return func(w *bufio.Writer) {
// 构建请求client
client := &fasthttp.Client{
StreamResponseBody: true, // 流式需要开启该选项,这里一定要开启,否则不能流式响应
Dial: fasthttp.Dial,
ReadTimeout: time.Duration(5) * time.Second,
WriteTimeout: time.Duration(5) * time.Second,
MaxIdleConnDuration: time.Duration(5) * time.Second,
}
// 构建请求对象
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
// 设置请求参数
req.SetBodyRaw(body)
// 设置请求方法
req.Header.SetMethod("POST")
// 设置请求地址
req.SetRequestURI("http://127.0.0.1:9090/api/test")
// 设置请求头
// req.Header.Set(key, value)
// 创建响应对象来接收后端响应
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp) // 确保释放资源
// 向后端服务器发起请求
if err := client.Do(req, resp); err != nil {
log.Errorf("请求后端失败: %v", err)
fmt.Fprintf(w, "data: [ERROR] %s\n\n", fmt.Sprintf("请求后端失败:%s", err.Error()))
w.Flush()
return
}
// 接收后端SSE响应数据
bodyStream := resp.BodyStream()
reader := bufio.NewReader(bodyStream)
for {
if line, rErr := reader.ReadBytes('\n'); rErr != nil {
log.Errorf("请求后端失败: %v", rErr)
break
} else if len(line) > 0 {
w.Write(line)
w.Flush()
}
}
}
}
fasthttp不兼容net/http部分
// 1. 获取参数方式不同, Context上下文对象不同
// ========== net/http ==========
func netHttpHandler(w http.ResponseWriter, r *http.Request) {
// 获取查询参数 - 返回 string
name := r.URL.Query().Get("name")
// 获取请求头 - 返回 string
userAgent := r.Header.Get("User-Agent")
// 读取请求体 - 返回 []byte, 但通常需要错误处理
body, _ := io.ReadAll(r.Body)
// 设置响应头
w.Header().Set("Content-Type", "application/json")
// 写入响应 - 接受 string 或 []byte
fmt.Fprintf(w, `{"name": "%s"}`, name)
}
// ========== fasthttp ==========
func fasthttpHandler(ctx *fasthttp.RequestCtx) {
// 获取查询参数 - 返回 []byte,需手动转换
nameBytes := ctx.QueryArgs().Peek("name")
name := string(nameBytes) // 必须转换!且要注意生命周期
// 获取请求头 - 返回 []byte
userAgent := ctx.Request.Header.Peek("User-Agent")
// 读取请求体 - 直接返回 []byte
body := ctx.PostBody()
// 设置响应头
ctx.Response.Header.SetContentType("application/json")
// 写入响应 - 只接受 []byte
ctx.Write([]byte(`{"name": "` + name + `"}`)) // 字符串拼接性能差
// 更高效的写法:
ctx.SetBodyString(`{"name": "` + name + `"}`)
}
// 2. 中间件生态完全割裂
// ========== net/http ==========
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.Path)
next.ServeHTTP(w, r) // 调用下一个处理器
})
}
// ========== fasthttp ==========
func loggingMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
log.Printf("%s", ctx.Path())
next(ctx) // 调用下一个处理器
}
}
// 3. http.Handler接口不支持, fasthttp未实现net/http的Handler接口,其他框架实现比如Gin等
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// 4. fasthttp默认不支持http2协议,需要安装其他模块,比如http2
目前只踩过这些大坑,后续使用过程中遇到的大坑,再继续更新!!!
1020

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



