最近在使用nginx+lua+redis做一个系统,来支撑高并发高访问量的应用。开发时突然想到golang是不是也可以达到同样的效果。于是写了个简单的代码对比一下。

具体就不多做介绍了,网上很多关于nginx+lua+redis构建高并发应用的介绍。我使用的是openresty+lua+redis。

先贴下测试结果,机器就是2013年新出的低配air——(1.3 GHz Intel Core i5, 4 GB 1600 MHz DDR3), 命令:

ab -n 1000 -c 100 http://localhost:8880/

::

openresty+lua+redis:

Concurrency Level:      100
Time taken for tests:   0.458 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      689000 bytes
HTML transferred:       533000 bytes
Requests per second:    2183.67 [#/sec] (mean)
Time per request:       45.794 [ms] (mean)
Time per request:       0.458 [ms] (mean, across all concurrent requests)
Transfer rate:          1469.29 [Kbytes/sec] received

::

golang+redis:

Concurrency Level:      100
Time taken for tests:   0.503 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      650000 bytes
HTML transferred:       532000 bytes
Requests per second:    1988.22 [#/sec] (mean)
Time per request:       50.296 [ms] (mean)
Time per request:       0.503 [ms] (mean, across all concurrent requests)
Transfer rate:          1262.05 [Kbytes/sec] received

lua代码:

.. code:: lua

-- redis 配置
local params = {
    host='127.0.0.1',
    port = 6379,
}

local red = redis:new()
local ok, err = red:connect(params.host, params.port)
if not ok then
    ngx.say("failed to connect: ", err)
    return
end

local position_key = ngx.var.position_key

local content = red:get(position_key)

ngx.print(content)

golang代码 :

.. code:: go

package main

import (
    "fmt"
    "github.com/garyburd/redigo/redis"
    "log"
    "net/http"
    "time"
)

func getConn() (redis.Conn, error) {
    conn, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
    if err != nil {
        fmt.Println(err)
    }
    return conn, err
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := getConn()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    result, err := conn.Do("get", "content_1")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "Hello, %q", result)
}

func main() {
    http.HandleFunc("/", indexHandler)
    err := http.ListenAndServe(":8880", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

经过多次压测之后发现,nginx + lua + redis的组合确实高效,golang + redis的方案其实也差不了多少。相对于整个系统从开发到部署的方式来说,golang可能更合适,更符合开发的习惯,毕竟nginx + lua 这种方案开发和测试都略显别扭。

补充连接池的使用和测试结果

上次测试完之后,觉得这个代码还有提高的空间,于是查了下怎么在golang中使用redis连接池(其实就是redigo的使用),还有lua中怎么使用redis连接池(其实就是rest.redis的使用)。

先上结果::

openresty + lua + redis

Concurrency Level:      100
Time taken for tests:   0.284 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      687000 bytes
HTML transferred:       531000 bytes
Requests per second:    3522.03 [#/sec] (mean)
Time per request:       28.393 [ms] (mean)
Time per request:       0.284 [ms] (mean, across all concurrent requests)
Transfer rate:          2362.93 [Kbytes/sec] received

再看golang::

golang + redis

Concurrency Level:      100
Time taken for tests:   0.327 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      650000 bytes
HTML transferred:       532000 bytes
Requests per second:    3058.52 [#/sec] (mean)
Time per request:       32.696 [ms] (mean)
Time per request:       0.327 [ms] (mean, across all concurrent requests)
Transfer rate:          1941.44 [Kbytes/sec] received

lua代码:

.. code:: lua

-- redis 配置
local params = {
    host='127.0.0.1',
    port = 6379,
}

local red = redis:new()
local ok, err = red:connect(params.host, params.port)
if not ok then
    ngx.say("failed to connect: ", err)
    return
end

local position_key = ngx.var.position_key

local content = red:get(position_key)

ngx.print(content)

local ok, err = red:set_keepalive(10000, 100)
if not ok then
    ngx.say("failed to set keepalive: ", err)
    return
end

golang代码:

.. code:: go

package main

import (
    "flag"
    "fmt"
    "github.com/garyburd/redigo/redis"
    "log"
    "net/http"
    "runtime"
    "time"
)

var (
    pool          *redis.Pool
    redisServer   = flag.String("redisServer", ":6379", "")
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
    t0 := time.Now()
    conn := pool.Get()
    t1 := time.Now()
    fmt.Printf("The call took %v to run.\n", t1.Sub(t0))
    defer conn.Close()
    result, err := conn.Do("get", "content_1")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "Hello, %q", result)
}
func newPool(server string) *redis.Pool {
    return &redis.Pool{
        MaxIdle:     3,
        IdleTimeout: 240 * time.Second,
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", server)
            if err != nil {
                return nil, err
            }
            return c, err
        },
        TestOnBorrow: func(c redis.Conn, t time.Time) error {
            _, err := c.Do("PING")
            return err
        },
    }
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    flag.Parse()
    pool = newPool(*redisServer)

    http.HandleFunc("/", indexHandler)
    err := http.ListenAndServe(":8880", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

golang中除了添加了线程池,还设置了cpu核数。

不过这个测试并不十分严谨,redis,nginx,golang http server,ab压测都在一台机器,相互之间会有影响。有兴趣的可以自己分开部署测试下。

- from the5fire.com