1. 程式人生 > >常用Lua開發庫redis、mysql、http客戶端

常用Lua開發庫redis、mysql、http客戶端

文章來源:

前言

對於開發來說需要有好的生態開發庫來輔助我們快速開發,而Lua中也有大多數我們需要的第三方開發庫如Redis、Memcached、Mysql、Http客戶端、JSON、模板引擎等。一些常見的Lua庫可以在github上搜索,下面給出連結:

一丶Redis客戶端

lua-resty-redis是為基於cosocket API的ngx_lua提供的Lua redis客戶端,通過它可以完成Redis的操作。預設安裝OpenResty時已經自帶了該模組,使用文件可參考:

在測試之前請啟動Redis例項:

nohup /usr/servers/redis-2.8.19
/src/redis-server /usr/servers/redis-2.8.19/redis_6660.conf &

1、基本操作

(1) 編輯 test_redis_baisc.lua

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配置檔案

location /lua_redis_basic {  
    default_type 'text/html';  
    lua_code_cache on;  
    content_by_lua_file /usr/example/lua/test_redis_basic.lua;  
}  
msg : hello world

2、連線池

建立TCP連線需要三次握手而釋放TCP連線需要四次握手,而這些往返時延僅需要一次,以後應該複用TCP連線,此時就可以考慮使用連線池,即連線池可以複用連線。我們只需要將之前的close_redis函式改造為如下即可:

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時連線池大小就確定下了,不會再變更;

3、pipeline

pipeline即管道,可以理解為把多個命令打包然後一起傳送;MTU(Maxitum Transmission Unit 最大傳輸單元)為二層包大小,一般為1500位元組;而MSS(Maximum Segment Size 最大報文分段大小)為四層包大小,其一般是1500-20(IP報頭)-20(TCP報頭)=1460位元組;因此假設我們執行的多個Redis命令能在一個報文中傳輸的話,可以減少網路往返來提高速度。因此可以根據實際情況來選擇走pipeline模式將多個命令打包到一個報文傳送然後接受響應,而Redis協議也能很簡單的識別和解決粘包。

(1) 修改之前的程式碼片段

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:

local resp, err = red:eval("return redis.call('get', KEYS[1])", 1, "msg");   

2、script load然後evalsha SHA1 校驗和,這樣可以節省指令碼本身的伺服器頻寬:

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時已經自帶了該模組,使用文件可參考:

1、編輯test_mysql.lua

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)  

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

{  
    insert_id = 0,  
    server_status = 2,  
    warning_count = 1,  
    affected_rows = 32,  
    message = nil  
} 

affected_rows表示操作影響的行數,insert_id是在使用自增序列時產生的id。

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

{  
    { id= 1, ch= "hello"},  
    { id= 2, ch= "hello2"}  
}  

null將返回ngx.null。

2、example.conf配置檔案

location /lua_mysql {  
   default_type 'text/html';  
   lua_code_cache on;  
   content_by_lua_file /usr/example/lua/test_mysql.lua;  
}  
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上搜索相應的客戶端:

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

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()  

3、example.conf配置檔案

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解析

resolver 8.8.8.8;  

記得要配置DNS解析器resolver 8.8.8.8,否則域名是無法解析的。

ngx.location.capture

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

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

upstream backend {  
    server s.taobao.com;  
    keepalive 100;  
}  

即我們將請求upstream到backend;另外記得一定要新增之前的DNS解析器。

2、在example.conf配置如下location

location ~ /proxy/(.*) {  
   internal;  
   proxy_pass http://backend/$1$is_args$args;  
}  

internal表示只能內部訪問,即外部無法通過url訪問進來; 並通過proxy_pass將請求轉發到upstream。

3、test_http_2.lua

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是否繼承可以參考官方文件

另外還提供了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;  
}  

我們通過upstream+ngx.location.capture方式雖然麻煩點,但是得到更好的效能和upstream的連線池、負載均衡、故障轉移、proxy cache等特性。不過因為繼承在當前請求的請求頭,所以可能會存在一些問題,比較常見的就是gzip壓縮問題,ngx.location.capture不會解壓縮後端伺服器的GZIP內容,解決辦法可以參考

因為我們大部分這種http呼叫的都是內部服務,因此完全可以在proxy location中新增proxy_pass_request_headers off;來不傳遞請求頭。

相關推薦

常用Lua開發redismysqlhttp客戶

文章來源: 前言 對於開發來說需要有好的生態開發庫來輔助我們快速開發,而Lua中也有大多數我們需要的第三方開發庫如Redis、Memcached、Mysql、Http客戶端、JSON、模板引擎等。一些常見的Lua庫可以在github上搜索,下面給出連結:

Docker筆記(七):常用服務安裝——NginxMySqlRedis

開發中經常需要安裝一些常用的服務軟體,如Nginx、MySql、Redis等,如果按照普通的安裝方法,一般都相對比較繁瑣 —— 要經過下載軟體或原始碼包,編譯安裝,配置,啟動等步驟,使用 Docker 來安裝這些服務軟體能極大地簡化安裝過程,且速度也很快。   本文以下操作假定你已經裝好了docke

KVM虛擬化MySQLNginxRabbitMQRedis組件安裝指導

mysql字符集 eas www cat listen copy 測試 arc remove 1 檢查服務器的配置信息 1.1 檢查服務器的CPU信息 [root@localhost iso]#cat /proc/cpuinfo | grep na

linux下zookeeperredisactivemqsolrmysqlnginx啟動停止檢視狀態命令

一、zookeeper 首先進入zookeeper/bin目錄下 *啟動 ./zkServer.sh start *停止 ./zkServer.sh stop *檢視狀態 ./zkServer.sh status 二、redis 1、 redis簡潔安裝 re

mongodbmysqlredis的區別和是用場景

mysql是關係型資料庫,支援事物 MongoDB、Redis是非關係型資料庫,不支援事物 mongodb、mysql、redis的使用根據如何方便進行選擇   希望速度快的時候,選擇MongoDB或者是Redis   資料量過大的時候,選擇頻繁使用的資料存入Redis,其他的存入MongoDB  

IISMySQLRedis環境搭建

IIS 安裝:(1)開啟“控制面板”-“程式”-“開啟或關閉Windows功能”,如下圖,勾選“Internet服務”,點選“確定”              (2)安裝相應的.net framework 版本,自己機器上安裝的是.net framework4.6.2  

linux01-常用命令網路mysqlyum

常用命令 日常操作命令 **檢視當前所在的工作目錄 pwd **檢視當前系統的時間 date **檢視有誰線上(哪些人登陸到了伺服器) who 檢視當前線上 last 檢視最近的登陸歷史記錄 檔案系統操作 ls / 檢視根目錄下的子節點(資料夾和檔案)資訊 ls

初次部署springbootdockerredismysqlnginx

本文章適合所有初學者閱讀、我會從剛買來的一臺伺服器開始從零到部署專案   當然怎麼購買我就不多說了 使用到的技術有springboot、docker、redis、mysql、nginx 操作環境MAC電腦 首先通過終端連線伺服器   ssh [email pr

Mac OS使用brew安裝NginxMySQLPHP-FPM的LAMP開發環境

準備工作 新版的 Mac OS 內建了Apache 和 PHP,我的系統版本是OS X 10.9.3,可以通過以下命令檢視Apache和PHP的版本號: httpd -v Server version: Apache/2.2.26 (Unix) Ser

HBaseMongoDBMySQLOracleRedis--nosql資料庫與關係資料庫對比

HBase vs. MongoDB vs. MySQL vs. Oracle vs. Redis,三大主流開源 NoSQL 資料庫的 PK 兩大主流傳統 SQL 資料庫 類別 HBase MongoDB MySQL Oracle Redis 描述 基於 Ap

常用查詢集合oraclemysqlsqlserver

1、統計資料庫的表的總數        mysql:SELECT count(*) TABLES, table_schema FROM information_schema.TABLES  where table_schema = 'test' GROUP BY table

wampServer(windowsapachemysqlphp)

list mysql 配置 allow error del 默認 virtual listen wampServer(windows/apche/mysql/php)集成環境 在線狀態:區域網內可以訪問 離線狀態:本地設備可以訪問 自擬定網站根目錄: Apache -- h

Ubuntu搭建 ApacheMySQLPHP環境

分享 管理員 mysq ima 表示 ubunt ets .cn image 以Ubuntu 16.04為例: 1、安裝MysSQL 打開命令行輸入 :sudo apt-get install mysql-server 輸入管理員密碼 選擇Y 在安裝的中間會出現輸

05006_Linux的jdkmysqltomcat安裝

命令 fig ref 不支持 啟動 軟件包 默認 mysql 文件 1、軟件包下載鏈接:軟件包下載 密碼:advk 2、安裝JDK   (1)查看當前Linux系統是否已經安裝java,輸入 rpm -qa | grep java ;      (2)卸載兩個openJDK

SqlserverMysqlOracle各自的默認口號

edr drive mic odin word host base nco mysql mysql 默認端口號為:3306URL:jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=

centos7重啟apachenginxmysqlphp-fpm命令

httpd stop 啟動 sta fpm start res gin SQ apache 啟動 systemctl start httpd 停止 systemctl stop httpd 重啟 systemctl restart httpd mysql 啟動 system

查看Linux Nginx MySQL PHP 版本的方法

style tps HR version light gda targe true pac 參考:查看Linux 、Apache 、 MySQL 、 PHP 版本的方法 1.查看Linux版本: uname -a; more /etc/issue; cat /proc/ve

Spring配置JDBC連接OrcaleMySqlsqlserver

使用 SQ pos pri pre ace apache 數據源 value 閱讀指南:本文章主要講述如何在Spring框架中配置JDBC連接方式連接Oracle、Mysql、SqlServer。 原理如下: 一、導包 連接oracle11g所需的jar包:ojdbc6.j

Linux安裝java jdkmysqltomcat

ref app 1.0 重置密碼 esc 啟動mysql TP mar des 安裝javajdk 1.8 檢查是否安裝 rpm -qa | grep jdk rpm方式安裝 下載java1.8 jdk http://download.oracle.com/otn-pub/

LAMP架構介紹MySQLMariaDB介紹MySQL安裝

LinuxLAMP架構介紹 LAMP是一個簡寫,包含了4個東西:linux、apache(httpd)、mysql、php linux操作系統、apache提供wb服務的軟件、mysql存儲數據的軟件、php腳本語言 LAMP的工作原理 瀏覽器向服務器發送http請求,服務器 (Apache) 接受請求,由