编写Go程序对Nginx服务器进行性能测试的方法

目前有很多提供Go语言HTTP应用服务的方法,但其中最好的选择取决于每个应用的实际情况。目前,Nginx看起来是每个新项目的标准Web服务器,即使在有其他许多不错Web服务器的情况下。然而,在Nginx上提供Go应用服务的开销是多少呢?我们需要一些nginx的特性参数(vhosts,负载均衡,缓存,等等)或者直接使用Go提供服务?如果你需要nginx,最快的连接机制是什么?这就是在这我试图回答的问题。该基准测试的目的不是要验证Go比nginx的快或慢。那将会很愚蠢。

下面是我们要比较不同的设置:

  • Go HTTP standalone (as the control group)
  • Nginx proxy to Go HTTP
  • Nginx fastcgi to Go TCP FastCGI
  • Nginx fastcgi to Go Unix Socket FastCGI

硬件

因为我们将在相同的硬件下比较所有设置,硬件选择的是廉价的一个。这不应该是一个大问题。

  • Samsung 笔记本 NP550P5C-AD1BR
  • Intel Core i7 3630QM @2.4GHz (quad core, 8 threads)
  • CPU caches: (L1: 256KiB, L2: 1MiB, L3: 6MiB)
  • RAM 8GiB DDR3 1600MHz

软件

  • Ubuntu 13.10 amd64 Saucy Salamander (updated)
  • Nginx 1.4.4 (1.4.4-1~saucy0 amd64)
  • Go 1.2 (linux/amd64)
  • wrk 3.0.4

设置
内核

只需很小的一点调整,将内核的limits调高。如果你对这一变量有更好的想法,请在写在下面评论处:

代码如下:

fs.file-max                    9999999
fs.nr_open                     9999999
net.core.netdev_max_backlog    4096
net.core.rmem_max              16777216
net.core.somaxconn             65535
net.core.wmem_max              16777216
net.ipv4.ip_forward            0
net.ipv4.ip_local_port_range   1025       65535
net.ipv4.tcp_fin_timeout       30
net.ipv4.tcp_keepalive_time    30
net.ipv4.tcp_max_syn_backlog   20480
net.ipv4.tcp_max_tw_buckets    400000
net.ipv4.tcp_no_metrics_save   1
net.ipv4.tcp_syn_retries       2
net.ipv4.tcp_synack_retries    2
net.ipv4.tcp_tw_recycle        1
net.ipv4.tcp_tw_reuse          1
vm.min_free_kbytes             65536
vm.overcommit_memory           1
Limits

供root和www-data打开的最大文件数限制被配置为200000。
Nginx

有几个必需得Nginx调整。有人跟我说过,我禁用了gzip以保证比较公平。下面是它的配置文件/etc/nginx/nginx.conf:
 

代码如下:

user www-data;
worker_processes auto;
worker_rlimit_nofile 200000;
pid /var/run/nginx.pid;
 
events {
    worker_connections 10000;
    use epoll;
    multi_accept on;
}
 
http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 300;
    keepalive_requests 10000;
    types_hash_max_size 2048;
 
    open_file_cache max=200000 inactive=300s;
    open_file_cache_valid 300s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
 
    server_tokens off;
    dav_methods off;
 
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
 
    access_log /var/log/nginx/access.log combined;
    error_log /var/log/nginx/error.log warn;
 
    gzip off;
    gzip_vary off;
 
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*.conf;
}
Nginx vhosts
 
upstream go_http {
    server 127.0.0.1:8080;
    keepalive 300;
}
 
server {
    listen 80;
    server_name go.http;
    access_log off;
    error_log /dev/null crit;
 
    location / {
        proxy_pass http://go_http;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
 
upstream go_fcgi_tcp {
    server 127.0.0.1:9001;
    keepalive 300;
}
 
server {
    listen 80;
    server_name go.fcgi.tcp;
    access_log off;
    error_log /dev/null crit;
 
    location / {
        include fastcgi_params;
        fastcgi_keep_conn on;
        fastcgi_pass go_fcgi_tcp;
    }
}
 
upstream go_fcgi_unix {
    server unix:/tmp/go.sock;
    keepalive 300;
}
 
server {
    listen 80;
    server_name go.fcgi.unix;
    access_log off;
    error_log /dev/null crit;
 
    location / {
        include fastcgi_params;
        fastcgi_keep_conn on;
        fastcgi_pass go_fcgi_unix;
    }
}

Go源码
 

代码如下:

package main
 
import (
    "fmt"
    "log"
    "net"
    "net/http"
    "net/http/fcgi"
    "os"
    "os/signal"
    "syscall"
)
 
var (
    abort bool
)
 
const (
    SOCK = "/tmp/go.sock"
)
 
type Server struct {
}
 
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    body := "Hello World\n"
    // Try to keep the same amount of headers
    w.Header().Set("Server", "gophr")
    w.Header().Set("Connection", "keep-alive")
    w.Header().Set("Content-Type", "text/plain")
    w.Header().Set("Content-Length", fmt.Sprint(len(body)))
    fmt.Fprint(w, body)
}
 
func main() {
    sigchan := make(chan os.Signal, 1)
    signal.Notify(sigchan, os.Interrupt)
    signal.Notify(sigchan, syscall.SIGTERM)
 
    server := Server{}
 
    go func() {
        http.Handle("/", server)
        if err := http.ListenAndServe(":8080", nil); err != nil {
            log.Fatal(err)
        }
    }()
 
    go func() {
        tcp, err := net.Listen("tcp", ":9001")
        if err != nil {
            log.Fatal(err)
        }
        fcgi.Serve(tcp, server)
    }()
 
    go func() {
        unix, err := net.Listen("unix", SOCK)
        if err != nil {
            log.Fatal(err)
        }
        fcgi.Serve(unix, server)
    }()
 
    <-sigchan
 
    if err := os.Remove(SOCK); err != nil {
        log.Fatal(err)
    }
}

检查HTTP header

为公平起见,所有的请求必需大小相同。

代码如下:

$ curl -sI http://127.0.0.1:8080/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 12
Content-Type: text/plain
Server: gophr
Date: Sun, 15 Dec 2013 14:59:14 GMT
 
$ curl -sI http://127.0.0.1:8080/ | wc -c
141
 
$ curl -sI http://go.http/
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 15 Dec 2013 14:59:31 GMT
Content-Type: text/plain
Content-Length: 12
Connection: keep-alive
 
$ curl -sI http://go.http/ | wc -c
141
 
$ curl -sI http://go.fcgi.tcp/
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12
Connection: keep-alive
Date: Sun, 15 Dec 2013 14:59:40 GMT
Server: gophr
 
$ curl -sI http://go.fcgi.tcp/ | wc -c
141
 
$ curl -sI http://go.fcgi.unix/
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12
Connection: keep-alive
Date: Sun, 15 Dec 2013 15:00:15 GMT
Server: gophr
 
$ curl -sI http://go.fcgi.unix/ | wc -c
141

启动引擎

  • 使用sysctl配置内核
  • 配置Nginx
  • 配置Nginx vhosts
  • 用www-data启动服务
  • 运行基准测试

基准测试

GOMAXPROCS = 1
Go standalone
 

代码如下:

# wrk -t100 -c5000 -d30s http://127.0.0.1:8080/
Running 30s test @ http://127.0.0.1:8080/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   116.96ms   17.76ms 173.96ms   85.31%
    Req/Sec   429.16     49.20   589.00     69.44%
  1281567 requests in 29.98s, 215.11MB read
Requests/sec:  42745.15
Transfer/sec:      7.17MB
Nginx + Go through HTTP

# wrk -t100 -c5000 -d30s http://go.http/
Running 30s test @ http://go.http/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   124.57ms   18.26ms 209.70ms   80.17%
    Req/Sec   406.29     56.94     0.87k    89.41%
  1198450 requests in 29.97s, 201.16MB read
Requests/sec:  39991.57
Transfer/sec:      6.71MB

Nginx + Go through FastCGI TCP
 

代码如下:

# wrk -t100 -c5000 -d30s http://go.fcgi.tcp/
Running 30s test @ http://go.fcgi.tcp/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   514.57ms  119.80ms   1.21s    71.85%
    Req/Sec    97.18     22.56   263.00     79.59%
  287416 requests in 30.00s, 48.24MB read
  Socket errors: connect 0, read 0, write 0, timeout 661
Requests/sec:   9580.75
Transfer/sec:      1.61MB
Nginx + Go through FastCGI Unix Socket

# wrk -t100 -c5000 -d30s http://go.fcgi.unix/
Running 30s test @ http://go.fcgi.unix/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   425.64ms   80.53ms 925.03ms   76.88%
    Req/Sec   117.03     22.13   255.00     81.30%
  350162 requests in 30.00s, 58.77MB read
  Socket errors: connect 0, read 0, write 0, timeout 210
Requests/sec:  11670.72
Transfer/sec:      1.96MB

GOMAXPROCS = 8
Go standalone
 

代码如下:

# wrk -t100 -c5000 -d30s http://127.0.0.1:8080/
Running 30s test @ http://127.0.0.1:8080/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    39.25ms    8.49ms  86.45ms   81.39%
    Req/Sec     1.29k   129.27     1.79k    69.23%
  3837995 requests in 29.89s, 644.19MB read
Requests/sec: 128402.88
Transfer/sec:     21.55MB

Nginx + Go through HTTP

代码如下:

# wrk -t100 -c5000 -d30s http://go.http/
Running 30s test @ http://go.http/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   336.77ms  297.88ms 632.52ms   60.16%
    Req/Sec     2.36k     2.99k   19.11k    84.83%
  2232068 requests in 29.98s, 374.64MB read
Requests/sec:  74442.91
Transfer/sec:     12.49MB

Nginx + Go through FastCGI TCP
 

代码如下:

# wrk -t100 -c5000 -d30s http://go.fcgi.tcp/
Running 30s test @ http://go.fcgi.tcp/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   217.69ms  121.22ms   1.80s    75.14%
    Req/Sec   263.09    102.78   629.00     62.54%
  721027 requests in 30.01s, 121.02MB read
  Socket errors: connect 0, read 0, write 176, timeout 1343
Requests/sec:  24026.50
Transfer/sec:      4.03MB

Nginx + Go through FastCGI Unix Socket
 

代码如下:

# wrk -t100 -c5000 -d30s http://go.fcgi.unix/
Running 30s test @ http://go.fcgi.unix/
  100 threads and 5000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   694.32ms  332.27ms   1.79s    62.13%
    Req/Sec   646.86    669.65     6.11k    87.80%
  909836 requests in 30.00s, 152.71MB read
Requests/sec:  30324.77
Transfer/sec:      5.09MB

结论

第一组基准测试时一些Nginx的设置还没有很好的优化(启用gzip,Go的后端没有使用keep-alive连接)。当改为wrk以及按建议优化Nginx后结果有较大差异。

当GOMAXPROCS=1时,Nginx的开销不是那么大,但当OMAXPROCS=8时差异就很大了。以后可能会再试一下其他设置。如果你需要使用Nginx像虚拟主机,负载均衡,缓存等特性,使用HTTP proxy,别使用FastCGI。有些人说Go的FastCGI还没有被很好优化,这也许就是测试结果中巨大差异的原因。

(0)

相关推荐

  • Go语言服务器开发之客户端向服务器发送数据并接收返回数据的方法

    本文实例讲述了Go语言服务器开发之客户端向服务器发送数据并接收返回数据的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package mysocket    import (      "fmt"      "io"      "net"  )    func MySocketBase() {      var (          host   = "www.apache.org"         

  • go语言实现一个最简单的http文件服务器实例

    本文实例讲述了go语言实现一个最简单的http文件服务器的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (     "net/http" ) func main() {     http.Handle("/", http.FileServer(http.Dir("./")))     http.ListenAndServe(":8123", nil) } 希望本文

  • 在Apache服务器上同时运行多个Django程序的方法

    在同一个 Apache 实例中运行多个 Django 程序是完全可能的. 当你是一个独立的 Web 开发人员并有多个不同的客户时,你可能会想这么做. 只要像下面这样使用 VirtualHost 你可以实现: NameVirtualHost * <VirtualHost *> ServerName www.example.com # ... SetEnv DJANGO_SETTINGS_MODULE mysite.settings </VirtualHost> <Virtual

  • 剖析Go编写的Socket服务器模块解耦及基础模块的设计

    Server的解耦-通过Router+Controller实现逻辑分发 在实际的系统项目工程中中,我们在写代码的时候要尽量避免不必要的耦合,否则你以后在更新和维护代码的时候会发现如同深陷泥潭,随便改点东西整个系统都要变动的酸爽会让你深切后悔自己当初为什么非要把东西都写到一块去(我不会说我刚实习的时候就是这么干的...) 所以这一篇主要说说如何设计Sever的内部逻辑,将Server处理Client发送信息的这部分逻辑与Sevrer处理Socket连接的逻辑进行解耦- 这一块的实现灵感主要是在读一

  • 在Go程序中实现服务器重启的方法

    Go被设计为一种后台语言,它通常也被用于后端程序中.服务端程序是GO语言最常见的软件产品.在这我要解决的问题是:如何干净利落地升级正在运行的服务端程序. 目标: 不关闭现有连接:例如我们不希望关掉已部署的运行中的程序.但又想不受限制地随时升级服务. socket连接要随时响应用户请求:任何时刻socket的关闭可能使用户返回'连接被拒绝'的消息,而这是不可取的. 新的进程要能够启动并替换掉旧的. 原理 在基于Unix的操作系统中,signal(信号)是与长时间运行的进程交互的常用方法. SIGT

  • Go语言服务器开发实现最简单HTTP的GET与POST接口

    本文实例讲述了Go语言服务器开发实现最简单HTTP的GET与POST接口.分享给大家供大家参考.具体分析如下: Go语言提供了http包,可以很轻松的开发http接口.以下为示例代码: 复制代码 代码如下: package webserver    import (      "encoding/json"      "fmt"      "net/http"      "time"  )    func WebServerB

  • Centos5.4+Nginx-0.8.50+UWSGI-0.9.6.2+Django-1.2.3搭建高性能WEB服务器

    之前一直使用Nginx+Fastcgi来搭建python web服务器,本文介绍Nginx+UWSGI组合来实现.uWSGI 是一个快速的.纯C语言开发的.自维护的.对开发者友好的WSGI服务器,旨在提供专业的 Python web应用发布和开发.它更符合python web的标准协议,速度要比Fastcgi要快.性能更加稳定. 一.安装平台 1.安装pcre 复制代码 代码如下: cd /home mkdir -p /home/install/nginx && cd /home/inst

  • C++、python和go语言实现的简单客户端服务器代码示例

    工作中用到了C/S模型,所做的也无非是给服务器发数据,但开发阶段会遇到程序自身的回环测试,需要用到简单的服务端以便验证数据发送的正确性. 写软件用C++,跑测试用python,这段时间也刚好看go语言,所以都要有demo.以下三组程序实现的功能相同,这里一起做下总结. 一.C++实现 Boost.Asio是一个跨平台的C++库,它用现代C++方法为网络和底层I/O程序提供了一致的异步I/O模型. 为了跨平台,我用boost库实现,具体如下. 服务端代码: 复制代码 代码如下: /*      F

  • Go语言实现简单Web服务器的方法

    本文实例讲述了Go语言实现简单Web服务器的方法.分享给大家供大家参考.具体分析如下: 包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求: package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } 在这个例子中,类型 Hello 实现了 http.Handler. 注意: 这个例子无法在基于 web 的指南用户界面运行.为了尝试编写 web 服务器,可能需要安装

  • 服务器端Go程序对长短链接的处理及运行参数的保存

    对长.短连接的处理策略(模拟心跳) 作为一个可能会和很多Client进行通讯交互的Server,首先要保证的就是整个Server运行状态的稳定性,因此在和Client建立连接通讯的时候,确保连接的及时断开非常重要,否则一旦和多个客户端建立不关闭的长连接,对于服务器资源的占用是很可怕的.因此,我们需要针对可能出现的短连接和长连接,设定不同的限制策略.     针对短连接,我们可以使用golang中的net包自带的timeout函数,一共有三个,分别是: 复制代码 代码如下: func (*IPCo

  • Go语言基于Socket编写服务器端与客户端通信的实例

    在golang中,网络协议已经被封装的非常完好了,想要写一个Socket的Server,我们并不用像其他语言那样需要为socket.bind.listen.receive等一系列操作头疼,只要使用Golang中自带的net包即可很方便的完成连接等操作~ 在这里,给出一个最最基础的基于Socket的Server的写法: 复制代码 代码如下: package main  import (      "fmt"      "net"      "log"

  • Go语言Echo服务器的方法

    本文实例讲述了Go语言Echo服务器的方法.分享给大家供大家参考.具体如下: 复制代码 代码如下: package main import (     "net"     "io" ) func main() {     serv, e := net.Listen("tcp", ":12345")     if e != nil {         panic(e)     }     defer serv.Close()  

  • Go语言实现socket实例

    本文实例讲述了Go语言实现socket的方法.分享给大家供大家参考.具体分析如下: 用golang不用他的net包还有什么意义,这里提供一个测试代码: server.go 服务端: 复制代码 代码如下: package main import (     "fmt"     "log"     "net"     "bufio" ) func handleConnection(conn net.Conn) {     data

随机推荐