日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

第五章 常用Lua开发库1-redis、mysql、http客户端

發布時間:2024/9/20 数据库 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第五章 常用Lua开发库1-redis、mysql、http客户端 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

對于開發來說需要有好的生態開發庫來輔助我們快速開發,而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

Java代碼??
  • local?function?close_redis(red)??
  • ????if?not?red?then??
  • ????????return??
  • ????end??
  • ????local?ok,?err?=?red:close()??
  • ????if?not?ok?then??
  • ????????ngx.say("close?redis?error?:?",?err)??
  • ????end??
  • end??
  • ??
  • local?redis?=?require("resty.redis")??
  • ??
  • --創建實例??
  • local?red?=?redis:new()??
  • --設置超時(毫秒)??
  • red:set_timeout(1000)??
  • --建立連接??
  • local?ip?=?"127.0.0.1"??
  • local?port?=?6660??
  • local?ok,?err?=?red:connect(ip,?port)??
  • if?not?ok?then??
  • ????ngx.say("connect?to?redis?error?:?",?err)??
  • ????return?close_redis(red)??
  • end??
  • --調用API進行處理??
  • ok,?err?=?red:set("msg",?"hello?world")??
  • if?not?ok?then??
  • ????ngx.say("set?msg?error?:?",?err)??
  • ????return?close_redis(red)??
  • end??
  • ??
  • --調用API獲取數據??
  • local?resp,?err?=?red:get("msg")??
  • if?not?resp?then??
  • ????ngx.say("get?msg?error?:?",?err)??
  • ????return?close_redis(red)??
  • end??
  • --得到的數據為空處理??
  • if?resp?==?ngx.null?then??
  • ????resp?=?''??--比如默認值??
  • end??
  • ngx.say("msg?:?",?resp)??
  • ??
  • close_redis(red)??
  • 基本邏輯很簡單,要注意此處判斷是否為nil,需要跟ngx.null比較。

    ?

    2、example.conf配置文件

    Java代碼??
  • ?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函數改造為如下即可:?

    Java代碼??
  • local?function?close_redis(red)??
  • ????if?not?red?then??
  • ????????return??
  • ????end??
  • ????--釋放連接(連接池實現)??
  • ????local?pool_max_idle_time?=?10000?--毫秒??
  • ????local?pool_size?=?100?--連接池大小??
  • ????local?ok,?err?=?red:set_keepalive(pool_max_idle_time,?pool_size)??
  • ????if?not?ok?then??
  • ????????ngx.say("set?keepalive?error?:?",?err)??
  • ????end??
  • end??
  • 即設置空閑連接超時時間防止連接一直占用不釋放;設置連接池大小來復用連接。

    ?

    此處假設調用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、修改之前的代碼片段

    Java代碼??
  • red:init_pipeline()??
  • red:set("msg1",?"hello1")??
  • red:set("msg2",?"hello2")??
  • red:get("msg1")??
  • red:get("msg2")??
  • local?respTable,?err?=?red:commit_pipeline()??
  • ??
  • --得到的數據為空處理??
  • if?respTable?==?ngx.null?then??
  • ????respTable?=?{}??--比如默認值??
  • end??
  • ??
  • --結果是按照執行順序返回的一個table??
  • for?i,?v?in?ipairs(respTable)?do??
  • ???ngx.say("msg?:?",?v,?"<br/>")??
  • end??
  • 通過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:

    Java代碼??
  • local?resp,?err?=?red:eval("return?redis.call('get',?KEYS[1])",?1,?"msg");???
  • 2、script load然后evalsha??SHA1 校驗和,這樣可以節省腳本本身的服務器帶寬:

    Java代碼??
  • local?sha1,?err?=?red:script("load",??"return?redis.call('get',?KEYS[1])");??
  • if?not?sha1?then??
  • ???ngx.say("load?script?error?:?",?err)??
  • ???return?close_redis(red)??
  • end??
  • ngx.say("sha1?:?",?sha1,?"<br/>")??
  • local?resp,?err?=?red:evalsha(sha1,?1,?"msg");??
  • 首先通過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

    Java代碼??
  • local?function?close_db(db)??
  • ????if?not?db?then??
  • ????????return??
  • ????end??
  • ????db:close()??
  • end??
  • ??
  • local?mysql?=?require("resty.mysql")??
  • --創建實例??
  • local?db,?err?=?mysql:new()??
  • if?not?db?then??
  • ????ngx.say("new?mysql?error?:?",?err)??
  • ????return??
  • end??
  • --設置超時時間(毫秒)??
  • db:set_timeout(1000)??
  • ??
  • local?props?=?{??
  • ????host?=?"127.0.0.1",??
  • ????port?=?3306,??
  • ????database?=?"mysql",??
  • ????user?=?"root",??
  • ????password?=?"123456"??
  • }??
  • ??
  • local?res,?err,?errno,?sqlstate?=?db:connect(props)??
  • ??
  • if?not?res?then??
  • ???ngx.say("connect?to?mysql?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • --刪除表??
  • local?drop_table_sql?=?"drop?table?if?exists?test"??
  • res,?err,?errno,?sqlstate?=?db:query(drop_table_sql)??
  • if?not?res?then??
  • ???ngx.say("drop?table?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • --創建表??
  • local?create_table_sql?=?"create?table?test(id?int?primary?key?auto_increment,?ch?varchar(100))"??
  • res,?err,?errno,?sqlstate?=?db:query(create_table_sql)??
  • if?not?res?then??
  • ???ngx.say("create?table?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • --插入??
  • local?insert_sql?=?"insert?into?test?(ch)?values('hello')"??
  • res,?err,?errno,?sqlstate?=?db:query(insert_sql)??
  • if?not?res?then??
  • ???ngx.say("insert?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • res,?err,?errno,?sqlstate?=?db:query(insert_sql)??
  • ??
  • ngx.say("insert?rows?:?",?res.affected_rows,?"?,?id?:?",?res.insert_id,?"<br/>")??
  • ??
  • --更新??
  • local?update_sql?=?"update?test?set?ch?=?'hello2'?where?id?="?..?res.insert_id??
  • res,?err,?errno,?sqlstate?=?db:query(update_sql)??
  • if?not?res?then??
  • ???ngx.say("update?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • ngx.say("update?rows?:?",?res.affected_rows,?"<br/>")??
  • --查詢??
  • local?select_sql?=?"select?id,?ch?from?test"??
  • res,?err,?errno,?sqlstate?=?db:query(select_sql)??
  • if?not?res?then??
  • ???ngx.say("select?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • ??
  • for?i,?row?in?ipairs(res)?do??
  • ???for?name,?value?in?pairs(row)?do??
  • ?????ngx.say("select?row?",?i,?"?:?",?name,?"?=?",?value,?"<br/>")??
  • ???end??
  • end??
  • ??
  • ngx.say("<br/>")??
  • --防止sql注入??
  • local?ch_param?=?ngx.req.get_uri_args()["ch"]?or?''??
  • --使用ngx.quote_sql_str防止sql注入??
  • local?query_sql?=?"select?id,?ch?from?test?where?ch?=?"?..?ngx.quote_sql_str(ch_param)??
  • res,?err,?errno,?sqlstate?=?db:query(query_sql)??
  • if?not?res?then??
  • ???ngx.say("select?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • for?i,?row?in?ipairs(res)?do??
  • ???for?name,?value?in?pairs(row)?do??
  • ?????ngx.say("select?row?",?i,?"?:?",?name,?"?=?",?value,?"<br/>")??
  • ???end??
  • end??
  • ??
  • --刪除??
  • local?delete_sql?=?"delete?from?test"??
  • res,?err,?errno,?sqlstate?=?db:query(delete_sql)??
  • if?not?res?then??
  • ???ngx.say("delete?error?:?",?err,?"?,?errno?:?",?errno,?"?,?sqlstate?:?",?sqlstate)??
  • ???return?close_db(db)??
  • end??
  • ??
  • ngx.say("delete?rows?:?",?res.affected_rows,?"<br/>")??
  • ??
  • ??
  • close_db(db)??
  • ?

    對于新增/修改/刪除會返回如下格式的響應:

    Java代碼??
  • {??
  • ????insert_id?=?0,??
  • ????server_status?=?2,??
  • ????warning_count?=?1,??
  • ????affected_rows?=?32,??
  • ????message?=?nil??
  • }??
  • affected_rows表示操作影響的行數,insert_id是在使用自增序列時產生的id。

    ?

    對于查詢會返回如下格式的響應:

    Java代碼??
  • {??
  • ????{?id=?1,?ch=?"hello"},??
  • ????{?id=?2,?ch=?"hello2"}??
  • }??
  • null將返回ngx.null。

    ?

    2、example.conf配置文件

    Java代碼??
  • location?/lua_mysql?{??
  • ???default_type?'text/html';??
  • ???lua_code_cache?on;??
  • ???content_by_lua_file?/usr/example/lua/test_mysql.lua;??
  • }??
  • ?

    3、訪問如http://192.168.1.2/lua_mysql?ch=hello進行測試,得到如下結果

    Java代碼??
  • insert?rows?:?1?,?id?:?2??
  • update?rows?:?1??
  • select?row?1?:?ch?=?hello??
  • select?row?1?:?id?=?1??
  • select?row?2?:?ch?=?hello2??
  • select?row?2?:?id?=?2??
  • select?row?1?:?ch?=?hello??
  • select?row?1?:?id?=?1??
  • delete?rows?:?2??
  • 客戶端目前還沒有提供預編譯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?

    Java代碼??
  • 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

    Java代碼??
  • local?http?=?require("resty.http")??
  • --創建http客戶端實例??
  • local?httpc?=?http.new()??
  • ??
  • local?resp,?err?=?httpc:request_uri("http://s.taobao.com",?{??
  • ????method?=?"GET",??
  • ????path?=?"/search?q=hello",??
  • ????headers?=?{??
  • ????????["User-Agent"]?=?"Mozilla/5.0?(Windows?NT?6.1;?WOW64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/40.0.2214.111?Safari/537.36"??
  • ????}??
  • })??
  • ??
  • if?not?resp?then??
  • ????ngx.say("request?error?:",?err)??
  • ????return??
  • end??
  • ??
  • --獲取狀態碼??
  • ngx.status?=?resp.status??
  • ??
  • --獲取響應頭??
  • for?k,?v?in?pairs(resp.headers)?do??
  • ????if?k?~=?"Transfer-Encoding"?and?k?~=?"Connection"?then??
  • ????????ngx.header[k]?=?v??
  • ????end??
  • end??
  • --響應體??
  • ngx.say(resp.body)??
  • ??
  • httpc:close()??
  • ?

    響應頭中的Transfer-Encoding和Connection可以忽略,因為這個數據是當前server輸出的。

    ?

    3、example.conf配置文件

    Java代碼??
  • location?/lua_http_1?{??
  • ???default_type?'text/html';??
  • ???lua_code_cache?on;??
  • ???content_by_lua_file?/usr/example/lua/test_http_1.lua;??
  • }??
  • 4、在nginx.conf中的http部分添加如下指令來做DNS解析 Java代碼??
  • resolver?8.8.8.8;??
  • 記得要配置DNS解析器resolver 8.8.8.8,否則域名是無法解析的。 5、訪問如http://192.168.1.2/lua_http_1會看到淘寶的搜索界面。

    使用方式比較簡單,如超時和連接池設置和之前Redis客戶端一樣,不再闡述。更多客戶端使用規則請參考https://github.com/pintsized/lua-resty-http。

    ?

    ngx.location.capture

    ngx.location.capture也可以用來完成http請求,但是它只能請求到相對于當前nginx服務器的路徑,不能使用之前的絕對路徑進行訪問,但是我們可以配合nginx upstream實現我們想要的功能。

    ?

    1、在nginx.cong中的http部分添加如下upstream配置

    Java代碼??
  • upstream?backend?{??
  • ????server?s.taobao.com;??
  • ????keepalive?100;??
  • }??
  • 即我們將請求upstream到backend;另外記得一定要添加之前的DNS解析器。

    ?

    2、在example.conf配置如下location

    Java代碼??
  • location?~?/proxy/(.*)?{??
  • ???internal;??
  • ???proxy_pass?http://backend/$1$is_args$args;??
  • }??
  • internal表示只能內部訪問,即外部無法通過url訪問進來; 并通過proxy_pass將請求轉發到upstream。

    ?

    3、test_http_2.lua

    Java代碼??
  • local?resp?=?ngx.location.capture("/proxy/search",?{??
  • ????method?=?ngx.HTTP_GET,??
  • ????args?=?{q?=?"hello"}??
  • ??
  • })??
  • if?not?resp?then??
  • ????ngx.say("request?error?:",?err)??
  • ????return??
  • end??
  • ngx.log(ngx.ERR,?tostring(resp.status))??
  • ??
  • --獲取狀態碼??
  • ngx.status?=?resp.status??
  • ??
  • --獲取響應頭??
  • for?k,?v?in?pairs(resp.header)?do??
  • ????if?k?~=?"Transfer-Encoding"?and?k?~=?"Connection"?then??
  • ????????ngx.header[k]?=?v??
  • ????end??
  • end??
  • --響應體??
  • if?resp.body?then??
  • ????ngx.say(resp.body)??
  • end??
  • 通過ngx.location.capture發送一個子請求,此處因為是子請求,所有請求頭繼承自當前請求,還有如ngx.ctx和ngx.var是否繼承可以參考官方文檔http://wiki.nginx.org/HttpLuaModule#ngx.location.capture。?另外還提供了ngx.location.capture_multi用于并發發出多個請求,這樣總的響應時間是最慢的一個,批量調用時有用。

    ?

    4、example.conf配置文件

    Java代碼??
  • 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;來不傳遞請求頭。


    來源:http://jinnianshilongnian.iteye.com/blog/2187328

    總結

    以上是生活随笔為你收集整理的第五章 常用Lua开发库1-redis、mysql、http客户端的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。