概述
fasthttp
是一个使用 Go 语言开发的 HTTP 包,主打高性能,针对 HTTP 请求响应流程中的hot path
代码进行了优化,达到零内存分配,性能比标准库的net/http
快 10 倍。
上面是来自官方
Github
主页的项目介绍,抛开其介绍内容不谈,光从名字本身来看,作者对项目代码的自信程度可见一斑。本文不会讲解
fasthttp
的应用方法,而是会重点分析 fasthttp
高性能的背后实现原理。基准测试
我们可以通过基准测试看看
fasthttp
是否真的如描述所言,吊打标准库的 net/http
,下面是官方提供的基准测试结果:net/http
fasthttp
基准结果对比
从基准测试结果来看,
fasthttp
的执行速度要比标准库的 net/http
快很多,此外,fasthttp
的内存分配方面优化到了 0
, 完胜 net/http
。核心优化点
笔者选择的 valyala/fasthttp 版本为
v1.45.0
。对象复用
workerPool
workerpool
对象表示 连接处理
工作池,这样可以控制连接建立后的处理方式,而不是像标准库 net/http
一样,对每个请求连接都启动一个 goroutine
处理, 内部的 ready
字段存储空闲的 workerChan
对象,workerChanPool
字段表示管理 workerChan
的对象池。请求/响应 对象
请求对象
Request
和响应对象 Response
都是通过对象池进行管理的,对应的代码如下:Cookie 对象
Cookie
对象也是通过对象池进行管理的,对应的代码如下:其他对象复用
通过输出结果可以看到,
fasthttp
中一共有 38
个对象是通过对象池进行管理的,可以说几乎复用了所有对象,So Crazy![]byte 复用
fasthttp
中复用的对象在使用完成后归还到对象池之前,需要调用对应的 Reset
方法进行重置,如果对象中包含 []byte
类型的字段, 那么会直接进行复用,而不是初始化新的 []byte
, 例如 URI
对象的 Reset
方法:此外,涉及到单个字段的修改,如果字段是
[]byte
类型,还是会直接复用,例如 Cookie
对象的这几个方法:上面几个方法的内部实现中,无一例外,都对
[]byte
类型的参数进行了复用。[]byte 和 string 转换
fasthttp
专门提供了 []byte
和 string
这两种常见的数据类型相互转换的方法 ,避免了 内存分配 + 复制
,提升性能。高性能的 bytebufferpool
fasthttp
并没有直接使用标准库中的 bytes.Buffer
对象,而是引用了作者的另外一个包 valyala/bytebufferpool, 这个包的核心优化点是 避免内存拷贝 + 底层 byte 切片复用
,感兴趣的读者可以看看官方给出的 基准测试结果。避免反射
fasthttp
中的所有 对象深拷贝
内部实现中都没有使用 反射
,而是手动实现的,这样可以完全规避 反射
带来的影响,例如 Cookie
对象的拷贝实现:从上面的代码中可以看到,
拷贝
的内部实现就是手动挨个复制字段,非常 原始
的解决方案。另外,请求对象
Request
和响应对象 Response
的拷贝实现和 Cookie
有异曲同工之处:fasthttp 的问题
软件工程没有银弹,高性能的背后必然是以某些条件作为代价的,
fasthttp
的主要问题有:- 降低了代码可读性 (如果不了解
fasthttp
的设计理念,贸然读代码很可能无法理解各种方法实现)
- 增加了开发复杂性,代码开发量要比使用标准库高,对象复用导致了
申请/归还
流程彷佛回到了C/C++
语言手动管理内存模式
- 增加了开发者心智负担,如果已经习惯了标准库的开发模式,很容易写出
Bug
- 如果业务中有
异步
处理场景,框架核心的对象复用
机制可能导致各种问题,如对象提前归还、对象指针hang
起、还有更严重的对象字段被重置后继续引用 (这类业务逻辑问题比较难排查)
多核系统的性能优化技巧
- 使用
reuseport
监听 (SO_REUSEPORT
允许在多核服务器上线性扩展服务器性能,详细信息请参阅 这个链接 )
- 使用
GOMAXPROCS=1
为每个 CPU 核运行一个单独的服务器实例 (进程和 CPU 绑定)
- 确保多队列网卡的中断均匀分布在 CPU 内核之间,详细信息请参阅 这个链接
fasthttp 最佳实践
- 尽可能复用对象和
[]byte buffers
, 而不是重新分配
- 使用
[]byte
特性技巧
- 使用
sync.Pool
对象池
- 在生产环境对程序进行性能分析,
go tool pprof --alloc_objects app mem.pprof
通常比go tool pprof app cpu.pprof
更容易体现性能瓶颈
- 为
hot path
上的代码编写测试和基准测试
- 避免
[]byte
和string
直接进行类型转换,因为这可能会导致内存分配 + 复制
,可以参考fasthttp
包内的s2b
方法和b2s
方法
- 定期对代码进行 竞态检测, 一般会直接集成到
CI
中
- 使用
quicktemplate
而非html/template
模板
是否采用 fasthttp
fasthttp
是为一些高性能边缘场景设计的,如果你的业务需要支撑较高的 QPS
并且保持一致的低延迟时间,那么采用 fasthttp
是非常合理的, 反之 fasthttp
可能并不适合 (增加开发复杂度和开发者心智负担)。大多数情况下,标准库 net/http
是更好的选择,因为它简单易用并且兼容性很高。 如果你的业务流量很少,那么两者之间的 所谓性能差异
几乎可以忽略 (果断使用标准库的 net/http 包就可以)。