更新時(shí)間:2023-09-25 來(lái)源:黑馬程序員 瀏覽量:
OpenResty? 是一個(gè)基于Nginx與Lua的高性能Web平臺(tái),其內(nèi)部集成了大量精良的Lua庫(kù)、第三方模塊以及大多數(shù)的依賴項(xiàng)。用于方便地搭建能夠處理超高并發(fā)、擴(kuò)展性極高的動(dòng)態(tài) Web 應(yīng)用、Web 服務(wù)和動(dòng)態(tài)網(wǎng)關(guān)。
OpenResty? 通過(guò)匯聚各種設(shè)計(jì)精良的 [Nginx] 模塊(主要由 OpenResty 團(tuán)隊(duì)自主開發(fā)),從而將Nginx有效地變成一個(gè)強(qiáng)大的通用Web應(yīng)用平臺(tái)。這樣,Web開發(fā)人員和系統(tǒng)工程師可以使用Lua腳本語(yǔ)言調(diào)動(dòng)Nginx支持的各種C以及Lua模塊,快速構(gòu)造出足以勝任10K乃至1000K以上單機(jī)并發(fā)連接的高性能Web應(yīng)用系統(tǒng)。
OpenResty? 的目標(biāo)是讓你的Web服務(wù)直接跑在Nginx服務(wù)內(nèi)部,充分利用Nginx的非阻塞I/O模型,不僅僅對(duì) HTTP客戶端請(qǐng)求,甚至于對(duì)遠(yuǎn)程后端諸如MySQL、PostgreSQL、Memcached以及Redis等都進(jìn)行一致的高性能響應(yīng)。
OpenResty簡(jiǎn)單理解,就相當(dāng)于封裝了nginx,并且集成了LUA腳本,開發(fā)人員只需要簡(jiǎn)單的其提供了模塊就可以實(shí)現(xiàn)相關(guān)的邏輯,而不再像之前,還需要在nginx中自己編寫lua的腳本,再進(jìn)行調(diào)用了。
linux安裝openresty:
1、添加倉(cāng)庫(kù)執(zhí)行命令
yum install yum-utils
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
2、執(zhí)行安裝
yum install openresty
3、安裝成功后 會(huì)在默認(rèn)的目錄如下:
/usr/local/openresty
4、啟動(dòng)openresty
cd /usr/local/openresty/nginx/sbin
./nginx
配置openresty的nginx配置文件`conf/nginx.conf`。在http模塊下添加一個(gè)server配置。
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, world</p>")
}
}
}
重啟openrestry
cd /usr/local/openresty/nginx/sbin
./nginx -s reload
輸入如下地址,進(jìn)行訪問 http://192.168.200.128:8080 瀏覽器輸出 `hello, world`
和一般的Web Server類似,我們需要接收請(qǐng)求、處理并輸出響應(yīng)。而對(duì)于請(qǐng)求我們需要獲取如請(qǐng)求參數(shù)、請(qǐng)求頭、Body體等信息;而對(duì)于處理就是調(diào)用相應(yīng)的Lua代碼即可;輸出響應(yīng)需要進(jìn)行響應(yīng)狀態(tài)碼、響應(yīng)頭和響應(yīng)內(nèi)容體的輸出。因此我們從如上幾個(gè)點(diǎn)出發(fā)即可。
獲取nginx變量:`ngx.var`
server {
listen 8080;
location / {
#定義nginx變量
set $b $host;
default_type text/html;
content_by_lua_block {
local var = ngx.var; -- 獲取nginx變量
ngx.say("ngx.var.b : ", var.b, "<br/>")
ngx.var.b = 2; -- 設(shè)置變量值
ngx.say("ngx.var.b : ", var.b, "<br/>")
ngx.say("<br/>")
}
}
}
獲取請(qǐng)求頭:`ngx.req.get_headers()`
server {
listen 8080;
location / {
#定義nginx變量
set $b $host;
default_type text/html;
content_by_lua_block {
local headers = ngx.req.get_headers()
ngx.say("headers begin", "<br/>")
ngx.say("Host : ", headers["Host"], "<br/>")
ngx.say("user-agent : ", headers["user-agent"], "<br/>")
ngx.say("user-agent : ", headers.user_agent, "<br/>")
ngx.say("=======================================","</br>")
for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), "<br/>")
else
ngx.say(k, " : ", v, "<br/>")
end
end
ngx.say("headers end", "<br/>")
ngx.say("<br/>")
}
}
}
get請(qǐng)求uri參數(shù):`ngx.req.get_uri_args()`
server {
listen 8080;
location / {
#定義nginx變量
set $b $host;
default_type text/html;
content_by_lua_block {
ngx.say("uri args begin", "<br/>")
local uri_args = ngx.req.get_uri_args()
ngx.say("param:username=",uri_args['username'], "<br/>")
ngx.say("param:password=",uri_args['password'], "<br/>")
ngx.say("uri args end", "<br/>")
ngx.say("<br/>")
}
}
}
post請(qǐng)求參數(shù):ngx.req.get_post_args()
server {
listen 8080;
location / {
#定義nginx變量
set $b $host;
default_type text/html;
content_by_lua_block {
ngx.say("uri args begin", "<br/>")
-- 獲取請(qǐng)求體中的數(shù)據(jù)
ngx.req.read_body()
local uri_args = ngx.req.get_post_args() --獲取key-value格式的數(shù)據(jù)
ngx.say("param:username=",uri_args['username'], "<br/>")
ngx.say("param:password=",uri_args['password'], "<br/>")
ngx.say("uri args end", "<br/>")
ngx.say("<br/>")
}
}
}
其他請(qǐng)求相關(guān)的方法:
> 獲取請(qǐng)求的http協(xié)議版本:`ngx.req.http_version()`
> 獲取請(qǐng)求方法:`ngx.req.get_method()`
> 獲取請(qǐng)求頭內(nèi)容:`ngx.req.get_headers()`
> 獲取請(qǐng)求的body內(nèi)容體:`ngx.req.get_body_data()`
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
--寫響應(yīng)頭
ngx.header.a = "1"
--多個(gè)響應(yīng)頭可以使用table
ngx.header.b = {"2", "3"}
--輸出響應(yīng)
ngx.say("a", "b", "<br/>")
ngx.print("c", "d", "<br/>")
--200狀態(tài)碼退出
return ngx.exit(200)
}
}
}
}
響應(yīng)相關(guān)方法:
> ngx.header.xx = yy:輸出響應(yīng)頭;
> ngx.print():輸出響應(yīng)內(nèi)容體;
> ngx.say():同ngx.print()一樣,但是會(huì)最后輸出一個(gè)換行符;
> ngx.exit():指定狀態(tài)碼退出;
> ngx.send_headers():發(fā)送響應(yīng)狀態(tài)碼,當(dāng)調(diào)用ngx.say/ngx.print時(shí)自動(dòng)發(fā)送響應(yīng)狀態(tài)碼;
> ngx.headers_sent( ): 判斷是否發(fā)送了響應(yīng)狀態(tài)碼。
重定向
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
ngx.redirect("http://jd.com", 302);
}
}
}
使用過(guò)如Java的朋友可能知道如Ehcache等這種進(jìn)程內(nèi)本地緩存,Nginx是一個(gè)Master進(jìn)程多個(gè)Worker進(jìn)程的
工作方式,因此我們可能需要在多個(gè)Worker進(jìn)程中共享數(shù)據(jù),那么此時(shí)就可以使用ngx.shared.DICT來(lái)實(shí)現(xiàn)全
局內(nèi)存共享。
```
共享全局變量,在所有worker間共享,如下:定義了一個(gè)名為shared_data的全局內(nèi)存,大小為1m
lua_shared_dict shared_data 1m;
```
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
--1、獲取全局共享內(nèi)存變量
local shared_data = ngx.shared.shared_data
--2、獲取字典值
local i = shared_data:get("i")
if not i then
i = 1
--3、惰性賦值
shared_data:set("i", i)
ngx.say("lazy set i ", i, "<br/>")
end
--遞增
i = shared_data:incr("i", 1)
ngx.say("i=", i, "<br/>")
}
}
}
ngx.shared.DICT
> 獲取共享內(nèi)存字典項(xiàng)對(duì)象
```
語(yǔ)法:dict = ngx.shared.DICT
dict = ngx.shared[name_var]
其中,DICT和name_var表示的名稱是一致的,比如上面例子中,shared_data = ngx.shared.shared_data
就是dict = ngx.shared.DICT的表達(dá)形式,
也可以通過(guò)下面的方式達(dá)到同樣的目的:
shared_data = ngx.shared['shared_data']
ngx.shared.DICT:get(key)
> 獲取共享內(nèi)存上key對(duì)應(yīng)的值。如果key不存在,或者key已經(jīng)過(guò)期,將會(huì)返回nil;如果出現(xiàn)錯(cuò)誤,那么將會(huì)返回nil以及錯(cuò)誤信息。
ngx.shared.DICT:get_stale(key)
> 與get方法類似,區(qū)別在于該方法對(duì)于過(guò)期的key也會(huì)返回,第三個(gè)返回參數(shù)表明返回的key的值是否已經(jīng)過(guò)期,true表示過(guò)期,false表示沒有過(guò)期。
ngx.shared.DICT:set(key, value, exptime?, flags?)
> “無(wú)條件”地往共享內(nèi)存上插入key-value對(duì),這里講的“無(wú)條件”指的是不管待插入的共享內(nèi)存上是否已經(jīng)存在相同的key。
> 三個(gè)返回值的含義:
> success:成功插入為true,插入失敗為false
> err:操作失敗時(shí)的錯(cuò)誤信息,可能類似"no memory"
> forcible:true表明需要通過(guò)強(qiáng)制刪除(LRU算法)共享內(nèi)存上其他字典項(xiàng)來(lái)實(shí)現(xiàn)插入,false表明沒有刪除共享內(nèi)存上的字典項(xiàng)來(lái)實(shí)現(xiàn)插入。
> exptime參數(shù)表明key的有效期時(shí)間,單位是秒(s),默認(rèn)值為0,表明永遠(yuǎn)不會(huì)過(guò)期;
> flags參數(shù)是一個(gè)用戶標(biāo)志值,會(huì)在調(diào)用get方法時(shí)同時(shí)獲取得到
ngx.shared.DICT.safe_set*(key, value, exptime?, flags?)
> 與set方法類似,區(qū)別在于不會(huì)在共享內(nèi)存用完的情況下,通過(guò)強(qiáng)制刪除(LRU算法)的方法實(shí)現(xiàn)插入。如果內(nèi)存不足,會(huì)直接返回nil和err信息"no memory"
ngx.shared.DICT.add*(key, value, exptime?, flags?)
> 與set方法類似,與set方法區(qū)別在于不會(huì)插入重復(fù)的鍵(可以簡(jiǎn)單認(rèn)為add方法是set方法的一個(gè)子方法),如果待插入的key已經(jīng)存在,將會(huì)返回nil和和err="exists"
ngx.shared.DICT.safe_add*(key, value, exptime?, flags?)
> 與safe_set方法類似,區(qū)別在于不會(huì)插入重復(fù)的鍵(可以簡(jiǎn)單認(rèn)為safe_add方法是safe_set方法的一個(gè)子方法),如果待插入的key已經(jīng)存在,將會(huì)返回nil和和err="exists"
ngx.shared.DICT.replace*(key, value, exptime?, flags?)
> 與set方法類似,區(qū)別在于只對(duì)已經(jīng)存在的key進(jìn)行操作(可以簡(jiǎn)單認(rèn)為replace方法是set方法的一個(gè)子方法),如果待插入的key在字典上不存在,將會(huì)返回nil和錯(cuò)誤信息"not found"
ngx.shared.DICT.delete*(key)
> 無(wú)條件刪除指定的key-value對(duì),其等價(jià)于
> ngx.shared.DICT:set(key, nil)
ngx.shared.DICT.incr*(key, value)
> 對(duì)key對(duì)應(yīng)的值進(jìn)行增量操作,增量值是value,其中value的值可以是一個(gè)正數(shù),0,也可以是一個(gè)負(fù)數(shù)。value必須是一個(gè)Lua類型中的number類型,否則將會(huì)返回nil和"not a number";key必須是一個(gè)已經(jīng)存在于共享內(nèi)存中的key,否則將會(huì)返回nil和"not found".
ngx.shared.DICT.flush_all*()
> 清除字典上的所有字段,但不會(huì)真正釋放掉字段所占用的內(nèi)存,而僅僅是將每個(gè)字段標(biāo)志為過(guò)期。
ngx.shared.DICT.flush_expired*(max_count?)
> 清除字典上過(guò)期的字段,max_count表明上限值,如果為0或者沒有給出,表明需要清除所有過(guò)期的字段,返回值flushed是實(shí)際刪除掉的過(guò)期字段的數(shù)目。
> 注意:
> 與flush_all方法的區(qū)別在于,該方法將會(huì)釋放掉過(guò)期字段所占用的內(nèi)存
ngx.shared.DICT.get_keys*(max_count?)
> 從字典上獲取字段列表,個(gè)數(shù)為max_count,如果為0或沒有給出,表明不限定個(gè)數(shù)。默認(rèn)值是1024個(gè)
> 注意:
> 強(qiáng)烈建議在調(diào)用該方法時(shí),指定一個(gè)max_count參數(shù),因?yàn)樵趉eys數(shù)量很大的情況下,如果不指定max_count的值,可能會(huì)導(dǎo)致字典被鎖定,從而阻塞試圖訪問字典的worker進(jìn)程。
Nginx與Lua編寫腳本的基本構(gòu)建塊是指令。 指令用于指定何時(shí)運(yùn)行用戶Lua代碼以及如何使用結(jié)果。openresty(Nginx+lua-nginx-module)中各個(gè)階段執(zhí)行的指令解釋及其執(zhí)行順序。
> init_by_lua*:初始化 nginx 和預(yù)加載 lua(nginx 啟動(dòng)和 reload 時(shí)執(zhí)行);*
>
> init_worker_by_lua*:每個(gè)工作進(jìn)程(worker_processes)被創(chuàng)建時(shí)執(zhí)行,用于啟動(dòng)一些定時(shí)任務(wù),比如心跳檢查,后端服務(wù)的健康檢查,定時(shí)拉取服務(wù)器配置等;*
>
> ssl_certificate_by_lua*:對(duì) https 請(qǐng)求的處理,即將啟動(dòng)下游 SSL(https)連接的 SSL 握手時(shí)執(zhí)行,用例:按照每個(gè)請(qǐng)求設(shè)置 SSL 證書鏈和相應(yīng)的私鑰,按照 SSL 協(xié)議有選擇的拒絕請(qǐng)求等;
>
> *set_by_lua*:設(shè)置 nginx 變量;
>
> rewrite_by_lua*:重寫請(qǐng)求(從原生 nginx 的 rewrite 階段進(jìn)入),執(zhí)行內(nèi)部 URL 重寫或者外部重定向,典型的如偽靜態(tài)化的 URL 重寫;*
>
> access_by_lua*:處理請(qǐng)求(和 rewrite_by_lua 可以實(shí)現(xiàn)相同的功能,從原生 nginx 的 access階段進(jìn)入);*
>
> content_by_lua*:執(zhí)行業(yè)務(wù)邏輯并產(chǎn)生響應(yīng),類似于 jsp 中的 servlet;
>
> *balancer_by_lua*:負(fù)載均衡;
>
> header_filter_by_lua*:處理響應(yīng)頭;
>
> *body_filter_by_lua*:處理響應(yīng)體;
>
> log_by_lua:記錄訪問日志;
備注:`*`表示兩種選擇,比如 `log_by_lua\*` 可以表示`log_by_lua`或`log_by_lua_file`
nginx_lua常用模塊介紹
json格式化:cjson
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
-- 引入cjson
local cjson = require("cjson")
--將lua對(duì)象 轉(zhuǎn)為 json字符串
local obj = {
id = 1,
name = "zhangsan",
age = nil,
is_male = false,
hobby = {"film", "music", "read"}
}
local str = cjson.encode(obj)
ngx.say("lua對(duì)象到字符串:",str,"</br>")
ngx.say("--------------------------------</br>");
--將字符串 轉(zhuǎn)為 lua對(duì)象
str = '{"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1,"age":null}'
local obj = cjson.decode(str)
ngx.say("字符串到lua對(duì)象:","</br>")
ngx.say("obj.age ",obj.age,"</br>")
ngx.say("obj.age == nil ",obj.age == nil,"</br>")
ngx.say("obj.age == cjson.null ",obj.age == cjson.null,"</br>")
ngx.say("obj.hobby[1] ",obj.hobby[1],"</br>")
}
}
}
redis客戶端:resty.redis
-- 引入resty.redis庫(kù)
local redis = require("resty.redis")
-- 定義關(guān)閉redis連接的方法
local function close_redis(instance)
if not instance then
return
end
local ok, error = instance:close()
if not ok then
ngx.say(error)
end
end
-- 創(chuàng)建redis連接
local redis_ip = "127.0.0.1"
local redis_port = "6379"
local redis_instance = redis:new()
redis_instance:set_timeout(1000)
local ok, error = redis_instance.connect(redis_ip, redis_port)
if not ok then
ngx.say(error)
return close_redis(redis_instance)
end
-- 執(zhí)行redis命令
local redis_key = "message"
local redis_value = ngx.md5("hello, world")
ok, error = redis_instance:set(redis_key, redis_value)
if not ok then
ngx.say(error)
return close_redis(redis_instance)
end
local message, error = redis_instance:get(redis_key)
if not message then
ngx.say(error)
return close_redis(redis_instance)
end
if message == ngx.null then
message = ""
end
ngx.say(redis_key, ": ", message)
-- 關(guān)閉redis連接
close_redis(redis_instance)
mysql客戶端:resty.mysql
配置`openresty`連接`mysql`
-- 引入resty.mysql庫(kù)
local mysql = require("resty.mysql")
-- 定義關(guān)閉連接的方法
local function close_db(instance)
if not instance then
return
end
instance:close()
end
-- 創(chuàng)建mysql實(shí)例對(duì)象
local instance, error = mysql:new()
if not instance then
ngx.say(error)
return
end
instance:set_timeout(1000)
local properties = {
host = "127.0.0.1",
port = 3306,
database = "wpsmail",
user = "root",
password = "123456"
}
-- 連接mysql
local result, error, error_no, sql_state = instance:connect(properties)
if not result then
ngx.say("error: ", error, ", error_no: ",
error_no, ", sql_state: ", sql_state)
return close_db(instance)
end
刪除表
local drop_table_sql = "drop table if exists test"
local drop_result, error, error_no, sql_state = instance:query(drop_table_sql)
if not drop_result then
ngx.say("error: ", error, ", error_no: ",
error_no, ", sql_state: ", sql_state)
return close_db(instance)
end
創(chuàng)建表
local create_table_sql
= "create table test(id int primary key auto_increment, ch varchar(100))"
local create_result, error, error_no, sql_state = instance:query(create_table_sql)
if not create_result then
ngx.say("error: ", error, ", error_no: ",
error_no, ", sql_state: ", sql_state)
return close_db(instance)
end
插入表數(shù)據(jù)
local insert_sql = "insert into test (ch) values('hello')"
local insert_result, error, error_no, sql_state = instance:query(insert_sql)
if not insert_result then
ngx.say("error: ", error, ", error_no: ",
error_no, ", sql_state: ", sql_state)
return close_db(instance)
end
ngx.say("affected row: ", insert_result.affected_rows,
", insert id: ", insert_result.insert_id)
更新表數(shù)據(jù)
local update_table_sql
= "update test set ch = 'hello2' where id =" .. insert_result.insert_id
local update_result, error, error_no, sql_state = instance:query(update_table_sql)
if not update_result then
ngx.say("error: ", error, ", error_no: ",
error_no, ", sql_state: ", sql_state)
return close_db(instance)
end
ngx.say("affected row: ", insert_result.affected_rows)
查詢表數(shù)據(jù)
local select_table_sql = "select id, ch from test"
local select_result, error, error_no, sql_state = instance:query(select_table_sql)
if not select_result then
ngx.say("error: ", error, ", error_no: ",
error_no, ", sql_state: ", sql_state)
return close_db(instance)
end
for i, row in ipairs(select_result) do
ngx.say(i, ": ", row)
end
本文給大家介紹了openresty這一高性能web服務(wù)平臺(tái)的基本使用。包括如何在openresty中處理請(qǐng)求和響應(yīng),在openresty是使用nginx的本地緩存(即nginx全局內(nèi)存)。分析了openresty的整體執(zhí)行過(guò)程,其中`content_by_lua*`階段,是執(zhí)行業(yè)務(wù)邏輯并產(chǎn)生響應(yīng)的階段,我們的業(yè)務(wù)代碼主要在此階段編寫。同時(shí)本文還介紹了如何使用`resty.redis`操作redis,使用`resty.mysql`來(lái)操作mysql,以及使用cjson進(jìn)行數(shù)據(jù)的json格式化。
本文版權(quán)歸黑馬程序員Java培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!
作者:黑馬程序員Java培訓(xùn)學(xué)院
首發(fā):https://java.itheima.com
還在用Zipkin分布式服務(wù)鏈路追蹤?來(lái)試試這個(gè)吧!
2023-09-25哪些是重要的bean生命周期方法?可以重載它們嗎?
2023-09-22在多線程環(huán)境下,SimpleDateFormat是線程安全的嗎?
2023-09-22什么是網(wǎng)關(guān)過(guò)濾器?怎樣實(shí)現(xiàn)自定義過(guò)濾器?
2023-09-21Java是什么?Java編程培訓(xùn)課程哪家好?
2023-09-21TCP/UDP協(xié)議和HTTP、FTP、SMTP區(qū)別及應(yīng)用場(chǎng)景
2023-09-21