摘要:多返回值開始變得越來越與眾不同了允許函數返回多個結果。這種情況函數沒有足夠的返回值時也會用來補充。中的索引習慣以開始。
為什么值得入手?
Nginx作為現在使用最廣泛的高性能后端服務器,Openresty為之提供了動態預言的靈活,當性能與靈活走在了一起,無疑對于被之前陷于臃腫架構,苦于提升性能的工程師來說是重大的利好消息,本文就是在這種背景下,將初入這一未知的領域之后的一些經驗與大家分享一下,若有失言之處,歡迎指教。
安裝現在除了能在 Download里面下載源碼來自己編譯安裝,現在連預編譯好的包都有了, 安裝也就分分鐘的事了。
hello world/path/to/nginx.conf, conftent_by_lua_file里面的路徑請根據lua_package_path調整一下。
location / { content_by_lua_file ../luablib/hello_world.lua; }
/path/to/openresty/lualib/hello_world.lua
ngx.say("Hello World")
訪問一下, Hello World~.
HTTP/1.1 200 OK Connection: keep-alive Content-Type: application/octet-stream Date: Wed, 11 Jan 2017 07:52:15 GMT Server: openresty/1.11.2.2 Transfer-Encoding: chunked Hello World
基本上早期的Openresty相關的開發的路數也就大抵如此了, 將lua庫發布到lualib之下,將對應的nginx的配置文件發布到nginx/conf底下,然后reload已有的Openresty進程(少數需要清空Openresty shared_dict數據的情況需要重啟), 如果是測試環境的話,那更是簡單了,在http段將lua_code_cache設為off, Openresty不會緩存lua腳本,每次執行都會去磁盤上讀取lua腳本文件的內容,發布之后就可以直接看效果了(當然如果配置文件修改了,reload是免不了了),是不是找到一點當初apache寫php的感覺呢:)
開發語言Lua的大致介紹環境搭建完畢之后,接下來就是各種試錯了,關于Lua的介紹,網上的資料比如:Openresty最佳實踐(版本比較多,這里就不放了)。 寫的都會比較詳細,本文就不在這里過多解釋了,只展示部分基礎的Lua的模樣。下面對lua一些個性有趣的地方做一下分享,可能不會涉及到lua語言比較全面或者細節的一些部分,作為補充,讀者可以翻閱官方的<
-- 單行注釋以兩個連字符開頭 --[[ 多行注釋 --]] -- 變量賦值 num = 13 -- 所有的數字都是雙精度浮點型。 s = "單引號字符串" t = "也可以用雙引號" u = [[ 多行的字符串 ]] -- 控制流程,和python最明顯的差別可能就是冒號變成了do, 最后還得數end的對應 -- while while n < 10 do n = n + 1 -- 不支持 ++ 或 += 運算符。 end -- for for i = 0, 9 do print(i) end -- if語句: f n == 0 then print("no hits") elseif n == 1 then print("one hit") else print(n .. " hits") end --只有nil和false為假; 0和 ""均為真! if not aBoolValue then print("false") end -- 循環的另一種結構: repeat print("the way of the future") num = num - 1 until num == 0 -- 函數定義: function add(x, y) return x + y end -- table 用作鍵值對 t = {key1 = "value1", key2 = false} print(t.key1) -- 打印 "value1". -- 使用任何非nil的值作為key: u = {["@!#"] = "qbert", [{}] = 1729, [6.28] = "tau"} print(u[6.28]) -- 打印 "tau" -- table用作列表、數組 v = {"value1", "value2", 1.21, "gigawatts"} for i = 1, #v do -- #v 是列表的大小 print(v[i]) end -- 元表 f1 = {a = 1, b = 2} -- 表示一個分數 a/b. f2 = {a = 2, b = 3} -- 這會失敗: -- s = f1 + f2 metafraction = {} function metafraction.__add(f1, f2) local sum = {} sum.b = f1.b * f2.b sum.a = f1.a * f2.b + f2.a * f1.b return sum end setmetatable(f1, metafraction) setmetatable(f2, metafraction) s = f1 + f2 -- 調用在f1的元表上的__add(f1, f2) 方法 -- __index、__add等的值,被稱為元方法。 -- 這里是一個table元方法的清單: -- __add(a, b) for a + b -- __sub(a, b) for a - b -- __mul(a, b) for a * b -- __div(a, b) for a / b -- __mod(a, b) for a % b -- __pow(a, b) for a ^ b -- __unm(a) for -a -- __concat(a, b) for a .. b -- __len(a) for #a -- __eq(a, b) for a == b -- __lt(a, b) for a < b -- __le(a, b) for a <= b -- __index(a, b)for a.b -- __newindex(a, b, c) for a.b = c -- __call(a, ...) for a(...)
以上參考了
learn lua in y minute ,做了適當的裁剪來做說明。
作為lua里面唯一標準的數據結構, 直接打印居然只有一個id狀的東西,這里說這一點沒有抱怨的意思,只是讓讀者做好倒騰的心理準備,畢竟倒騰一個簡潔語言終歸是有代價的,了解決定背后的原因,有時候比現成的一步到位的現成方案這也是倒騰的另一面好處吧,這里給出社區里面的討論
舉個例子: lua里面一般使用#table來獲取table的長度,究其原因,lua對于未定義的變量、table的鍵,總是返回nil,而不像python里面肯定是拋出異常, 所以#來計算table長度的時候只會遍歷到第一個值為nil的地方,畢竟他不能一直嘗試下去,這時候就需要使用table.maxn的方式來獲取了。
Good or Bad? 自動類型轉換如果你在python里面去把一個字符串和數字相加,python必定以異常回應。
>>> "a" + 1 Traceback (most recent call last): File "", line 1, in TypeError: cannot concatenate "str" and "int" objects
但是Lua覺得他能搞定。
> = "20" + 10 30
如果你覺得Lua選擇轉換加號操作符的操作數成數字類型去進行求值顯得不可思議的,下面這種情況下,這種轉換又貌似是可以有點用的了,print("hello" .. 123),這時你不用手動去將所有參數手工轉換成字符串類型。尚沒有定論說這項特性就是一無是處,但是這種依賴語言本身不明顯的特性的代碼筆者是不希望在項目里面去踩雷的。
多返回值Lua開始變得越來越與眾不同了:允許函數返回多個結果。
function foo0() end --無返回值 function foo1() return "a" end -- 返回一個結果 function foo2() return "a","b" end -- 返回兩個結果 -- 多重賦值時, 函數調用是最后一個表達式時 -- 保留盡可能多的返回值 x, y = foo2() -- x="a", y="b" x = foo2() -- x="a", "b"被丟棄 x,y,z = 10,foo2() -- x=10, y="a", z="b" -- 如果多重賦值時,函數調用不是最后一個表達式時 -- 只產生一個值 x, y = foo2(),20 -- x="a", y=20 x,y = foo0(), 20, 30 -- x=nil, y= 20,30被丟棄,這種情況當函數沒有返回值時,會用nil來補充。 x,y,z = foo2() -- x="a", y="b", z=nil, 這種情況函數沒有足夠的返回值時也會用nil來補充。 -- 同樣在函數調用、table聲明中 函數調用作為最后的表達式,都會竟可能多的填充返回值,如果不是最后,則只返回一個 print(foo2(), 1) --> a 1 print(1, foo2()) --> 1 a b t = {foo2(), 1} --> {"a", 1} t = {1, foo2()} --> {1, "a", "b"} -- 阻止這種參數順序搞事: print(1, (foo2())) -- 1 a 加一層括號,強制只返回一個值真個性: 模式匹配
簡潔的Lua容不下行數比自己實現語言行數還多的正則表達式實現(無論是POSIX, 還是Perl正則表達式),于是乎有了獨樹一幟的模式與匹配,下面只用模式匹配來做URL解碼、編碼功能實現的演示。
-- 解碼 function unescape(s) s = string.gsub(s, "+", " ") s = string.gsub(s, "%%(%x%x)", function (h) return string.char(tonumber(h, 16)) end) return s end print(unescape("a%2Bb+%3D+c")) ---> a+b =c cgi = {} function decode(s) for name,value in string.gmatch(s, "([^&=]+)=([^&=]+)") do name = unescape(name) value = unescape(value) cgi[name] = value end end -- 編碼 function escape(s) s = string.gsub(s, "[&=+%%%c]", function(c) return string.format("%%%02X", string.byte(c)) end) s = string.gsub(s, " ", "+") return s end function encode(t) local b = {} for k,v in pairs(t) do b[#b+1] = (escape(k) .. "=" .. escape(v)) end return table.concat(b,"&") end
模式匹配實現的功能是足夠強大,但是工程上是否值得投入,還值得商榷,沒有通用性,只此lua一家用,雖然正則表達式也是不好調試,但是至少知道了解的人多,可能到最后筆者也不會多深入lua的模式匹配,但是如此單純為了減少代碼而放棄正則表達式現成的庫,自己又玩了一套,這也是沒誰了。
與c的天然親密一言不合,就拿c寫一個庫給lua用,由此可見兩門語言是多么哥兩好了,如果舉個例子的話就是lua5.1里面的位操作符,luajit就是這樣提供的解決方案, Lua Bit Operations Module, 有興趣的讀者可以下載源碼看一下,完全就是用lua的c api包裝了c里面的位操作符出來用,除了加了些限制的話(ex.位移出來的必然是32位有符)。lua除了數據結構上面的過于簡潔外,其他的控制結構、操作符這些語言特性基本該有的都有了,唯獨缺了位操作符,5.1為什么當時選擇了不實現位操作符呢?有知道出處或者原因的讀者歡迎留言告知。(順帶一提,lua5.2里面有官方的bit32庫可以用,lua5.3已經補上了位操作符,另外Openresty在lua版本的選擇上是選擇停留在5.1,這點在github的Issue里面有回答過,且沒有升級的打算)
雜只有nil、false為布爾假。
lua中的索引習慣以1開始。
沒有整型,所有數字都是浮點數。
當函數的參數是單引號或者雙引號的字符串或者table定義的時候,可以省略外面的(), 所以require "cookie"并不是代表require是個關鍵字。
table[index] 等價于 table [index]。
構建公司層面完整的Openresty生態 開發助手:成長中的resty命令習慣了動態語言的解釋器的立即反饋,哪怕是熟悉lua的同學,初入Openresty的時候似乎又想起了編譯->執行->修改的無限循環的記憶,因為每次都需要修改配置文件、reload、測試再如此重復個幾次才能寫對一段函數,resty命令無疑期待,筆者也希望resty命令能夠更加完善、易用。
另外提一個小遺憾,現在resty命令不能玩Openresty里面的shared_dict共享內存, 這可能跟目前resty使用的nginx配置的模板是固定有關吧。
環境:可能不再需要重新編譯Nginx有過Nginx維護開發經驗的同學可能都熟悉這么一個過程,因為多半會做業務的拆分,除了小公司外,基本都不會把一個Nginx的所有可選模塊都編譯進去,每次有新的Nginx相關的功能的增減,都免不了重新編譯,重新部署上線,Openresty是基于Nginx的,如果是新增Nginx本身的功能,重新編譯增加功能沒什么好說的,如何優雅的更新Nginx服務進程,Nginx有提供方案、各家也有各家的服務可靠性要求,具體怎么辦這里就不贅述了。
發布部署Openresty本身的發布部署跟Nginx本身沒有太大的不同,Openresty本身的發布部署官方也推出了linux平臺的預編譯好的包,在這樣的基礎上構建環境就更加便捷,環境之上,首先是lua腳本和nginx配置文件的發布,在版本管理之下,加上自動構建的發布平臺,Openresty的應用分分鐘就可以上線了:),這個流程本身無關Openresty,但是簡而言之一句話,當重復性的東西自動化之后,我們才有精力去解決更有趣的問題,不是么?
第三方庫的安裝、管理以前: 自己找個第三方庫編譯之后扔到Openresty的lualib目錄,luajit是否兼容、是否lua5.1兼容都得自己來測試一遍。
之前: 對于解決前一個問題,Openresty是通過給出lua里面Luarocks的安裝使用來解決的,但是這種方式不能解決上面所說的第二個問題,所以現在這種方式已經不推薦使用了,下面貼一下官網的說明,只做內容收集、展示用, 最新的具體說明參見using luarocks。
wget http://luarocks.org/releases/luarocks-2.0.13.tar.gz tar -xzvf luarocks-2.0.13.tar.gz cd luarocks-2.0.13/ ./configure --prefix=/usr/local/openresty/luajit --with-lua=/usr/local/openresty/luajit/ --lua-suffix=jit --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1 make sudo make install
安裝第三方庫示例: sudo /usr/local/openresty/luajit/luarocks install md5。
現在: Openresty提供了解決這兩個問題的完整方案,自己的包管理的規范和倉庫opm。
詳細的標準說明, 請移步: https://github.com/openresty/... 這里就不多做介紹了,關于第三方庫的質量,Openresty官網上也有了專門的QA頁面,至少保證了第三方庫一些實現的下限,不像python里面安裝某些第三方包,比如aerospike的, 里面安裝python客戶端,每次要去網上拉個c的客戶端下來之類的稀奇古怪的玩法,期待opm未來更加完善。
關于單元測試關于自動化測試的話,就筆者的試用經驗而言,感覺還不是特別的順手,官方提供的Test:Nginx工具已經提供簡潔強大的功能,但是如果作為TDD開發中的測試驅動的工具而言,筆者覺得報錯信息的有效性上面可能是唯一讓人有點覺得有點捉雞的地方,尚不清楚是否是筆者用的有誤,一般Test:Nginx的報錯多半無法幫助調試代碼,還是要走調試、修改的老路子。但是Test:Nginx的真正價值筆者覺得是講實例代碼和測試完美的結合,由此養成了看每個Openresty相關的項目代碼都必先閱讀里面的Test:Nginx的測試,當然最多最豐富的還是Openresty本身的測試。
舉個實際的例子,在使用Test:Nginx之前,之前對于Nginx的日志輸出,一切的測試依據,對于外面的運行環境跑的測試只能通過http的請求和返回來做測試的判斷條件,這時候對于一些情況就束手無策了, 比如處理某種錯誤情況可能需要在log里面記錄一下,這種測試就無法保證,另外也有類似lua-resty-test這樣通過提供組件的方式來進行,但是我們一旦接觸的了Test:Nginx的測試方法之后,這些就顯得相形見絀了,我們舉個實際的例子。
# vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; #worker_connections(1014); #master_process_enabled(1); #log_level("warn"); #repeat_each(2); plan tests => repeat_each() * (blocks() * 3 + 0); #no_diff(); no_long_string(); #master_on(); #workers(2); run_tests(); __DATA__ === TEST 1: lpush & lpop --- http_config lua_shared_dict dogs 1m; --- config location = /test { content_by_lua_block { local dogs = ngx.shared.dogs local len, err = dogs:lpush("foo", "bar") if len then ngx.say("push success") else ngx.say("push err: ", err) end local val, err = dogs:llen("foo") ngx.say(val, " ", err) local val, err = dogs:lpop("foo") ngx.say(val, " ", err) local val, err = dogs:llen("foo") ngx.say(val, " ", err) local val, err = dogs:lpop("foo") ngx.say(val, " ", err) } } --- request GET /test --- response_body push success 1 nil bar nil 0 nil nil nil --- no_error_log [error]
以上是隨便選取的lua-nginx-module的測試文件145-shdict-list.t中的一段做說明,測試文件分為3個部分,__DATA__以上的部分編排測試如何運行, __DATA__作為分隔符, __DATA__以下的是各個測試的說明部分. 測試部分如果具體細分的話,一般由====TEST 1: name開始到下一個測試的聲明;然后是配置nginx配置的http_config、config、...的部分;接著是模擬請求的部分,基本就是http請求報文的設定,功能不限于這里的request部分;最后是輸出部分,這時候不僅是http報文的body部分之類的http響應、還有nginx的日志的輸出這樣的測試條件,對于這樣清晰可讀、還能順帶把使用例子寫的清楚的單元測試的框架,pythoner真的難道不羨慕么?
關于調試、性能調優這一塊筆者還沒有深入研究過,所以,這里就不多說了,這里就做一下相關知識的鏈接歸納,方便大家整理資料吧。
lua語言本身提供的調試就比較簡潔、加上Openresty是嵌入Nginx內部的,這就更給排查工作帶來了困難。
官方的調試頁面
官方的性能調優頁面
通過systemtap探查在線的Nginx work進程
額外的工具庫stap++
工具火焰圖Flame Graphs的介紹
Linux Kernel Performance: Flame Graphs
反爬蟲
作者 toyld 豈安科技搬運代碼負責人
主導各處的挖坑工作,擅長挖坑于悄然不息,負責生命不息,挖坑不止。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/39428.html
摘要:為了幫助用戶更好地完成消費決策閉環,馬蜂窩上線了大交通業務。現在,用戶在馬蜂窩也可以完成購買機票火車票等操作。第二階段架構轉變及服務化初探從年開始,整個大交通業務開始從架構向服務化演變。 交通方式是用戶旅行前要考慮的核心要素之一。為了幫助用戶更好地完成消費決策閉環,馬蜂窩上線了大交通業務。現在,用戶在馬蜂窩也可以完成購買機票、火車票等操作。 與大多數業務系統相同,我們一樣經歷著從無到有...
摘要:為了幫助用戶更好地完成消費決策閉環,馬蜂窩上線了大交通業務。現在,用戶在馬蜂窩也可以完成購買機票火車票等操作。第二階段架構轉變及服務化初探從年開始,整個大交通業務開始從架構向服務化演變。 交通方式是用戶旅行前要考慮的核心要素之一。為了幫助用戶更好地完成消費決策閉環,馬蜂窩上線了大交通業務。現在,用戶在馬蜂窩也可以完成購買機票、火車票等操作。 與大多數業務系統相同,我們一樣經歷著從無到有...
閱讀 1583·2021-10-18 13:35
閱讀 2362·2021-10-09 09:44
閱讀 816·2021-10-08 10:05
閱讀 2718·2021-09-26 09:47
閱讀 3563·2021-09-22 15:22
閱讀 432·2019-08-29 12:24
閱讀 1999·2019-08-29 11:06
閱讀 2857·2019-08-26 12:23