常用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開發庫redis、mysql、http客戶端
文章來源: 前言 對於開發來說需要有好的生態開發庫來輔助我們快速開發,而Lua中也有大多數我們需要的第三方開發庫如Redis、Memcached、Mysql、Http客戶端、JSON、模板引擎等。一些常見的Lua庫可以在github上搜索,下面給出連結:
Docker筆記(七):常用服務安裝——Nginx、MySql、Redis
開發中經常需要安裝一些常用的服務軟體,如Nginx、MySql、Redis等,如果按照普通的安裝方法,一般都相對比較繁瑣 —— 要經過下載軟體或原始碼包,編譯安裝,配置,啟動等步驟,使用 Docker 來安裝這些服務軟體能極大地簡化安裝過程,且速度也很快。 本文以下操作假定你已經裝好了docke
KVM虛擬化、MySQL、Nginx、RabbitMQ、Redis組件安裝指導
mysql字符集 eas www cat listen copy 測試 arc remove 1 檢查服務器的配置信息 1.1 檢查服務器的CPU信息 [root@localhost iso]#cat /proc/cpuinfo | grep na
linux下zookeeper、redis、activemq、solr、mysql、nginx啟動、停止、檢視狀態命令
一、zookeeper 首先進入zookeeper/bin目錄下 *啟動 ./zkServer.sh start *停止 ./zkServer.sh stop *檢視狀態 ./zkServer.sh status 二、redis 1、 redis簡潔安裝 re
mongodb、mysql、redis的區別和是用場景
mysql是關係型資料庫,支援事物 MongoDB、Redis是非關係型資料庫,不支援事物 mongodb、mysql、redis的使用根據如何方便進行選擇 希望速度快的時候,選擇MongoDB或者是Redis 資料量過大的時候,選擇頻繁使用的資料存入Redis,其他的存入MongoDB
IIS、MySQL、Redis環境搭建
IIS 安裝:(1)開啟“控制面板”-“程式”-“開啟或關閉Windows功能”,如下圖,勾選“Internet服務”,點選“確定” (2)安裝相應的.net framework 版本,自己機器上安裝的是.net framework4.6.2
linux01-常用命令、網路、mysql、yum
常用命令 日常操作命令 **檢視當前所在的工作目錄 pwd **檢視當前系統的時間 date **檢視有誰線上(哪些人登陸到了伺服器) who 檢視當前線上 last 檢視最近的登陸歷史記錄 檔案系統操作 ls / 檢視根目錄下的子節點(資料夾和檔案)資訊 ls
初次部署springboot、docker、redis、mysql、nginx
本文章適合所有初學者閱讀、我會從剛買來的一臺伺服器開始從零到部署專案 當然怎麼購買我就不多說了 使用到的技術有springboot、docker、redis、mysql、nginx 操作環境MAC電腦 首先通過終端連線伺服器 ssh [email pr
Mac OS使用brew安裝Nginx、MySQL、PHP-FPM的LAMP開發環境
準備工作 新版的 Mac OS 內建了Apache 和 PHP,我的系統版本是OS X 10.9.3,可以通過以下命令檢視Apache和PHP的版本號: httpd -v Server version: Apache/2.2.26 (Unix) Ser
HBase、MongoDB、MySQL、Oracle、Redis--nosql資料庫與關係資料庫對比
HBase vs. MongoDB vs. MySQL vs. Oracle vs. Redis,三大主流開源 NoSQL 資料庫的 PK 兩大主流傳統 SQL 資料庫 類別 HBase MongoDB MySQL Oracle Redis 描述 基於 Ap
常用查詢集合oracle、mysql、sqlserver
1、統計資料庫的表的總數 mysql:SELECT count(*) TABLES, table_schema FROM information_schema.TABLES where table_schema = 'test' GROUP BY table
wampServer(windows、apache、mysql、php)
list mysql 配置 allow error del 默認 virtual listen wampServer(windows/apche/mysql/php)集成環境 在線狀態:區域網內可以訪問 離線狀態:本地設備可以訪問 自擬定網站根目錄: Apache -- h
Ubuntu搭建 Apache、MySQL、PHP環境
分享 管理員 mysq ima 表示 ubunt ets .cn image 以Ubuntu 16.04為例: 1、安裝MysSQL 打開命令行輸入 :sudo apt-get install mysql-server 輸入管理員密碼 選擇Y 在安裝的中間會出現輸
05006_Linux的jdk、mysql、tomcat安裝
命令 fig ref 不支持 啟動 軟件包 默認 mysql 文件 1、軟件包下載鏈接:軟件包下載 密碼:advk 2、安裝JDK (1)查看當前Linux系統是否已經安裝java,輸入 rpm -qa | grep java ; (2)卸載兩個openJDK
Sqlserver、Mysql、Oracle各自的默認端口號
edr drive mic odin word host base nco mysql mysql 默認端口號為:3306URL:jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=
centos7重啟apache、nginx、mysql、php-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連接Orcale、MySql、sqlserver
使用 SQ pos pri pre ace apache 數據源 value 閱讀指南:本文章主要講述如何在Spring框架中配置JDBC連接方式連接Oracle、Mysql、SqlServer。 原理如下: 一、導包 連接oracle11g所需的jar包:ojdbc6.j
Linux安裝java jdk、mysql、tomcat
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架構介紹、MySQL、MariaDB介紹、MySQL安裝
LinuxLAMP架構介紹 LAMP是一個簡寫,包含了4個東西:linux、apache(httpd)、mysql、php linux操作系統、apache提供wb服務的軟件、mysql存儲數據的軟件、php腳本語言 LAMP的工作原理 瀏覽器向服務器發送http請求,服務器 (Apache) 接受請求,由