對於開發來說需要有好的生態開發庫來輔助我們快速開發,而Lua中也有大多數我們需要的第三方開發庫如Redis、Memcached、Mysql、Http客戶端、JSON、模板引擎等。
一些常見的Lua庫可以在github上搜索,https://github.com/search?utf8=%E2%9C%93&q=lua+resty。
Redis客戶端
lua-resty-redis是為基於cosocket API的ngx_lua提供的Lua redis客戶端,通過它可以完成Redis的操作。默認安裝OpenResty時已經自帶了該模塊,使用文檔可參考https://github.com/openresty/lua-resty-redis。
在測試之前請啟動Redis實例:
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/servers/redis-2.8.19/redis_6660.conf &
1、基本操作
編輯test_redis_baisc.lua
基本邏輯很簡單,要注意此處判斷是否為nil,需要跟ngx.null比較。
2、example.conf配置文件
location /lua_redis_basic { default_type 'text/html'; lua_code_cache on; content_by_lua_file /usr/example/lua/test_redis_basic.lua; }
3、訪問如http://192.168.1.2/lua_redis_basic進行測試,正常情況得到如下信息
msg : hello world
2、連接池
建立TCP連接需要三次握手而釋放TCP連接需要四次握手,而這些往返時延僅需要一次,以后應該復用TCP連接,此時就可以考慮使用連接池,即連接池可以復用連接。
我們只需要將之前的close_redis函數改造為如下即可:
即設置空閑連接超時時間防止連接一直占用不釋放;設置連接池大小來復用連接。
此處假設調用red:set_keepalive(),連接池大小通過nginx.conf中http部分的如下指令定義:
#默認連接池大小,默認30
lua_socket_pool_size 30;
#默認超時時間,默認60s
lua_socket_keepalive_timeout 60s;
注意:
1、連接池是每Worker進程的,而不是每Server的;
2、當連接超過最大連接池大小時,會按照LRU算法回收空閑連接為新連接使用;
3、連接池中的空閑連接出現異常時會自動被移除;
4、連接池是通過ip和port標識的,即相同的ip和port會使用同一個連接池(即使是不同類型的客戶端如Redis、Memcached);
5、連接池第一次set_keepalive時連接池大小就確定下了,不會再變更;
5、cosocket的連接池http://wiki.nginx.org/HttpLuaModule#tcpsock:setkeepalive。
3、pipeline
pipeline即管道,可以理解為把多個命令打包然后一起發送;MTU(Maxitum Transmission Unit 最大傳輸單元)為二層包大小,一般為1500字節;而MSS(Maximum Segment Size 最大報文分段大小)為四層包大小,其一般是1500-20(IP報頭)-20(TCP報頭)=1460字節;因此假設我們執行的多個Redis命令能在一個報文中傳輸的話,可以減少網絡往返來提高速度。因此可以根據實際情況來選擇走pipeline模式將多個命令打包到一個報文發送然后接受響應,而Redis協議也能很簡單的識別和解決粘包。
1、修改之前的代碼片段
通過init_pipeline()初始化,然后通過commit_pipieline()打包提交init_pipeline()之后的Redis命令;返回結果是一個lua table,可以通過ipairs循環獲取結果;
2、配置相應location,測試得到的結果
msg : OK
msg : OK
msg : hello1
msg : hello2
3、Redis Lua腳本
利用Redis單線程特性,可以通過在Redis中執行Lua腳本實現一些原子操作。如之前的red:get("msg")可以通過如下兩種方式實現:
1、直接eval:
local resp, err = red:eval("return redis.call('get', KEYS[1])", 1, "msg");
2、script load然后evalsha SHA1 校驗和,這樣可以節省腳本本身的服務器帶寬:
首先通過script load導入腳本並得到一個sha1校驗和(僅需第一次導入即可),然后通過evalsha執行sha1校驗和即可,這樣如果腳本很長通過這種方式可以減少帶寬的消耗。
此處僅介紹了最簡單的redis lua腳本,更復雜的請參考官方文檔學習使用。
另外Redis集群分片算法該客戶端沒有提供需要自己實現,當然可以考慮直接使用類似於Twemproxy這種中間件實現。
Memcached客戶端使用方式和本文類似,本文就不介紹了。
Mysql客戶端
lua-resty-mysql是為基於cosocket API的ngx_lua提供的Lua Mysql客戶端,通過它可以完成Mysql的操作。默認安裝OpenResty時已經自帶了該模塊,使用文檔可參考https://github.com/openresty/lua-resty-mysql。
1、編輯test_mysql.lua
對於新增/修改/刪除會返回如下格式的響應:
affected_rows表示操作影響的行數,insert_id是在使用自增序列時產生的id。
對於查詢會返回如下格式的響應:
null將返回ngx.null。
2、example.conf配置文件
3、訪問如http://192.168.1.2/lua_mysql?ch=hello進行測試,得到如下結果
客戶端目前還沒有提供預編譯SQL支持(即占位符替換位置變量),這樣在入參時記得使用ngx.quote_sql_str進行字符串轉義,防止sql注入;連接池和之前Redis客戶端完全一樣就不介紹了。
對於Mysql客戶端的介紹基本夠用了,更多請參考https://github.com/openresty/lua-resty-mysql。
其他如MongoDB等數據庫的客戶端可以從github上查找使用。
Http客戶端
OpenResty默認沒有提供Http客戶端,需要使用第三方提供;當然我們可以通過ngx.location.capture 去方式實現,但是有一些限制,后邊我們再做介紹。
我們可以從github上搜索相應的客戶端,比如https://github.com/pintsized/lua-resty-http。
lua-resty-http
1、下載lua-resty-http客戶端到lualib
cd /usr/example/lualib/resty/ wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua
2、test_http_1.lua
響應頭中的Transfer-Encoding和Connection可以忽略,因為這個數據是當前server輸出的。
3、example.conf配置文件
resolver 8.8.8.8;
使用方式比較簡單,如超時和連接池設置和之前Redis客戶端一樣,不再闡述。更多客戶端使用規則請參考https://github.com/pintsized/lua-resty-http。
ngx.location.capture
ngx.location.capture也可以用來完成http請求,但是它只能請求到相對於當前nginx服務器的路徑,不能使用之前的絕對路徑進行訪問,但是我們可以配合nginx upstream實現我們想要的功能。
1、在nginx.cong中的http部分添加如下upstream配置
2、在example.conf配置如下location
internal表示只能內部訪問,即外部無法通過url訪問進來; 並通過proxy_pass將請求轉發到upstream。
3、test_http_2.lua
通過ngx.location.capture發送一個子請求,此處因為是子請求,所有請求頭繼承自當前請求,還有如ngx.ctx和ngx.var是否繼承可以參考官方文檔http://wiki.nginx.org/HttpLuaModule#ngx.location.capture。 另外還提供了ngx.location.capture_multi用於並發發出多個請求,這樣總的響應時間是最慢的一個,批量調用時有用。
4、example.conf配置文件
location /lua_http_2 { default_type 'text/html'; lua_code_cache on; content_by_lua_file /usr/example/lua/test_http_2.lua; }
5、訪問如http://192.168.1.2/lua_http_2進行測試可以看到淘寶搜索界面。
我們通過upstream+ngx.location.capture方式雖然麻煩點,但是得到更好的性能和upstream的連接池、負載均衡、故障轉移、proxy cache等特性。
不過因為繼承在當前請求的請求頭,所以可能會存在一些問題,比較常見的就是gzip壓縮問題,ngx.location.capture不會解壓縮后端服務器的GZIP內容,解決辦法可以參考https://github.com/openresty/lua-nginx-module/issues/12;因為我們大部分這種http調用的都是內部服務,因此完全可以在proxy location中添加proxy_pass_request_headers off;來不傳遞請求頭。
第二部分:
常用Lua開發庫2-JSON庫、編碼轉換、字符串處理
https://www.iteye.com/blog/jinnianshilongnian-2187643
原文轉載:https://www.iteye.com/blog/jinnianshilongnian-2187643
作者:jinnianshilongnian
