OpenResty的現狀、趨勢、使用及學習方法

河馬大俠發表於2015-12-08

1. OpenResty 是什麼,適合什麼場景下使用

和大部分知名開源軟體誕生在歐美國家不同,OpenResty 自身和依賴的主要元件都是金磚國家的開發者發明的,這點還挺有意思。

Nginx 是俄羅斯人發明的, Lua 是巴西幾個教授發明的,中國人章亦春把 LuaJIT VM 嵌入到 Nginx 中,實現了 OpenResty 這個高效能服務端解決方案。

通過 OpenResty,你可以把 nginx 的各種功能進行自由拼接, 更重要的是,開發門檻並不高,這一切都是用強大輕巧的 Lua 語言來操控。

它主要的使用場景主要是:

在 Lua 中揉和和處理各種不同的 nginx 上游輸出(Proxy,Postgres,Redis,Memcached 等)

在請求真正到達上游服務之前,Lua 可以隨心所欲的做複雜的訪問控制和安全檢測

隨心所欲的操控響應頭裡面的資訊

從外部儲存服務(比如 Redis,Memcached,MySQL,Postgres)中獲取後端資訊,並用這些資訊來實時選擇哪一個後端來完成業務訪問

在內容 handler 中隨意編寫複雜的 Web 應用,使用 同步但依然非阻塞 的方式,訪問後端資料庫和其他儲存

在 rewrite 階段,通過 Lua 完成非常複雜的 URL dispatch

用 Lua 可以為 nginx 子請求和任意 location,實現高階快取機制

組織 OpenResty 技術大會之前,我一直認為自己是一個孤獨的 OpenResty 使用者,覺得自己在使用一個冷門的技術。

雖然大家都聽說過 OpenResty 或者 ngx_lua,但感覺用在生產環境中使用的卻少之又少,除了幾個 CDN 公司外,好像沒有聽說過哪家知名網際網路公司在使用。而 CDN 行業之所以使用,很多是受到 cloudflare 技術棧的影響,OpenResty 的作者也在國外這家 CDN 公司。

但辦完這個大會,我發現使用者真的挺多,奇虎360的所有服務端團隊都在使用,京東、百度、魅族、知乎、優酷、新浪這些網際網路公司都在使用。有用來寫 WAF、有做 CDN 排程、有做廣告系統、訊息推送系統,還有像我們部門一樣,用作 API server 的。有些還用在非常關鍵的業務上,比如開濤在高可用架構分享的京東商品詳情頁,是我知道的 ngx_lua 最大規模的應用。

2. 奇虎企業安全服務端技術選型的標準

先說下 3 年多前做架構選型的時候,我為什麼會選擇 OpenResty?

其實架構如何設計並不重要,因為每家公司,每個團隊,他們的公司文化和技術背景各不相同,生搬硬套會適得其反。重要的是當初為什麼這麼選擇,中途為什麼調整。

我們的產品要求單機上面,服務端提供高效能的 API 介面, QPS 至少過萬,未來需要支撐到 10 萬。我們並沒有急於去使用 PHP 、 Python 或者其他的語言來實現功能,而是先勾勒出一個理想化的技術模型。

這個模型應該具備:

非阻塞的訪問網路IO。在連線 MySQL 、Redis 和發起 HTTP 請求時,工作程式不能傻傻的等待網路IO的返回,而是需要支援事件驅動,用協程的方式讓 CPU 資源更有效的去處理其他請求。很多語言並不具備這樣的能力和周邊庫。

有完備的快取機制。不僅需要支援 Redis 、Memcached 等外部快取,也應該在自己的程式內有快取系統。我們希望大部分的請求都能在一個程式中得到資料並返回,這樣是最高效的方法,一旦有了網路IO和程式間的互動,效能就會受到很大影響。

同步的寫程式碼邏輯,不要讓開發者感知到回撥和非同步。這個也很重要,程式設計師也是人,程式碼應該更符合人的思維習慣,顯式的回撥和非同步關鍵字,會打斷思路,也給除錯帶來困難。

最好是站在巨人肩上,基於成熟的技術上搭建。採用一門全新誕生的語言和技術,需要經歷語言自身發展期頻繁調整的陣痛,還可能站錯隊。

不僅支援 Linux 平臺,還需要支援 Windows 平臺,這個是我們產品很特別的需求,很多中小企業使用者還是習慣 Windows 的操作,不具備 Linux 的維護能力。

基於以上幾點的考慮,考察了當時的一些方案,選擇了 OpenResty 。

首先,它最大的特點就是用同步的程式碼邏輯實現非阻塞的呼叫,其次它有單程式內的 LRU cache 和程式間的 share DICT cache,而且它是揉合 nginx 和 LuaJIT 而產生的。而且 nginx 有 Windows 版本,雖然有非常多的限制,但這些限制都是可以解決的, nginx 官方 Windows 版本中不支援的特性,我們開源出來的版本都解決了。

第一次看到這樣的方案,我覺得它肯定會顛覆高效能服務端的開發。為什麼呢?在我之前的公司裡,每天會有近百億次的查詢請求,而伺服器只用了十臺。

我們採用了 nginx C 模組 + 內建在 nginx 中的 K-V 資料庫(自己開發的),來實現所有的業務邏輯,達到這個目標。聽上去很簡單,但是過程非常艱辛,兩三個十幾年工作經驗的大牛做了一年多才穩定下來。絕大部分開發能力不足,只能望塵莫及。而且後續的除錯和維護,也會花費不少精力。

但是 OpenResty 的出現改變了這一切, OpenResty 非常的 pythonic ,適合人類的正常思維。新手經過一兩個月的學習,做出來的 API, 就可以達到 nginx C 模組的效能,而且程式碼量大大減少,也方便除錯。

3. 以奇虎和新浪為例,如何在專案中引入新技術

技術選型只是第一步,如何才能在一個產品或者專案中引入 OpenResty 這個新的技術呢?我拿奇虎企業安全和新浪移動這兩家公司真實發生的案例給大家看看。我和新浪移動的周晶,都是在一個有成熟產品的部門,用一兩個人的力量,把一個新技術,替換掉了原有的技術架構。但由於企業產品和個人產品的不同,方法有很大的不一樣。

先說我所在奇虎企業安全。我在 2012 年初加入這個部門,當時產品主打免費,目標使用者是小企業。所以架構設計上面,只考慮了幾十點、幾百點的終端請求,使用了非常強繫結的 Windows 平臺技術,而且傾向於不用開源軟體,自己新做一個更適合自己的框架。包括自己用 C++ 開發的 Web server,自己寫的 PHP 路由和框架,資料儲存在 sqlite 裡面。

我幫忙修改了兩個月 PHP 的 bug,看明白了技術架構的思路之後,就去新開的一個產品線了。這是一個實驗性的產品,主要面對央企和專用網,一個網路中有上百萬的終端。

剛開始沒有什麼人關注,我就直接採用了 Linux + OpenResty + Redis + Postgres 的開源元件,效能測試甩之前的N條街。後面這個實驗性的產品,和之前的產品,合併為一個產品,技術上面就割裂為兩套架構。老功能用老架構,新功能用新架構。

隨著越來越多大使用者的增加,原有的技術架構開始捉襟見肘,技術債務越積壓越多。隨著使用者的抱怨,sqlite 被拋棄,全面換成 Postgres。但對於自己開發的框架還是有些敝帚自珍。

期間通過對比測試、OpenResty 培訓還有多次使用者效能問題排查,讓開發同學們都知道這門技術的優勢。快被加班壓垮的開發同學,逐漸開始選擇使用 OpenResty 而不是自研的框架,來進行新功能的開發,以及舊功能的遷移,來避免加班。

在產品重構的時候,之前自研的服務端框架被完全拋棄,服務端開發的同學從 8 、9 個人減少到 3 個人。在新技術的引入過程中,我們沒有采用強制的舉措,因為企業產品需要穩定,使用者處部署的版本更新很慢。

而新浪移動周晶的實踐,對大家更有參考意義。新浪移動最開始是基於 Apache,用 PHP 來處理使用者請求。Apache 是同步多程式模型,在併發請求不多的情況下沒有問題。

但是總是會有突發新聞,比如馬航失聯、文章出軌等,突發的高流量把後臺壓垮了幾次。而且可以預見世界盃的流量也會很大,所以周晶花幾個月時間,用 nginx 替換了 Apache,使用 nginx 的 fast_cgi_cache,QPS 提升了一個數量級。

新浪移動後臺的介面都是使用 PHP 來實現的,在高併發下有些力不從心。而 nginx 簡單的快取雖然能滿足效能,但不能滿足業務精細化和資料一致性的要求,需要找 PHP 之外的解決方案,前提是讓 PHP 的開發能夠舒適的使用。 node.js 的回撥地獄、Go 的除錯不方便,都是一個阻礙。

他們最後選擇了 OpenResty,而且基於 OpenResty 開源了一個 Web 框架 Vanilla(香草),模仿了 Yaf 的使用習慣,讓 PHP 的開發更容易接受和上手。 Vanilla 已經在新浪移動開始使用,一些核心業務,比如高清圖和體育直播,正在向這個框架遷移中。

4. 入門痛點,以及學習的正確方法

我和周晶的入門,都是自己摸著石頭過河。當時除了 Python 社群「大媽」的那篇使用文章外,找不到其他的資料。

奇虎和新浪都用 OpenResty 成功替換了之前的技術,但問題還是挺明顯,就是大家都認為自己是孤獨的使用者,同事中基本沒有人認同。在關鍵和支撐業務上,使用 OpenResty 有些不放心,都會在邊緣業務上先做嘗試和驗證。

雖然 OpenResty 的效能做的很棒,比肩或者超過其他所有的高效能解決方案,但是擔心沒有學習資料、擔心招不到人、擔心沒人交流,可能還擔心作者章亦春哪天撂挑子不幹了,這個專案就黃了。

高可用架構群裡的各位都是架構師,是技術決策者,在引入一門新技術的時候,肯定會考慮到這些風險。比如小米科技馬利超在高可用架構的分享,他們在搶購系統中曾經使用過 ngx_lua,雖然效能滿足需求,但是團隊裡面熟悉的人少,最後還是改成了 Go 語言實現。

如何解決這些擔憂? 社群是有過思考和討論的,我們放在分享最後講。先從一個嘗試使用這門技術的開發者的角度看,OpenResty 不少基礎工作沒有完善,友好程度不夠:

只能從原始碼安裝,沒有 apt-get、brew 等軟體倉庫安裝方法;安裝第三方庫沒有 PIP、NPM 之類的包管理工具,需要去先谷歌,然後拷貝程式碼檔案到指定的目錄下,才能 require 使用。

程式碼編寫需要修改 nginx.conf 和對應的 lua 程式碼,即使是 hello world 也是如此。當然你可以把程式碼寫在 nginx 的配置檔案裡面,但是生產環境肯定是要分離的。這種編寫程式碼的方式,不像是一個程式語言,和常規的程式設計方式不同。

有獨特的執行階段概念,因為 OpenResty 是基於 nginx 的,所以也繼承它的這種概念。你的程式碼邏輯,可能需要放在不同的階段裡面執行,才能獲取你想要的預期。而這些階段間資訊如何傳遞,以及哪些 API 不能在某些階段使用,就會經常攔住新手。

遇到問題只有郵件列表這一種方式來溝通,而郵件列表是被牆的。文件也只有英文版本,導致很多新手的問題無法被解決。

沒有系統學習 OpenResty 的手段,大都是業務需要實現什麼功能,就去文件和 API 裡面去找。至於方式對不對,能不能優化,就不知道了。

而 Lua 語言自身也有一些特別的地方:

下標從 1 開始,這個是和其他程式語言很大的不同。

不區分 array 和 dict ,會導致處理 json 的時候,無法區分 array 和 object。

預設全域性變數,需要在所有變數前加 local,忘記的話,可能導致各種難查的 bug。

自帶的字串正則匹配規則和通常的 PCRE 不同,使用的話,學習成本較高。

Lua 標準庫和周邊庫,都是阻塞的,需要自己甄別哪些可以和 OpenResty 搭配使用。新手很容易使用了阻塞的庫,而導致效能急劇下降。

有沒有好的入門方法?

我們團隊正在做這方面的努力,儘量在現有的基礎上,降低學習的門檻。 對於新手,可以看 StuQ 上面 OpenResty 的系列視訊教程 (http://www.stuq.org/course/de…,我們計劃有 4 季,分別是入門、進階、實戰和原始碼分析。現在第一季已經上線,第二季正在後期製作。看完前兩季,基本上就可以在專案裡面用了。

對於已經使用了 OpenResty 的開發者,我們把這兩三年遇到的坑,都記錄在 GitHub 的OpenResty最佳實踐,大家可以當做 cookbook 來使用。

5. nginScript 這樣的嘗試會替代 OpenResty 嗎?

nginScript 是今年 nginx 大會上,Nginx 官方推出的一個新的配置語言。它是模仿了 OpenResty 的做法,把 JavaScript VM 嵌入到 nginx 中,提供簡單的 nginx 配置功能。

我們看下它的 hello world:

    locationi / {
        js_run "
            var res;
            res = $r.response;
            res.status = 200;
            res.send("hello world!");
            res.finish();
        "
    }

再對比下 OpenResty 的 hello world:

    location / {
        content_by_lua_block {
            ngx.say("hello world")
        }
    }

看上去差不多,只是 OpenResty 簡潔一些。根據 nginx 官方的說明,nginScript 只是想提供一種更方便配置 nginx 的方法,並不想取代 ngx_lua。

考慮到 JavaScript 本身的流行和開發社群的強大,如果未來兩三年它從一個簡單的 nginx 配置語言,逐漸演變成類似 ngx_lua 這樣功能非常完備的開發語言,甚至替代 OpenResty 也是有可能的。

當然,這個前提是 OpenResty 停滯不前。現在 OpenResty 已經有的功能,和計劃開發的功能,傾向於覆蓋 nginx Plus 的功能。所以 nginx 和 OpenResty 之間,有一個良性的競爭關係,這是大家都樂意看到的。

6. 未來重點解決的問題和新增特性

短期內的目標,是想降低入門的難度:

提供官方二進位制釋出包。類似於 docker 的安裝方法,一行命令,下載一個sh指令碼,增加一個源地址,不用手工解決依賴,不用原始碼編譯,直接就可以試用。
而且會發布 Windows 的二進位制包,方便這個平臺的開發者本機做一些測試。

增加包管理。命令列工具叫 iresty,可以從 iresty.org 上面搜尋、安裝需要的 lua resty 庫,避免找錯庫或者放錯目錄。

寫一本書《 OpenResty 程式設計》,這本書會成為官方的入門書籍,框架和關鍵內容由作者春哥直接操刀,我和社群的其他同學幫助一起完成。

做完上面3點,OpenResty 的入門難度會降低到和其他程式語言一樣。

在功能上面,會增加很多激動人心的新特性:

支援 TCP 和 UDP 。Nginx 最新的 stream 子系統已經支援了 TCP,OpenResty 的 ngx_stream_lua 模組正在開發中,會擁有和現有的 nginx http modlue 相同的 lua API,所以很多應用和庫,可以不加修改的執行在一個新的子系統上面。

更好的支援推送場景。增加 shared list 共享記憶體的佇列,可以用於 worker 間的通訊;增加 semaphore 特性,用於 ngx_lua 輕量級執行緒間的通訊。酷狗音樂的推送服務就是基於這些實現的,這些改動點會在這個月併入 master。可以邀請酷狗音樂的同學,來給大家詳細分享下里面的細節。

建立一個開源的 WAF 平臺。現在阿里雲和 cloudflare 的 WAF 做的都很棒,經受住了很多實際的考驗。但是都沒有開源,我們希望最好的 WAF 是開源的,而且是基於 OpenResty 的。

在 OpenResty 中增加記憶體資料庫。可以有持久化,或者就是全記憶體的,支援 SQL 的查詢。這個也是出於極致效能的考慮,有時候我們還是需要使用 SQL 來做一些複雜的查詢,但有不想使用那麼重的關係型資料庫,而且資料是可以丟失的。那麼這個就可以排上用場。

實現 PHP、Python 等方言,讓 PHP、Python 等程式設計師可以用自己喜歡的語言寫 OpenResty 的程式碼,底層轉換為 LuaJIT 的位元組碼。

春哥在 OpenResty 技術大會上面說了非常多的新特性,包括 streaming RegEx 正則引擎等等,非常高階,我挑了幾個我覺得有意思的做介紹。

相關文章