這篇文章主要著重討論如何透過ngx_lua同後端的memcached、redis進行非阻塞通訊。
1. Memcached
在Nginx中存取Memcached需要模組的支援,這裡選用HttpMemcModule,這個模組可以與後端的Memcached進行非阻塞的通訊。我們知道官方提供了Memcached,這個模組只支援get操作,而Memc支援大部分Memcached的指令。
Memc模組以入口變數作為參數傳遞,所有以$memc_為前綴的變數都是Memc的入口變數。 memc_pass指向後端的Memcached Server。
設定:
[plain] view
plaincopyprint?
- #使用HttpMemcModule
- location = /me {
- set $memc_key $arg_key;
- set $memc_value $
- set $memc_exptime $arg_exptime;
- 11';
- }
- 輸出:
- [plain]
plaincopyprint?
$ curl 'http://localhost/memc?cmd=set&key=foo&val=Hello' calhost/memc?cmd=get&key =foo'
$ Hello -
這實現了memcached的訪問,請看一下如何在lua中訪問memcached。
-
設定:
-
[plain] view
plaincopyprint?
#Lua中訪問Memcached location = /memc
set $memc_cmd get;
set $memc_key $ arg_key;
- memc_pass '127.0.0.1:11211';
-
content_by_lua '
-
local res = ngx.lo.capture("/cation local res = ngx.lo.capture("/cation.capture("/cation. memc", {
-
args = { key = ngx.var.
-
if res.status == 200 then
-
- end
- ';
-
[plain] view
plaincopyprint?
- $ curl 'http://localhost/lua_memc?key=foo'
- $Hello
-
$Hello
透過lua存取memcached,主要是透過子請求採用類似函數呼叫的方式來實現。首先,定義了一個memc location用於透過後端memcached通信,就相當於memcached storage。由於整個Memc模組時非阻塞的,ngx.location.capture也是非阻塞的,所以整個操作非阻塞。
2. Redis
訪問redis需要HttpRedis2Module的支持,它也可以與redis進行非阻塞通行。不過,redis2的回應是redis的原生回應,所以在lua中使用時,需要解析這個回應。可以採用LuaRedisModule,這個模組可以建構redis的原生請求,並解析redis的原生回應。
設定:
[plain] view
plaincopyprint?
- #在Lua中訪問Redis
- location = /redis {
- redis2_query get $arg_key;
- redis2_pass '127.0. 0.1:6379';
- }
- location = /lua_redis { #
- local parser = require("redis.parser") ngx.location.capture("/redis", {
-
args = })
-
if res.status == 200 then
= parser.parse_reply(res.body) ngx.say(reply) - ';
- }
- 輸出:
-
plaincopyprint?
-
$ curl 'http://localhost/lua_redis?key=foo'
-
,需要提供一個redis storage專門用於查詢redis,然後透過子請求去呼叫redis。
- 3. 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的原生請求查詢。 設定:
[plain] view
plaincopyprint?
- #在Lua中訪問Redis
-
location = /redis {
redis2_raw_queries $args $echo_request_body; '127.0.0.1:6379'; } content_by_lua 'conf/pipeline.lua'; }
-
.lua [plain] view
plaincopyprint?
- -- conf/pipeline.lua file
local parser = require('redis.parlocal parser
{'get', 'one' }, {'get', 'two'}
- }
- -- 構造原生的redis查詢,get onerngetcomq.
- for i, req in ipairs (reqs) do
- table.insert(raw_reqs, parser.build_query(req)) .location.capture('/redis?'..#reqs, {身體 = table.concat(raw_reqs, '') })
-
-- 解析redis的原生回應 local replies = parser. parse_replies(res.body, #reqs) for i, reply in ipairs( ngx.say(reply[1]) end end
- end
- end
-
end
end
- end 輸出:
- [plain] view
plaincopyprint?
$ curl 'http://localhost/pipeline'
$ 4. Connection Pool 前面造訪redis和memcached的例子中,在每次處理一個請求時,都會和後端的server建立連接,然後在請求處理完之後這個連接就會被釋放。這個過程中,會有3次握手、timewait等一些開銷,這對於高並發的應用是不可容忍的。這裡引入connection pool來消除這個開銷。 連接池需要HttpUpstreamKeepaliveModule模組的支援。
設定:- [plain] view
plaincopyprint?
- http {
- # 需要HttpUpstreamKeepaliveModule
- upstream redis_pool {
- server 127.0.0.1:6379;
- # 可以容納1024個連接的連接池
- keepalive 1024 single;
- }
- location = /redis {
- 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%的提高還是可以的。 -
5. 小結
- 這裡對memcached、redis的訪問做個小結。 1. Nginx提供了強大的程式設計模型,location相當於函數,子請求相當於函數調用,並且location還可以向自己發送子請求,這樣構成一個遞歸的模型,所以採用這種模型實現複雜的業務邏輯。
2. Nginx的IO操作必須是非阻塞的,如果Nginx在那阻著,則會大幅降低Nginx的效能。所以在Lua中必須透過ngx.location.capture發出子請求將這些IO操作委託給Nginx的事件模型。 3. 在需要使用tcp連接時,盡量使用連接池。這樣可以消除大量的建立、釋放連線的開銷。
以上就介紹了使用ngx_lua建立高並發應用,包括了方面的內容,希望對PHP教程有興趣的朋友有所幫助。