美文网首页
gaio学习和高性能http服务端实践

gaio学习和高性能http服务端实践

作者: 西5d | 来源:发表于2021-01-29 16:17 被阅读0次

    介绍

    是一个go开发的高性能异步网络通信组件,由xtaci开发,基于对epoll/kqueue的封装实践。xtaci也是kcptun作者。

    原理

    参考官方github和作者写的文章。
    https://github.com/xtaci/gaio
    https://zhuanlan.zhihu.com/p/102890337

    实践

    描述

    这里使用官方的demo加以修改,用来验证性能。其中模拟了http的返回是因为可以利用ab(Apache Benchmark)工具方便测试。加了对http keep-alive的支持,同时比较了两种情况下的性能,后面有具体的测试结果

    代码

    //go_io.go
    package main
    
    import (
       "fmt"
       "github.com/xtaci/gaio"
       "log"
       "net"
       "strings"
    )
    
    //返回也要设置keep-alive,才能让ab支持
    const http_alive_resp = `HTTP/1.1 200 OK
    Server: Mini-Server
    Last-Modified: Sun, 26 Sep 2029 22:04:35 GMT
    Accept-Ranges: bytes
    Content-Type: text/html
    Connection: Keep-Alive
    
    Hello world!
    `
    
    const http_nalive_resp = `HTTP/1.1 200 OK
    Server: Mini-Server
    Last-Modified: Sun, 26 Sep 2029 22:04:35 GMT
    Accept-Ranges: bytes
    Content-Type: text/html
    Connection: close
    
    Hello world!
    `
    
    // this goroutine will wait for all io events, and sents back everything it received
    // in async way
    func echoServer(w *gaio.Watcher) {
       for {
          // loop wait for any IO events
          results, err := w.WaitIO()
          if err != nil {
             log.Println(err)
             return
          }
    
          for _, res := range results {
             switch res.Operation {
             case gaio.OpRead: // read completion event
                if res.Error == nil {
                   in := string(res.Buffer[:res.Size])
                   fmt.Println(in)
                   //从http请求内容判断是否keep-alive,http文本协议。
                   ka := strings.Contains(strings.ToLower(in), "keep-alive")
                   // send back everything, we won't start to read again until write completes.
                   // submit an async write request
                   if ka {
                      res.Buffer = []byte(http_alive_resp)
                   } else {
                      res.Buffer = []byte(http_nalive_resp)
                   }
                   w.Write(ka, res.Conn, res.Buffer)
    
                }
             case gaio.OpWrite: // write completion event
                if res.Error == nil {
                    //keepalive 连接复用
                   if true == res.Context {
                      // since write has completed, let's start read on this conn again
                      w.Read(nil, res.Conn, res.Buffer[:cap(res.Buffer)])
                   } else {
                    //不复用进行释放
                      w.Free(res.Conn)
                   }
                }
             }
          }
       }
    }
    
    func main() {
       w, err := gaio.NewWatcher()
       if err != nil {
          log.Fatal(err)
       }
       defer w.Close()
    
       go echoServer(w)
    
       ln, err := net.Listen("tcp", "localhost:3987")
       if err != nil {
          log.Fatal(err)
       }
       log.Println("echo server listening on", ln.Addr())
    
       for {
          conn, err := ln.Accept()
          if err != nil {
             log.Println(err)
             return
          }
          log.Println("new client", conn.RemoteAddr())
    
          // submit the first async read IO request
          err = w.Read(nil, conn, make([]byte, 128))
          if err != nil {
             log.Println(err)
             return
          }
       }
    }
    
    

    性能测试参数

    使用ab进行,开启10个并发,执行 1000 0000 (1KW)的请求。

    性能测试结果

    1. 不开启keep-alive复用连接

    ab -c 10 -n 10000000  http://127.0.0.1:3987/ 
    
    # 不使用keep-alive 复用  fd=14000/2左右
    
    $ ab -c 10 -n 10000000  http://127.0.0.1:3987/ 
    This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
    Licensed to The Apache Software Foundation, http://www.apache.org/
    
    Benchmarking 127.0.0.1 (be patient)
    Completed 1000000 requests
    Completed 2000000 requests
    Completed 3000000 requests
    Completed 4000000 requests
    Completed 5000000 requests
    Completed 6000000 requests
    Completed 7000000 requests
    Completed 8000000 requests
    Completed 9000000 requests
    Completed 10000000 requests
    Finished 10000000 requests
    
    
    Server Software:        Mini-Server
    Server Hostname:        127.0.0.1
    Server Port:            3987
    
    Document Path:          /
    Document Length:        13 bytes
    
    Concurrency Level:      10
    Time taken for tests:   650.511 seconds
    Complete requests:      10000000
    Failed requests:        0
    Total transferred:      1570000000 bytes
    HTML transferred:       130000000 bytes
    Requests per second:    15372.53 [#/sec] (mean)
    Time per request:       0.651 [ms] (mean)
    Time per request:       0.065 [ms] (mean, across all concurrent requests)
    Transfer rate:          2356.92 [Kbytes/sec] received
    
    Connection Times (ms)
                  min  mean[+/-sd] median   max
    Connect:        0    0   0.1      0      13
    Processing:     0    0   0.3      0      92
    Waiting:        0    0   0.2      0      92
    Total:          0    1   0.3      1      93
    
    Percentage of the requests served within a certain time (ms)
      50%      1
      66%      1
      75%      1
      80%      1
      90%      1
      95%      1
      98%      1
      99%      2
     100%     93 (longest request)
    

    2. 开启keep-alive复用连接

    ab -c 10 -n 10000000  -k  http://127.0.0.1:3987/
    
    # 使用keep-alive复用连接  fd = 20 / 2
    
    $ ab -c 10 -n 10000000  -k  http://127.0.0.1:3987/
    This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
    Licensed to The Apache Software Foundation, http://www.apache.org/
    
    Benchmarking 127.0.0.1 (be patient)
    Completed 1000000 requests
    Completed 2000000 requests
    Completed 3000000 requests
    Completed 4000000 requests
    Completed 5000000 requests
    Completed 6000000 requests
    Completed 7000000 requests
    Completed 8000000 requests
    Completed 9000000 requests
    Completed 10000000 requests
    Finished 10000000 requests
    
    
    Server Software:        Mini-Server
    Server Hostname:        127.0.0.1
    Server Port:            3987
    
    Document Path:          /
    Document Length:        13 bytes
    
    Concurrency Level:      10
    Time taken for tests:   136.885 seconds
    Complete requests:      10000000
    Failed requests:        0
    Keep-Alive requests:    10000000
    Total transferred:      1630000000 bytes
    HTML transferred:       130000000 bytes
    Requests per second:    73053.89 [#/sec] (mean)
    Time per request:       0.137 [ms] (mean)
    Time per request:       0.014 [ms] (mean, across all concurrent requests)
    Transfer rate:          11628.70 [Kbytes/sec] received
    
    Connection Times (ms)
                  min  mean[+/-sd] median   max
    Connect:        0    0   0.0      0       0
    Processing:     0    0   0.1      0      10
    Waiting:        0    0   0.1      0      10
    Total:          0    0   0.1      0      10
    
    Percentage of the requests served within a certain time (ms)
      50%      0
      66%      0
      75%      0
      80%      0
      90%      0
      95%      0
      98%      0
      99%      0
     100%     10 (longest request)
    

    结果表格

    - 耗时(秒) QPS 端口连接占用 流量速率(Kbytes/sec)
    keep-alive 136.885 7 3053.89 10 11628.70(11M)
    no keep-alive 650.511 1 5372.53 7000 2356.92 (2M)

    总结

    如上,可以看到在开启keep-alive复用tcp连接之后,本地的连接FD等于开启的连接数(因为都是本地,所以netstat 查看后除以2,得到10个),而且请求QPS达到了73053.89,耗时136.885秒,不到3分钟,单次阶段耗时都最高10ms。而没有开启keep-alive,本地连接FD占用到了14000多(即单向应该在7000左右),QPS仅15372.53,耗时却有650.511秒,接近11分钟。

    最后

    最后再推荐一个我使用gnet实现的http echo demo, gnet作者也是个很年轻的国人。

    package main
    
    import (
        "fmt"
        "github.com/panjf2000/gnet"
    )
    
    const http_close_resp = `HTTP/1.1 200 OK
    Server: Gnet-Server
    Content-Type: text/plain; charset=UTF-8
    Connection: Close
    
    Hello world!
    `
    
    type echoGnetServer struct {
        *gnet.EventServer
    }
    
    func (es *echoGnetServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) {
        in := string(frame)
        fmt.Println(in)
        out = []byte(http_close_resp)
        action = gnet.Close
        return
    }
    
    func (es *echoGnetServer) OnClosed(c gnet.Conn, err error) (action gnet.Action) {
        fmt.Println("closed")
        return
    }
    
    func main() {
        echo := new(echoGnetServer)
        _ = gnet.Serve(echo, "tcp://:9000", gnet.WithMulticore(true))
    
    }
    
    

    如果改成keep-alive, ab 测试可以达到QPS 8.8万。

    相关文章

      网友评论

          本文标题:gaio学习和高性能http服务端实践

          本文链接:https://www.haomeiwen.com/subject/ptoqtltx.html