专栏首页码农桃花源fasthttp 快在哪里

fasthttp 快在哪里

坊间传言 fasthttp 在某些场景下比 nginx 还要快,说明 fasthttp 中应该是做足了优化。我们来做一些相关的验证工作。

先是简单的 hello server 压测。下面的结果是在 mac 上得到的,linux 下可能会有差异。

fasthttp:

wrk -c36 -t12 -d 5s http://127.0.0.1:8080
Running 5s test @ http://127.0.0.1:8080
  12 threads and 36 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   267.58us   42.44us   0.90ms   79.18%
    Req/Sec    11.05k   391.97    11.79k    87.42%
  672745 requests in 5.10s, 89.18MB read
Requests/sec: 131930.78
Transfer/sec:     17.49MB

标准库:

wrk -c36 -t12 -d 5s http://127.0.0.1:8080
Running 5s test @ http://127.0.0.1:10002
  12 threads and 36 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   310.94us  163.45us  14.41ms   93.42%
    Req/Sec     9.74k     1.01k   12.80k    75.82%
  593327 requests in 5.10s, 63.37MB read
Requests/sec: 116348.87
Transfer/sec:     12.43MB

rust 的 actix,编译选项带 --release:

wrk -c36 -t12 -d 5s http://127.0.0.1:9999
Running 5s test @ http://127.0.0.1:9999
  12 threads and 36 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   267.31us   20.52us 622.00us   86.07%
    Req/Sec    11.11k   364.03    11.64k    82.68%
  676329 requests in 5.10s, 68.37MB read
Requests/sec: 132629.68
Transfer/sec:     13.41MB

好家伙,虽然是 hello 服务,但是 fasthttp 在性能上竟然赶上 rust 编写的服务了,确实有点夸张。这也间接证明了,“某些场景”至少可能真的和不带 GC 的语言性能差不多。

说明 fasthttp 里所做的优化是值得我们做点研究的。不过 fasthttp 搞的这些优化点也并不是很神奇,首先是很常见的 goroutine workerpool,对创建的 goroutine 进行了重用,其本身的 workerpool 结构:

type workerPool struct {
  // Function for serving server connections.
  // It must leave c unclosed.
  WorkerFunc ServeHandler
    .....
  ready []*workerChan
}

核心就是 ready 数组,该数组的元素是已经创建出来的 goroutine 的 job channel。

type workerChan struct {
  lastUseTime time.Time
  ch          chan net.Conn
}

这么多年过去了,基本的 workerpool 的模型还是没什么变化。最早大概是在 handling 1 million requests with go 提到,fasthttp 中的也只是稍有区别。

具体的请求处理流程也比较简单:

tcp accept -> workerpool.Serve -> 从 workerpool 的 ready 数组中获取一个 channel -> 将当前已 accept 的连接发送到 channel 中 -> 对端消费者调用 workerFunc

这里这个 workerFunc 其实就是 serveConn,之所以不写死成 serveConn 主要还是为了在测试的时候能替换掉做 mock,不新鲜。

主要的 serve 流程:

func (s *Server) serveConn(c net.Conn) (err error) {
    for {
        ctx := s.acquireCtx(c)
        br = acquireReader(ctx)   // or br, err = acquireByteReader(&ctx)
        // read request header && body
    
        bw = acquireWriter(ctx)
    
        s.Handler(ctx) // 这里就是 listenAndServe 传入的那个 handler
    
        if br != nil {
            releaseReader(s, br)
        }
    
        if bw != nil {
            releaseWriter(s, bw)
        }
        if ctx != nil {
            s.releaseCtx(ctx)
        }
    }
}

在整个 serve 流程中,几乎所有对象全部都进行了重用,ctx(其中有 Request 和 Response 结构),reader,writer,body read buffer。可见作者对于内存重用达到了偏执的程度。

同时,对于 header 的处理,rawHeaders 是个大 byte 数组。解析后的 header 的 value 如果是字符串类型,其实都是指向这个大 byte 数组的,不会重复生成很多小对象:

如果是我们自己写这种 kv 结构的 header,大概率就直接 map[string][]string 上了。

通过阅读 serveConn 的流程我们也可以发现比较明显的问题,在执行完用户的 Handler 之后,fasthttp 会将所有相关的对象全部释放并重新推进对象池中,在某些场景下,这样做显然是不合适的,举个例子:

当用户流程中异步启动了 goroutine,并且在 goroutine 中使用 ctx.Request 之类对象时就会遇到并发问题,因为在 fasthttp 的主流程中认为 ctx 的生命周期已经结束,将该 ctx 放回了 sync.Pool,然而用户依然在使用。想要避免这种问题,用户需要将各种 fasthttp 返回的对象人肉拷贝一遍。

从这点上来看,基于 sync.Pool 的性能优化往往也是有代价的,无论在什么场景下使用 sync.Pool,都需要对应用程序中的对象生命周期进行一定的假设,这种假设并不见得适用于 100% 的场景,否则这些手段早就进标准库,而非开源库了。

对于库的用户来说,这样的优化手段轻则带来更高的心智负担,重则是线上 bug。在使用开源库之前,还是要多多注意。非性能敏感的业务场景,还是用标准库比较踏实。

参考资料

[1] https://github.com/valyala/fasthttp

[2] https://medium.com/smsjunk/handling-1-million-requests-per-minute-with-golang-f70ac505fca

本文分享自微信公众号 - 码农桃花源(CoderPark),作者:曹春晖

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Golang error 的突围

    姗姗来迟的 Go 1.13 修改了 errors 包,增加了几个函数,用于增强 error 的功能,这篇文章介绍 error 相关的用法。

    梦醒人间
  • 深度解密Go语言之context

    Go 语言的 context 包短小精悍,非常适合新手学习。不论是它的源码还是实际使用,都值得投入时间去学习。

    梦醒人间
  • 意犹未尽 —— GPM 的状态流转(十)

    最开始的时候,我们讲了 GPM 到底是什么,当时没有看过太多源码,所以对 GPM 没有一个整体上的认识。

    梦醒人间
  • 有人骗了你的小钱钱快通过微信小程序举报他

    都说人心复杂,特别在网络上,骗子都带上了面具,想尽办法想要骗走我们辛辛苦苦攒下的小钱钱,如果在微信、QQ等社交软件或其他渠道上,被不怀好意的小骗子骗走了我们...

    耐思智慧
  • Nginx隐藏版本号的办法

    隐藏nginx的版本号很简单,nginx的HttpCoreModule提供了一条叫做server_tokens指令,我这要将这条指令设置为“server_tok...

    习惯说一说
  • 2019牛客暑期多校训练营 第三场 F Planting Trees 暴力枚举子矩阵

    题意:t个样例, n,m 给一个 n*n 矩形,值为高,求最大子矩阵面积,要求子矩阵中高度差不超过m

    用户2965768
  • Flash被全世界抛弃,到底经历了什么

    For 20 years, Flash has helped shape the way that you play games, watch videos a...

    哲洛不闹
  • 到底是C还是C++?关于数据/程序员的22则冷笑话

    1、浙大软件学院,为了吸引更多的优秀人才报考,发布了一系列的宣传照,其中有一张引起了很多人的激烈争议。一些人说是C,另一些人非说是C++。。。 ? 2、别人家的...

    CSDN技术头条
  • Leetcode-Medium 96.Unique Binary Search Trees

    即有:G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n)

    致Great
  • 视频 | MIT和FB搞了个视频数据集,让Youtube视频审查更容易

    创建这样的数据集是一项非常艰巨的工作,因为它包含超过 50 万个视频,为 200 个不同的活动提供近 200 万个注释,并且还有很多预处理步骤需要执行才能使其可...

    AI科技评论

扫码关注云+社区

领取腾讯云代金券