首頁常見問題正文

Java培訓:高性能web平臺openrestry簡介

更新時間:2023-09-25 來源:黑馬程序員 瀏覽量:

  一、概述

  OpenResty? 是一個基于Nginx與Lua的高性能Web平臺,其內部集成了大量精良的Lua庫、第三方模塊以及大多數的依賴項。用于方便地搭建能夠處理超高并發(fā)、擴展性極高的動態(tài) Web 應用、Web 服務和動態(tài)網關。

  OpenResty? 通過匯聚各種設計精良的 [Nginx] 模塊(主要由 OpenResty 團隊自主開發(fā)),從而將Nginx有效地變成一個強大的通用Web應用平臺。這樣,Web開發(fā)人員和系統(tǒng)工程師可以使用Lua腳本語言調動Nginx支持的各種C以及Lua模塊,快速構造出足以勝任10K乃至1000K以上單機并發(fā)連接的高性能Web應用系統(tǒng)。

  OpenResty? 的目標是讓你的Web服務直接跑在Nginx服務內部,充分利用Nginx的非阻塞I/O模型,不僅僅對 HTTP客戶端請求,甚至于對遠程后端諸如MySQL、PostgreSQL、Memcached以及Redis等都進行一致的高性能響應。

  OpenResty簡單理解,就相當于封裝了nginx,并且集成了LUA腳本,開發(fā)人員只需要簡單的其提供了模塊就可以實現相關的邏輯,而不再像之前,還需要在nginx中自己編寫lua的腳本,再進行調用了。

  二、安裝

  linux安裝openresty:

  1、添加倉庫執(zhí)行命令

    yum install yum-utils
    yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

  2、執(zhí)行安裝

    yum install openresty

  3、安裝成功后 會在默認的目錄如下:

    /usr/local/openresty

  4、啟動openresty

    cd  /usr/local/openresty/nginx/sbin
    ./nginx

  三、入門程序

  配置openresty的nginx配置文件`conf/nginx.conf`。在http模塊下添加一個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

  輸入如下地址,進行訪問 http://192.168.200.128:8080 瀏覽器輸出 `hello, world`

  四、openrestry中的常用Lua API介紹

   和一般的Web Server類似,我們需要接收請求、處理并輸出響應。而對于請求我們需要獲取如請求參數、請求頭、Body體等信息;而對于處理就是調用相應的Lua代碼即可;輸出響應需要進行響應狀態(tài)碼、響應頭和響應內容體的輸出。因此我們從如上幾個點出發(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; -- 設置變量值
                ngx.say("ngx.var.b : ", var.b, "<br/>")
                ngx.say("<br/>")
            }
        }
    }

  獲取請求頭:`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請求uri參數:`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請求參數: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/>")
                -- 獲取請求體中的數據
                ngx.req.read_body()
                local uri_args = ngx.req.get_post_args() --獲取key-value格式的數據
                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/>")
            }
        }
    }

  其他請求相關的方法:

  > 獲取請求的http協議版本:`ngx.req.http_version()`

  > 獲取請求方法:`ngx.req.get_method()`

  > 獲取請求頭內容:`ngx.req.get_headers()`

  > 獲取請求的body內容體:`ngx.req.get_body_data()`

  六、輸出響應

 server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                --寫響應頭
                ngx.header.a = "1"
                --多個響應頭可以使用table
                ngx.header.b = {"2", "3"}
                --輸出響應
                ngx.say("a", "b", "<br/>")
                ngx.print("c", "d", "<br/>")
                --200狀態(tài)碼退出
                return ngx.exit(200)
            }
        }
    }

}

  響應相關方法:

  > ngx.header.xx = yy:輸出響應頭;

  > ngx.print():輸出響應內容體;

  > ngx.say():同ngx.print()一樣,但是會最后輸出一個換行符;

  > ngx.exit():指定狀態(tài)碼退出;

  > ngx.send_headers():發(fā)送響應狀態(tài)碼,當調用ngx.say/ngx.print時自動發(fā)送響應狀態(tài)碼;

  > ngx.headers_sent( ): 判斷是否發(fā)送了響應狀態(tài)碼。

  重定向

 server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.redirect("http://jd.com", 302);  
            }
        }
    }

  Nginx全局內存

  使用過如Java的朋友可能知道如Ehcache等這種進程內本地緩存,Nginx是一個Master進程多個Worker進程的

  工作方式,因此我們可能需要在多個Worker進程中共享數據,那么此時就可以使用ngx.shared.DICT來實現全

  局內存共享。

  1、首先在nginx.conf的http部分定義一個全局內存,并指定內存大小。

  ```

  共享全局變量,在所有worker間共享,如下:定義了一個名為shared_data的全局內存,大小為1m

  lua_shared_dict shared_data 1m;

  ```

  2、使用全局內存

 server {  
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                --1、獲取全局共享內存變量
                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/>")
            }
        }
    }

  3、全局內存常用方法介紹

  ngx.shared.DICT

  > 獲取共享內存字典項對象

  ```

  語法:dict = ngx.shared.DICT

  dict = ngx.shared[name_var]

  其中,DICT和name_var表示的名稱是一致的,比如上面例子中,shared_data = ngx.shared.shared_data

  就是dict = ngx.shared.DICT的表達形式,

  也可以通過下面的方式達到同樣的目的:

  shared_data = ngx.shared['shared_data']

  ngx.shared.DICT:get(key)

  > 獲取共享內存上key對應的值。如果key不存在,或者key已經過期,將會返回nil;如果出現錯誤,那么將會返回nil以及錯誤信息。

  ngx.shared.DICT:get_stale(key)

  > 與get方法類似,區(qū)別在于該方法對于過期的key也會返回,第三個返回參數表明返回的key的值是否已經過期,true表示過期,false表示沒有過期。

  ngx.shared.DICT:set(key, value, exptime?, flags?)

  > “無條件”地往共享內存上插入key-value對,這里講的“無條件”指的是不管待插入的共享內存上是否已經存在相同的key。

  > 三個返回值的含義:

  >  success:成功插入為true,插入失敗為false

  >  err:操作失敗時的錯誤信息,可能類似"no memory"

  >  forcible:true表明需要通過強制刪除(LRU算法)共享內存上其他字典項來實現插入,false表明沒有刪除共享內存上的字典項來實現插入。

  > exptime參數表明key的有效期時間,單位是秒(s),默認值為0,表明永遠不會過期;

  > flags參數是一個用戶標志值,會在調用get方法時同時獲取得到

  ngx.shared.DICT.safe_set*(key, value, exptime?, flags?)

  > 與set方法類似,區(qū)別在于不會在共享內存用完的情況下,通過強制刪除(LRU算法)的方法實現插入。如果內存不足,會直接返回nil和err信息"no memory"

  ngx.shared.DICT.add*(key, value, exptime?, flags?)

  > 與set方法類似,與set方法區(qū)別在于不會插入重復的鍵(可以簡單認為add方法是set方法的一個子方法),如果待插入的key已經存在,將會返回nil和和err="exists"

  ngx.shared.DICT.safe_add*(key, value, exptime?, flags?)

  > 與safe_set方法類似,區(qū)別在于不會插入重復的鍵(可以簡單認為safe_add方法是safe_set方法的一個子方法),如果待插入的key已經存在,將會返回nil和和err="exists"

  ngx.shared.DICT.replace*(key, value, exptime?, flags?)

  > 與set方法類似,區(qū)別在于只對已經存在的key進行操作(可以簡單認為replace方法是set方法的一個子方法),如果待插入的key在字典上不存在,將會返回nil和錯誤信息"not found"

  ngx.shared.DICT.delete*(key)

  > 無條件刪除指定的key-value對,其等價于

  > ngx.shared.DICT:set(key, nil)

  ngx.shared.DICT.incr*(key, value)

  > 對key對應的值進行增量操作,增量值是value,其中value的值可以是一個正數,0,也可以是一個負數。value必須是一個Lua類型中的number類型,否則將會返回nil和"not a number";key必須是一個已經存在于共享內存中的key,否則將會返回nil和"not found".

  ngx.shared.DICT.flush_all*()

  > 清除字典上的所有字段,但不會真正釋放掉字段所占用的內存,而僅僅是將每個字段標志為過期。

  ngx.shared.DICT.flush_expired*(max_count?)

  > 清除字典上過期的字段,max_count表明上限值,如果為0或者沒有給出,表明需要清除所有過期的字段,返回值flushed是實際刪除掉的過期字段的數目。

  > 注意:

  > 與flush_all方法的區(qū)別在于,該方法將會釋放掉過期字段所占用的內存

  ngx.shared.DICT.get_keys*(max_count?)

  > 從字典上獲取字段列表,個數為max_count,如果為0或沒有給出,表明不限定個數。默認值是1024個

  > 注意:

  > 強烈建議在調用該方法時,指定一個max_count參數,因為在keys數量很大的情況下,如果不指定max_count的值,可能會導致字典被鎖定,從而阻塞試圖訪問字典的worker進程。

  openresty執(zhí)行過程分析

  Nginx與Lua編寫腳本的基本構建塊是指令。 指令用于指定何時運行用戶Lua代碼以及如何使用結果。openresty(Nginx+lua-nginx-module)中各個階段執(zhí)行的指令解釋及其執(zhí)行順序。

1695610974229_執(zhí)行順序.jpg

  > init_by_lua*:初始化 nginx 和預加載 lua(nginx 啟動和 reload 時執(zhí)行);*

  >

  > init_worker_by_lua*:每個工作進程(worker_processes)被創(chuàng)建時執(zhí)行,用于啟動一些定時任務,比如心跳檢查,后端服務的健康檢查,定時拉取服務器配置等;*

  >

  > ssl_certificate_by_lua*:對 https 請求的處理,即將啟動下游 SSL(https)連接的 SSL 握手時執(zhí)行,用例:按照每個請求設置 SSL 證書鏈和相應的私鑰,按照 SSL 協議有選擇的拒絕請求等;

  >

  > *set_by_lua*:設置 nginx 變量;

  >

  > rewrite_by_lua*:重寫請求(從原生 nginx 的 rewrite 階段進入),執(zhí)行內部 URL 重寫或者外部重定向,典型的如偽靜態(tài)化的 URL 重寫;*

  >

  > access_by_lua*:處理請求(和 rewrite_by_lua 可以實現相同的功能,從原生 nginx 的 access階段進入);*

  >

  > content_by_lua*:執(zhí)行業(yè)務邏輯并產生響應,類似于 jsp 中的 servlet;

  >

  > *balancer_by_lua*:負載均衡;

  >

  > header_filter_by_lua*:處理響應頭;

  >

  > *body_filter_by_lua*:處理響應體;

  >

  > 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對象 轉為 json字符串
                local obj = {
                    id = 1,
                    name = "zhangsan",
                    age = nil,
                    is_male = false,
                    hobby = {"film", "music", "read"}
                }
                local str = cjson.encode(obj)
                ngx.say("lua對象到字符串:",str,"</br>")
                ngx.say("--------------------------------</br>");
               
                --將字符串 轉為 lua對象
                str = '{"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1,"age":null}'
                local obj = cjson.decode(str)
                ngx.say("字符串到lua對象:","</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庫
local redis = require("resty.redis")

-- 定義關閉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)

-- 關閉redis連接
close_redis(redis_instance) 

       mysql客戶端:resty.mysql

  配置`openresty`連接`mysql`

-- 引入resty.mysql庫
local mysql = require("resty.mysql")

-- 定義關閉連接的方法
local function close_db(instance)
    if not instance then
        return
    end

    instance:close()
end

-- 創(chuàng)建mysql實例對象
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

  插入表數據

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)

  更新表數據

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)

  查詢表數據

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服務平臺的基本使用。包括如何在openresty中處理請求和響應,在openresty是使用nginx的本地緩存(即nginx全局內存)。分析了openresty的整體執(zhí)行過程,其中`content_by_lua*`階段,是執(zhí)行業(yè)務邏輯并產生響應的階段,我們的業(yè)務代碼主要在此階段編寫。同時本文還介紹了如何使用`resty.redis`操作redis,使用`resty.mysql`來操作mysql,以及使用cjson進行數據的json格式化。

  本文版權歸黑馬程序員Java培訓學院所有,歡迎轉載,轉載請注明作者出處。謝謝!

  作者:黑馬程序員Java培訓學院

  首發(fā):https://java.itheima.com

分享到:
在線咨詢 我要報名
和我們在線交談!