1. 程式人生 > >用lua擴充套件你的Nginx(整理)

用lua擴充套件你的Nginx(整理)

首先得宣告,這不是我的原創,是在網上搜索到的一篇文章,原著是誰也搞不清楚了,按風格應該是屬於章亦春的文章。

整理花了不少時間,所以就暫寫成原創吧。

一. 概述

Nginx是一個高效能,支援高併發的,輕量級的web伺服器。目前,Apache依然web伺服器中的老大,但是在全球前1000大的web伺服器中,Nginx的份額為22.4%。Nginx採用模組化的架構,官方版本的Nginx中大部分功能都是通過模組方式提供的,比如Http模組、Mail模組等。通過開發模組擴充套件Nginx,可以將Nginx打造成一個全能的應用伺服器,這樣可以將一些功能在前端Nginx反向代理層解決,比如登入校驗、js合併、甚至資料庫訪問等等。     但是,Nginx模組需要用C開發,而且必須符合一系列複雜的規則,最重要的用C開發模組必須要熟悉Nginx的原始碼,使得開發者對其望而生畏。淘寶的agentzh和chaoslawful開發的ngx_lua模組通過將lua直譯器整合進Nginx,可以採用lua指令碼實現業務邏輯,由於lua的緊湊、快速以及內建協程,所以在保證高併發服務能力的同時極大地降低了業務邏輯實現成本。     本文向大家介紹ngx_lua,以及我在使用它開發專案的過程中遇到的一些問題。

二. 準備

首先,介紹一下Nginx的一些特性,便於後文介紹ngx_lua的相關特性。

Nginx程序模型

Nginx採用多程序模型,單Master—多Worker,由Master處理外部訊號、配置檔案的讀取及Worker的初始化,Worker程序採用單執行緒、非阻塞的事件模型(Event Loop,事件迴圈)來實現埠的監聽及客戶端請求的處理和響應,同時Worker還要處理來自Master的訊號。由於Worker使用單執行緒處理各種事件,所以一定要保證主迴圈是非阻塞的,否則會大大降低Worker的響應能力。

Nginx處理Http請求的過程

表面上看,當Nginx處理一個來自客戶端的請求時,先根據請求頭的host、ip和port來確定由哪個server處理,確定了server之後,再根據請求的uri找到對應的location,這個請求就由這個location處理。實際Nginx將一個請求的處理劃分為若干個不同階段(phase),這些階段按照前後順序依次執行,也就是說NGX_HTTP_POST_READ_PHASE在第一個,NGX_HTTP_LOG_PHASE在最後一個。
<span style="font-size:10px;">NGX_HTTP_POST_READ_PHASE,     //0讀取請求phase
NGX_HTTP_SERVER_REWRITE_PHASE,//1這個階段主要是處理全域性的(server block)的rewrite
NGX_HTTP_FIND_CONFIG_PHASE,   //2這個階段主要是通過uri來查詢對應的location,然後根據loc_conf設定r的相應變數
NGX_HTTP_REWRITE_PHASE,       //3這個主要處理location的rewrite
NGX_HTTP_POST_REWRITE_PHASE,  //4postrewrite,這個主要是進行一些校驗以及收尾工作,以便於交給後面的模組。
NGX_HTTP_PREACCESS_PHASE,     //5比如流控這種型別的access就放在這個phase,也就是說它主要是進行一些比較粗粒度的access。
NGX_HTTP_ACCESS_PHASE,        //6這個比如存取控制,許可權驗證就放在這個phase,一般來說處理動作是交給下面的模組做的.這個主要是做一些細粒度的access
NGX_HTTP_POST_ACCESS_PHASE,   //7一般來說當上面的access模組得到access_code之後就會由這個模組根據access_code來進行操作
NGX_HTTP_TRY_FILES_PHASE,     //8try_file模組,就是對應配置檔案中的try_files指令,可接收多個路徑作為引數,當前一個路徑的資源無法找到,則自動查詢下一個路徑   
NGX_HTTP_CONTENT_PHASE,       //9內容處理模組   
NGX_HTTP_LOG_PHASE            //10log模組

每個階段上可以註冊handler,處理請求就是執行每個階段上註冊的handler。Nginx模組提供的配置指令只會一般只會註冊並執行在其中的某一個處理階段。

比如,set指令屬於rewrite模組的,執行在rewrite階段,deny和allow執行在access階段。


子請求(subrequest)

其實在Nginx 世界裡有兩種型別的“請求”,一種叫做“主請求”(main request),而另一種則叫做“子請求”(subrequest)。 所謂“主請求”,就是由 HTTP 客戶端從 Nginx 外部發起的請求。比如,從瀏覽器訪問Nginx就是一個“主請求”。 而“子請求”則是由 Nginx 正在處理的請求在 Nginx 內部發起的一種級聯請求。“子請求”在外觀上很像 HTTP 請求,但實現上卻和 HTTP 協議乃至網路通訊一點兒關係都沒有。它是 Nginx 內部的一種抽象呼叫,目的是為了方便使用者把“主請求”的任務分解為多個較小粒度的“內部請求”,併發或序列地訪問多個 location 介面,然後由這些 location 介面通力協作,共同完成整個“主請求”。當然,“子請求”的概念是相對的,任何一個“子請求”也可以再發起更多的“子子請求”,甚至可以玩遞迴呼叫(即自己呼叫自己)。

當一個請求發起一個“子請求”的時候,按照 Nginx 的術語,習慣把前者稱為後者的“父請求”(parent request)。

location /main {
    echo_location /foo;     # echo_location傳送子請求到指定的location
    echo_location /bar;
}
location /foo {
    echo foo;
}
location /bar {
    echo bar;
}

輸出:

$ curl location/main
$ foo   03.  bar

這裡,main location就是傳送2個子請求,分別到foo和bar,這就類似一種函式呼叫。

“子請求”方式的通訊是在同一個虛擬主機內部進行的,所以 Nginx 核心在實現“子請求”的時候,就只調用了若干個 C 函式,完全不涉及任何網路或者 UNIX 套接字(socket)通訊。我們由此可以看出“子請求”的執行效率是極高的。

協程(Coroutine)

協程類似一種多執行緒,與多執行緒的區別有: 

1. 協程並非os執行緒,所以建立、切換開銷比執行緒相對要小。 

2. 協程與執行緒一樣有自己的棧、區域性變數等,但是協程的棧是在使用者程序空間模擬的,所以建立、切換開銷很小。

3. 多執行緒程式是多個執行緒併發執行,也就是說在一瞬間有多個控制流在執行。而協程強調的是一種多個協程間協作的關係,只有當一個協程主動放棄執行權,另一個協程才能獲得執行權,所以在某一瞬間,多個協程間只有一個在執行。 

4. 由於多個協程時只有一個在執行,所以對於臨界區的訪問不需要加鎖,而多執行緒的情況則必須加鎖。 

5. 多執行緒程式由於有多個控制流,所以程式的行為不可控,而多個協程的執行是由開發者定義的所以是可控的。 

Nginx的每個Worker程序都是在epoll或kqueue這樣的事件模型之上,封裝成協程,每個請求都有一個協程進行處理。這正好與Lua內建協程的模型是一致的,所以即使ngx_lua需要執行Lua,相對C有一定的開銷,但依然能保證高併發能力。

三. ngx_lua

原理
ngx_lua將Lua嵌入Nginx,可以讓Nginx執行Lua指令碼,並且高併發、非阻塞的處理各種請求。Lua內建協程,這樣就可以很好的將非同步回撥轉換成順序呼叫的形式。ngx_lua在Lua中進行的IO操作都會委託給Nginx的事件模型,從而實現非阻塞呼叫。開發者可以採用序列的方式編寫程式,ngx_lua會自動的在進行阻塞的IO操作時中斷,儲存上下文;然後將IO操作委託給Nginx事件處理機制,在IO操作完成後,ngx_lua會恢復上下文,程式繼續執行,這些操作都是對使用者程式透明的。 每個NginxWorker程序持有一個Lua直譯器或者LuaJIT例項,被這個Worker處理的所有請求共享這個例項。每個請求的Context會被Lua輕量級的協程分割,從而保證各個請求是獨立的。 ngx_lua採用“one-coroutine-per-request”的處理模型,對於每個使用者請求,ngx_lua會喚醒一個協程用於執行使用者程式碼處理請求,當請求處理完成這個協程會被銷燬。每個協程都有一個獨立的全域性環境(變數空間),繼承於全域性共享的、只讀的“comman data”。所以,被使用者程式碼注入全域性空間的任何變數都不會影響其他請求的處理,並且這些變數在請求處理完成後會被釋放,這樣就保證所有的使用者程式碼都執行在一個“sandbox”(沙箱),這個沙箱與請求具有相同的生命週期。 得益於Lua協程的支援,ngx_lua在處理10000個併發請求時只需要很少的記憶體。根據測試,ngx_lua處理每個請求只需要2KB的記憶體,如果使用LuaJIT則會更少。所以ngx_lua非常適合用於實現可擴充套件的、高併發的服務。

典型應用

官網上列出: 

· Mashup’ing and processing outputs of various nginx upstream outputs(proxy, drizzle, postgres, redis, memcached, and etc) in Lua, 
· doing arbitrarily complex access control and security checks in Luabefore requests actually reach the upstream backends, 
· manipulating response headers in an arbitrary way (by Lua) 
· fetching backend information from external storage backends (likeredis, memcached, mysql, postgresql) and use that information to choose whichupstream backend to access on-the-fly, 
· coding up arbitrarily complex web applications in a content handlerusing synchronous but still non-blocking access to the database backends andother storage, 
· doing very complex URL dispatch in Lua at rewrite phase, 
· using Lua to implement advanced caching mechanism for nginxsubrequests and arbitrary locations.
Hello Lua!
# nginx.conf
worker_processes 4;

events {
     worker_connections 1024;
}
http {

    server {
        listen 80;
        server_name localhost;

        location=/lua {
            content_by_lua ‘
                ngx.say("Hello, Lua!")
            ';
        }
    }
}
輸出:
$ curl 'localhost/lua'
Hello,Lua!

這樣就實現了一個很簡單的ngx_lua應用,如果這麼簡單的模組要是用C來開發的話,程式碼量估計得有100行左右,從這就可以看出ngx_lua的開發效率。

Benchmark
通過和nginx訪問靜態檔案還有nodejs比較,來看一下ngx_lua提供的高併發能力。 返回的內容都是”Hello World!”,151bytes 通過.ab -n 60000   取10次平均
從圖表中可以看到,在各種併發條件下ngx_lua的rps都是最高的,並且基本維持在10000rps左右,nginx讀取靜態檔案因為會有磁碟io所以效能略差一些,而nodejs是相對最差的。通過這個簡單的測試,可以看出ngx_lua的高併發能力。 ngx_lua的開發者也做過一個測試對比nginx+fpm+php和nodejs,他得出的結果是ngx_lua可以達到28000rps,而nodejs有10000多一點,php則最差只有6000。可能是有些配置我沒有配好導致ngx_lua rps沒那麼高。

ngx_lua安裝

ngx_lua安裝可以通過下載模組原始碼,編譯Nginx,但是推薦採用openresty。Openresty就是一個打包程式,包含大量的第三方Nginx模組,比如HttpLuaModule,HttpRedis2Module,HttpEchoModule等。省去下載模組,並且安裝非常方便。 ngx_openresty bundle: openresty ./configure --with-luajit&& make && make install 預設Openresty中ngx_lua模組採用的是標準的Lua5.1直譯器,通過--with-luajit使用LuaJIT。
ngx_lua的用法
ngx_lua模組提供了配置指令和Nginx API。 配置指令:在Nginx中使用,和set指令和pass_proxy指令使用方法一樣,每個指令都有使用的context。        Nginx API:用於在Lua指令碼中訪問Nginx變數,呼叫Nginx提供的函式。 下面舉例說明常見的指令和API。

配置指令

set_by_lua和set_by_lua_file

和set指令一樣用於設定Nginx變數並且在rewrite階段執行,只不過這個變數是由lua指令碼計算並返回的。
語法:set_by_lua$res <lua-script-str> [$arg1 $arg2 ...]

配置:

location =/adder {
    set_by_lua $res"
            local a = tonumber(ngx.arg[1])
                local b = tonumber(ngx.arg[2])
                return a + b"$arg_a$arg_b;

        echo$res;
}
輸出:
$ curl 'localhost/adder?a=25&b=75'
$ 100

set_by_lua_file執行Nginx外部的lua指令碼,可以避免在配置檔案中使用大量的轉義。

配置:

location =/fib {
        set_by_lua_file $res "conf/adder.lua" $arg_n;

        echo $res;
}</span>

adder.lua:

local a=tonumber(ngx.arg[1])
local b=tonumber(ngx.arg[2])
return a + b

輸出:
$ curl 'localhost/adder?a=25&b=75
$ 100

access_by_lua和access_by_lua_file

執行在access階段,用於訪問控制。Nginx原生的allow和deny是基於ip的,通過access_by_lua能完成複雜的訪問控制,比如,訪問資料庫進行使用者名稱、密碼驗證等。

配置:

location /auth {
    access_by_lua '
        if ngx.var.arg_user == "ntes" then
            return
        else
            Ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    ';
    echo'welcome ntes';
}
輸出:
$ curl 'localhost/auth?user=sohu'
$ Welcome ntes

$ curl 'localhost/auth?user=ntes'
$ <html>
<head><title>403 Forbidden</title></heda>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>ngx_openresty/1.0.10.48</center>
</body>
</html>

rewrite_by_lua和rewrite_by_lua_file

實現url重寫,在rewrite階段執行。

配置:
location =/foo {
        rewrite_by_lua 'ngx.exec("/bar")';
    echo'in foo';
}

location =/bar {
        echo'in bar';
}
輸出:
$ curl 'localhost/lua'
$ Hello, Lua!

content_by_lua和content_by_lua_file

Contenthandler在content階段執行,生成http響應。由於content階段只能有一個handler,所以在與echo模組使用時,不能同時生效,我測試的結果是content_by_lua會覆蓋echo。這和之前的hello world的例子是類似的。

配置(直接響應):
location =/lua {
        content_by_lua 'ngx.say("Hello, Lua!")';
}

輸出:
$ curl 'localhost/lua'
$ Hello, Lua!

配置(在Lua中訪問Nginx變數):
location =/hello {
        content_by_lua '
            local who = ngx.var.arg_who
            ngx.say("Hello, ", who, "!")
        ';
}

輸出:
$ curl 'localhost/hello?who=world
$ Hello, world!

Nginx API
Nginx API被封裝ngx和ndk兩個package中。比如ngx.var.NGX_VAR_NAME可以訪問Nginx變數。這裡著重介紹一下ngx.location.capture和ngx.location.capture_multi。

ngx.location.capture
語法:res= ngx.location.capture(uri, options?)     用於發出一個同步的,非阻塞的Nginxsubrequest(子請求)。可以通過Nginx subrequest向其它location發出非阻塞的內部請求,這些location可以是配置用於讀取資料夾的,也可以是其它的C模組,比如ngx_proxy, ngx_fastcgi, ngx_memc, ngx_postgres, ngx_drizzle甚至是ngx_lua自己。     Subrequest只是模擬Http介面,並沒有額外的Http或者Tcp傳輸開銷,它在C層次上執行,非常高效。Subrequest不同於Http 301/302重定向,以及內部重定向(通過ngx.redirection)。

配置:
location =/other {
    ehco 'Hello, world!';
}

# Lua非阻塞IO
location =/lua {
    content_by_lua '
        local res = ngx.location.capture("/other")
        if res.status == 200 then
            ngx.print(res.body)
        end
    ';
}

輸出:
$ curl  'http://localhost/lua'
$ Hello, world!

實際上,location可以被外部的Http請求呼叫,也可以被內部的子請求呼叫。每個location相當於一個函式,而傳送子請求就類似於函式呼叫,而且這種呼叫是非阻塞的,這就構造了一個非常強大的變成模型,後面我們會看到如何通過location和後端的memcached、redis進行非阻塞通訊。
ngx.location.capture_multi

語法:res1,res2, ... = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, ...})     與ngx.location.capture功能一樣,可以並行的、非阻塞的發出多個子請求。這個方法在所有子請求處理完成後返回,並且整個方法的執行時間取決於執行時間最長的子請求,並不是所有子請求的執行時間之和。

配置:
# 同時傳送多個子請求(subrequest)
location =/moon {
    ehco 'moon';
}
location =/earth {
    ehco 'earth';
}

location =/lua {
    content_by_lua '
        local res1,res2 = ngx.location.capture_multi({ {"/moon"}, {"earth"} })
        if res1.status == 200 then
            ngx.print(res1.body)
        end
        ngx.print(",")
        if res2.status == 200 then
            ngx.print(res2.body)
        end
    ';
}


輸出:
$ curl  'http://localhost/lua'
$ moon,earth

注意
在Lua程式碼中的網路IO操作只能通過Nginx Lua API完成,如果通過標準Lua API會導致Nginx的事件迴圈被阻塞,這樣效能會急劇下降。     在進行資料量相當小的磁碟IO時可以採用標準Lua io庫,但是當讀寫大檔案時這樣是不行的,因為會阻塞整個NginxWorker程序。為了獲得更大的效能,強烈建議將所有的網路IO和磁碟IO委託給Nginx子請求完成(通過ngx.location.capture)。     下面通過訪問/html/index.html這個檔案,來測試將磁碟IO委託給Nginx和通過Lua io直接訪問的效率。     通過ngx.location.capture委託磁碟IO:

配置:

location / {
    internal;
    root html;
}

location /capture {
    content_by_lua '
        res = ngx.location.capture("/")
        echo res.body
    ';
}
通過標準lua io訪問磁碟檔案:

配置:
location /luaio{
    content_by_lua '
        local io = require("io")
        local chunk_SIZE = 4096
        local f = assert(io.open("html/index.html","r"))
        while true do
            local chunk = f:read(chunk)
            if not chunk then
                break
            end
            ngx.print(chunk)
            ngx.flush(true)
        end
        f:close()
    ';
}

這裡通過ab去壓,在各種併發條件下,分別返回151bytes、151000bytes的資料,取10次平均,得到兩種方式的rps。     靜態檔案:151bytes
1000 3000 5000 7000 10000  capture  11067 8880 8873 8952 9023  Lua io     11379 9724 8938 9705 9561

靜態檔案:151000bytes,在10000併發下記憶體佔用情況太嚴重,測不出結果        這種情況下,檔案較小,通過Nginx訪問靜態檔案需要額外的系統呼叫,效能略遜於ngx_lua。
1000 3000 5000 7000    10000  capture    3338 3435 3178 3043         /  Lua io      3174 3094 3081 2916         /

在大檔案的情況,capture就要略好於ngx_lua。      這裡沒有對Nginx讀取靜態檔案進行優化配置,只是採用了sendfile。如果優化一下,可能nginx讀取靜態檔案的效能會更好一些,這個目前還不熟悉。所以,在Lua中進行各種IO時,都要通過ngx.location.capture傳送子請求委託給Nginx事件模型,這樣可以保證IO是非阻塞的。

四. 小結

這篇文章簡單介紹了一下ngx_lua的基本用法,後一篇會對ngx_lua訪問redis、memcached已經連線池進行詳細介紹。

五. 進階

在之前的文章中,已經介紹了ngx_lua的一些基本介紹,這篇文章主要著重討論一下如何通過ngx_lua同後端的memcached、redis進行非阻塞通訊。

Memcached

在Nginx中訪問Memcached需要模組的支援,這裡選用HttpMemcModule,這個模組可以與後端的Memcached進行非阻塞的通訊。我們知道官方提供了Memcached,這個模組只支援get操作,而Memc支援大部分Memcached的命令。 Memc模組採用入口變數作為引數進行傳遞,所有以$memc_為字首的變數都是Memc的入口變數。memc_pass指向後端的Memcached Server。

配置:
#使用HttpMemcModule
location =/memc {
    set $memc_cmd $arg_cmd;
    set $memc_key $arg_key;
    set $memc_value $arg_val;
    set $memc_exptime $arg_exptime;

    memc_pass '127.0.0.1:11211';
}
輸出:
$ curl  'http://localhost/memc?cmd=set&key=foo&val=Hello'
$ STORED
$ curl  'http://localhost/memc?cmd=get&key=foo'
$ Hello

這就實現了memcached的訪問,下面看一下如何在lua中訪問memcached。

配置:
#在Lua中訪問Memcached
location =/memc {
    internal;   #只能內部訪問
    set $memc_cmd get;
    set $memc_key $arg_key;
    memc_pass '127.0.0.1:11211';
}
location =/lua_memc {
    content_by_lua '
        local res = ngx.location.capture("/memc", {
            args = { key = ngx.var.arg_key }
        })
        if res.status == 200 then
            ngx.say(res.body)
        end
    ';
}

輸出:
$ curl  'http://localhost/lua_memc?key=foo'
$ Hello

通過lua訪問memcached,主要是通過子請求採用一種類似函式呼叫的方式實現。首先,定義了一個memc location用於通過後端memcached通訊,就相當於memcached storage。由於整個Memc模組時非阻塞的,ngx.location.capture也是非阻塞的,所以整個操作非阻塞。

Redis

訪問redis需要HttpRedis2Module的支援,它也可以同redis進行非阻塞通行。不過,redis2的響應是redis的原生響應,所以在lua中使用時,需要解析這個響應。可以採用LuaRedisModule,這個模組可以構建redis的原生請求,並解析redis的原生響應。

配置:
#在Lua中訪問Redis
location =/redis {
    internal;   #只能內部訪問
    redis2_query get $arg_key;
    redis2_pass '127.0.0.1:6379';
}
location =/lua_redis {#需要LuaRedisParser
    content_by_lua '
        local parser = require("redis.parser")
        local res = ngx.location.capture("/redis", {
            args = { key = ngx.var.arg_key }
        })
        if res.status == 200 then
            reply = parser.parse_reply(res.body)
            ngx.say(reply)
        end
    ';
}

輸出:
$ curl  'http://localhost/lua_redis?key=foo'
$ Hello

和訪問memcached類似,需要提供一個redis storage專門用於查詢redis,然後通過子請求去呼叫redis。

Redis Pipeline
在實際訪問redis時,有可能需要同時查詢多個key的情況。我們可以採用ngx.location.capture_multi通過傳送多個子請求給redis storage,然後在解析響應內容。但是,這會有個限制,Nginx核心規定一次可以發起的子請求的個數不能超過50個,所以在key個數多於50時,這種方案不再適用。
幸好redis提供pipeline機制,可以在一次連線中執行多個命令,這樣可以減少多次執行命令的往返時延。客戶端在通過pipeline傳送多個命令後,redis順序接收這些命令並執行,然後按照順序把命令的結果輸出出去。在lua中使用pipeline需要用到redis2模組的redis2_raw_queries進行redis的原生請求查詢。

配置:

#在Lua中訪問Redis
location =/redis {
    internal;   #只能內部訪問
    redis2_raw_queries $args$echo_request_body;
    redis2_pass '127.0.0.1:6379';
}

location =/pipeline {
    content_by_lua 'conf/pipeline.lua';
}

pipeline.lua

-- conf/pipeline.lua file
local parser=require(‘redis.parser’)
local reqs={
    {‘get’, ‘one’}, {‘get’, ‘two’}
}
-- 構造原生的redis查詢,get one\r\nget two\r\n
local raw_reqs={}
for i, req in ipairs(reqs)do
      table.insert(raw_reqs, parser.build_query(req))
end
local res=ngx.location.capture(‘/redis?’..#reqs, {body=table.concat(raw_reqs, ‘’)})

if res.status and res.body then
       -- 解析redis的原生響應
       local replies=parser.parse_replies(res.body, #reqs)
       for i, reply in ipairs(replies)do
          ngx.say(reply[1])
       end
end

輸出:
$ curl  'http://localhost/pipeline'
$ first
  second

Connection Pool

前面訪問redis和memcached的例子中,在每次處理一個請求時,都會和後端的server建立連線,然後在請求處理完之後這個連線就會被釋放。這個過程中,會有3次握手、timewait等一些開銷,這對於高併發的應用是不可容忍的。這裡引入connection pool來消除這個開銷。 連線池需要HttpUpstreamKeepaliveModule模組的支援。

配置:
http {
    # 需要HttpUpstreamKeepaliveModule
    upstream redis_pool {
        server 127.0.0.1:6379;
        # 可以容納1024個連線的連線池
        keepalive 1024 single;
    }

    server {
        location=/redis {
            …
            redis2_pass redis_pool;
        }
    }
}

這個模組提供keepalive指令,它的context是upstream。我們知道upstream在使用Nginx做反向代理時使用,實際upstream是指“上游”,這個“上游”可以是redis、memcached或是mysql等一些server。upstream可以定義一個虛擬server叢集,並且這些後端的server可以享受負載均衡。keepalive 1024就是定義連線池的大小,當連線數超過這個大小後,後續的連線自動退化為短連線。連線池的使用很簡單,直接替換掉原來的ip和埠號即可。      有人曾經測過,在沒有使用連線池的情況下,訪問memcached(使用之前的Memc模組),rps為20000。在使用連線池之後,rps一路飆到140000。在實際情況下,這麼大的提升可能達不到,但是基本上100-200%的提高還是可以的。

小結

這裡對memcached、redis的訪問做個小結。 1. Nginx提供了強大的程式設計模型,location相當於函式,子請求相當於函式呼叫,並且location還可以向自己傳送子請求,這樣構成一個遞迴的模型,所以採用這種模型實現複雜的業務邏輯。 2. Nginx的IO操作必須是非阻塞的,如果Nginx在那阻著,則會大大降低Nginx的效能。所以在Lua中必須通過ngx.location.capture發出子請求將這些IO操作委託給Nginx的事件模型。 3. 在需要使用tcp連線時,儘量使用連線池。這樣可以消除大量的建立、釋放連線的開銷。

參考:


原文: