天天看點

OpenResty初涉

關于openresty可參考官方文檔:

OpenResty初涉

1、這個是什麼?

    在網際網路公司,Nginx可以說是标配元件,但是主要場景還是負載均衡、反向代理、代理緩存、限流等場景;而把Nginx作為一個Web容器使用的還不是那麼廣泛。Nginx的高性能是大家公認的,而Nginx開發主要是以C/C++子產品的形式進行,整體學習和開發成本偏高;如果有一種簡單的語言來實作Web應用的開發,那麼Nginx絕對是把好的瑞士軍刀;目前Nginx團隊也開始意識到這個問題,開發了nginxScript:可以在Nginx中使用JavaScript進行動态配置一些變量和動态腳本執行;而目前市面上用的非常成熟的擴充是由章亦春将Lua和Nginx粘合的ngx_lua子產品,并且将Nginx核心、LuaJIT、ngx_lua子產品、許多有用的Lua庫和常用的第三方Nginx子產品組合在一起成為OpenResty,這樣開發人員就可以安裝OpenResty,使用Lua編寫腳本,然後部署到Nginx Web容器中運作。進而非常輕松就能開發出高性能的Web服務。

OpenResty® 是一個基于 ​​Nginx​​ 與 Lua 的高性能 Web 平台,其内部內建了大量精良的 Lua 庫、第三方子產品以及大多數的依賴項。用于友善地搭建能夠處理超高并發、擴充性極高的動态 Web 應用、Web 服務和動态網關。

OpenResty® 通過彙聚各種設計精良的 ​​Nginx​​ 子產品(主要由 OpenResty 團隊自主開發),進而将 ​​Nginx​​有效地變成一個強大的通用 Web 應用平台。這樣,Web 開發人員和系統工程師可以使用 Lua 腳本語言調動 ​​Nginx​​ 支援的各種 C 以及 Lua 子產品,快速構造出足以勝任 10K 乃至 1000K 以上單機并發連接配接的高性能 Web 應用系統。

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

2、這裡複制了他人寫的比較好的部落格:總的來說:

ngx_lua(OpenResty)是Nginx的一個子產品,将Lua嵌入到Nginx中,進而可以使用Lua來編寫腳本,這樣就可以使用Lua編寫應用腳本,部署到Nginx中運作,即Nginx變成了一個Web容器;這樣開發人員就可以使用Lua語言開發高性能Web應用了。

1、Nginx優點

Nginx設計為一個主程序多個工作程序的工作模式,每個程序是單線程來處理多個連接配接,而且每個工作程序采用了非阻塞I/O來處理多個連接配接,進而減少了線程上下文切換,進而實作了公認的高性能、高并發;是以在生成環境中會通過把CPU綁定給Nginx工作程序進而提升其性能;另外因為單線程工作模式的特點,記憶體占用就非常少了。

Nginx更改配置重新開機速度非常快,可以毫秒級,而且支援不停止Nginx進行更新Nginx版本、動态重載Nginx配置。

Nginx子產品也是非常多,功能也很強勁,不僅可以作為http負載均衡,Nginx釋出1.9.0版本還支援TCP負載均衡,還可以很容易的實作内容緩存、web伺服器、反向代理、通路控制等功能。

2、Lua的優點

Lua是一種輕量級、可嵌入式的腳本語言,這樣可以非常容易的嵌入到其他語言中使用。另外Lua提供了協程并發,即以同步調用的方式進行異步執行,進而實作并發,比起回調機制的并發來說代碼更容易編寫和了解,排查問題也會容易。Lua還提供了閉包機制,函數可以作為First Class Value 進行參數傳遞,另外其實作了标記清除垃圾收集。

因為Lua的小巧輕量級,可以在Nginx中嵌入Lua VM,請求的時候建立一個VM,請求結束的時候回收VM。

3、什麼是ngx_lua

ngx_lua是Nginx的一個子產品,将Lua嵌入到Nginx中,進而可以使用Lua來編寫腳本,這樣就可以使用Lua編寫應用腳本,部署到Nginx中運作,即Nginx變成了一個Web容器;這樣開發人員就可以使用Lua語言開發高性能Web應用了。

ngx_lua提供了與Nginx互動的很多的API,對于開發人員來說隻需要學習這些API就可以進行功能開發,而對于開發web應用來說,如果接觸過Servlet的話,其開發和Servlet類似,無外乎就是知道接收請求、參數解析、功能處理、傳回響應這幾步的API是什麼樣子的。

4、開發環境

我們可以使用​​OpenResty​​來搭建開發環境,OpenResty将Nginx核心、LuaJIT、許多有用的Lua庫和Nginx第三方子產品打包在一起;這樣開發人員隻需要安裝OpenResty,不需要了解Nginx核心和寫複雜的C/C++子產品就可以,隻需要使用Lua語言進行Web應用開發了。

如何安裝可以參考《​​跟我學Nginx+Lua開發​​》。

5、OpenResty生态

OpenResty提供了一些常用的ngx_lua開發子產品:如

  lua-resty-memcached

  lua-resty-mysql

  lua-resty-redis

  lua-resty-dns

  lua-resty-limit-traffic

  lua-resty-template

這些子產品涉及到如mysql資料庫、redis、限流、子產品渲染等常用功能元件;另外也有很多第三方的ngx_lua元件供我們使用,對于大部分應用場景來說現在生态環境中的元件已經足夠多了;如果不滿足需求也可以自己去寫來完成自己的需求。

6、場景

理論上可以使用ngx_lua開發各種複雜的web應用,不過Lua是一種腳本/動态語言,不适合業務邏輯比較重的場景,适合小巧的應用場景,代碼行數保持在幾十行到幾千行。目前見到的一些應用場景:

web應用:會進行一些業務邏輯處理,甚至進行耗CPU的模闆渲染,一般流程:mysql/redis/http擷取資料、業務處理、産生JSON/XML/模闆渲染内容,比如京東的清單頁/商品詳情頁;

接入網關:實作如資料校驗前置、緩存前置、資料過濾、API請求聚合、AB測試、灰階釋出、降級、監控等功能,比如京東的交易大Nginx節點、無線部門正在開發的無線網關、單品頁統一服務、實時價格、動态服務;

Web防火牆:可以進行IP/URL/UserAgent/Referer黑名單、限流等功能;

緩存伺服器:可以對響應内容進行緩存,減少到後端的請求,進而提升性能;

其他:如靜态資源伺服器、消息推送服務、縮略圖裁剪等。

1、負載均衡

如上圖,我們首先通過LVS+HAProxy将流量轉發給核心Nginx 1和核心Nginx 2,即實作了流量的負載均衡,此處可以使用如輪訓、一緻性哈希等排程算法來實作負載的轉發;然後核心Nginx會根據請求特征如“Host:item.jd.com”,轉發給相應的業務Nginx節點如單品頁Nginx 1。此處為什麼分兩層呢?

1、核心Nginx層是無狀态的,可以在這一層實作流量分組(内網和外網隔離、爬蟲和非爬蟲流量隔離)、内容緩存、請求頭過濾、故障切換(機房故障切換到其他機房)、限流、防火牆等一些通用型功能;

2、業務Nginx如單品頁Nginx,可以在在業務Nginx實作業務邏輯、或者反向代理到如Tomcat,在這一層可以實作内容壓縮(放在這一層的目的是減少核心Nginx的CPU壓力,将壓力分散到各業務Nginx)、AB測試、降級;即這一層的Nginx跟業務有關聯,實作業務的一些通用邏輯。

不管是核心Nginx還是業務Nginx,都應該是無狀态設計,可以水準擴容。

業務Nginx一般會把請求直接轉發給後端的業務應用,如Tomcat、PHP,即将請求内部轉發到相應的業務應用;當有的Tomcat出現問題了,可以在這一層摘掉;或者有的業務路徑變了在這一層進行rewrite;或者有的後端Tomcat壓力太大也可以在這一層降級,減少對後端的沖擊;或者業務需要灰階釋出時也可以在這一層Nginx上控制。

2、單機閉環

所謂單機閉環即所有想要的資料都能從本伺服器直接擷取,在大多數時候無需通過網絡去其他伺服器擷取。

如上所示,主要有三種應用模式:

2.1、第一張圖應用場景是Nginx應用誰也不依賴,比如我們的Cookie白名單應用,其目的是不在白名單中的Cookie将被清理,防止大家随便将Cookie寫到jd.om根下;大家通路http://www.jd.com時,會看到一個http://ccc.jd.com/cookie_check的請求用來清理Cookie的;對于這種應用非常簡單,不需要依賴資料源,直接單應用閉環即可。

2.2、第二張圖,是讀取本機檔案系統,如靜态資源合并:比如通路​​http://item.jd.com/1856584.html​​,檢視源碼會發現【<link type="text/css" rel="stylesheet" href="​​//misc.360buyimg.com/jdf/1.0.0/unit/??ui-base/1.0.0/ui-base.css,shortcut/2.0.0/shortcut.css,global-header/1.0.0/global-header.css,myjd/2.0.0/myjd.css,nav/2.0.0/nav.css,shoppingcart/2.0.0/shoppingcart.css,global-footer/1.0.0/global-footer.css,service/1.0.0/service.css​​"/>】這種請求,即多個請求合并為一個發給服務端,服務端進行了檔案資源的合并;

目前有成熟的Nginx子產品如​​nginx-http-concat​​進行靜态資源合并;因為我們使用了OpenResty,那麼我們完全可以使用Lua編寫程式實作該功能,比如已經有人寫了​​nginx-lua-static-merger​​來實作這個功能。

還一些業務型應用場景如下圖所示

商品頁面是由商品架構和其他次元的頁面片段(面包屑、相關分類、商家資訊、規格參數、商品詳情)組成;或者首頁是由首頁架構和一些頁面片段(分類、輪播圖、樓層1、樓層N)組成;分次元是因為不同的次元是獨立變化的。對于這種靜态内容但是需要進行架構内容嵌入的方式,Nginx自帶的SSI(Server Side Include)可以很輕松的完成;也可以使用Lua程式更靈活的完成(讀取架構、讀取頁面片段、合并輸出)。

比如商品頁面的架構我們可以這樣:

首先接收到商品變更消息,商品頁面同步Worker會根據消息次元生成相關的頁面推送到Nginx伺服器;Nginx應用再通過SSI輸出。目前京東商品詳情頁沒有再采用這種架構,具體架構可以參考《​​建構需求響應式億級商品詳情頁​​》。

對于首頁的架構是類似的,因為其特點(架構變化少,樓層變化較頻繁)和個性化的要求,樓層一般實作為異步加載。

2.3、 第三張圖和第二張圖的不同處是不再直接讀取檔案系統,而是讀取本機的Redis或者Redis叢集或者如SSDB這種持久化存儲或者其他存儲系統都是可以的,比如直接說的商品頁面可以使用SSDB進行存儲實作。檔案系統一個很大的問題是當多台伺服器時需要Worker去寫多台伺服器,而這個過程可以使用SSDB的主從實作。

 此處可以看到,不管是圖二還是圖三架構,都需要Worker去進行資料推送;假設本機資料丢了可怎麼辦?是以實際大部分應用不會是完全單機閉環的,而是會采用如下架構:

即首先讀本機,如果沒資料會回源到相應的Web應用從資料源拉取原始資料進行處理。這種架構的大部分場景本機都可以命中資料,隻有很少一部分情況會回源到Web應用。

如京東的實時價格/動态服務就是采用類似架構。

3、分布式閉環

單機閉環會遇到如下兩個主要問題: 1、資料不一緻問題(比如沒有采用主從架構導緻不同伺服器資料不一緻);2、遇到存儲瓶頸(磁盤或者記憶體遇到了天花闆)。

解決資料不一緻的比較好的辦法是采用主從或者分布式集中存儲;而遇到存儲瓶頸就需要進行按照業務鍵進行分片,将資料分散到多台伺服器。

如采用如下架構,按照尾号将内容分布到多台伺服器。

即第一步先讀取分布式存儲(JIMDB是京東的一個分布式緩存/存儲系統,類似于Redis);如果不命中則回源到Tomcat叢集(其會調用資料庫、服務總線擷取相關資料)來擷取相關資料。可以參考《​​建構需求響應式億級商品詳情頁​​》來擷取更詳細的架構實作。

JIMDB叢集會進行多機房主從同步,各自機房讀取自己機房的從JIMDB叢集,如下圖

4、接入網關

接入網關也可以叫做接入層,即接收到流量的入口,在入口我們可以進行如下事情:

4.1、核心接入Nginx會做如下事情:

1、動态負載均衡;1、普通流量走一緻性哈希,提升命中率;熱點流量走輪訓減少單伺服器壓力;2、根據請求特征将流量配置設定到不同分組并限流(爬蟲、或者流量大的IP);3、動态流量(動态增加upstream或者減少upstream或者動态負載均衡)可以使用balancer_by_lua或者微網誌開源的upsync;

2、防DDOS攻擊限流:可以将請求日志推送到實時計算叢集,然後将需要限流的IP推送到核心Nginx進行限流;

3、非法請求過濾:比如應該有Referer卻沒有,或者應該帶着Cookie卻沒有Cookie;

4、請求聚合:比如請求的是http://c.3.cn/proxy?methods=a,b,c,核心接入Nginx會在服務端把Nginx并發的請求并把結果聚合然後一次性吐出;

5、請求頭過濾:有些業務是不需要請求頭的,是以可以在往業務Nginx轉發時把這些資料過濾掉;

6、緩存服務:使用Nginx Proxy Cache實作内容頁面的緩存;

4.2、業務Nginx會做如下事情:

1、緩存:對于讀服務會使用大量的緩存來提升性能,我們在設計時主要有如下緩存應用:首先讀取Nginx本地緩存  Shared Dict或者Nginx Proxy Cache,如果有直接傳回内容給使用者;如果本地緩存不命中,則會讀取分布式緩存如Redis,如果有直接傳回;如果還是不命中則回源到Tomcat應用讀取DB或調用服務擷取資料。另外我們會按照次元進行資料的緩存。

2、業務邏輯:我們會進行一些資料校驗/過濾邏輯前置(如商品ID必須是數字)、業務邏輯前置(擷取原子資料,然後在Nginx上寫業務邏輯)。

3、細粒度限流:按照接口特征和接口吞吐量來實作動态限流,比如後端服務快扛不住了,那我們就需要進行限流,被限流的請求作為降級請求處理;通過lua-resty-limit-traffic可以通過程式設計實作更靈活的降級邏輯,如根據使用者、根據URL等等各種規則,如降級了是讓使用者請求等待(比如sleep 100ms,這樣使用者請求就慢下來了,但是服務還是可用)還是傳回降級内容。

4、降級:降級主要有兩種:主動降級和被動降級;如請求量太大扛不住了,那我們需要主動降級;如後端挂了或者被限流了或者後端逾時了,那我們需要被動降級。降級方案可以是:1、傳回預設資料如庫存預設有貨;2、傳回靜态頁如預先生成的靜态頁;3、部分使用者降級,告訴部分使用者等待下再操作;4、直接降級,服務沒資料,比如商品頁面的規格參數不展示;5、隻降級回源服務,即可以讀取緩存的資料傳回,實作部分可用,但是不會回源處理;

5、AB測試/灰階釋出:比如要上一個新的接口,可以通過在業務Nginx通過Lua寫複雜的業務規則實作不同的人看到不同的版本。

6、服務品質監控:我們可以記錄請求響應時間、緩存響應時間、反向代理服務響應時間來詳細了解到底哪塊服務慢了;另外記錄非200狀态碼錯誤來了解服務的可用率。

京東的交易大Nginx節點、無線部門正在開發的無線Nginx網關、和單品頁統一服務都是接入網關的實踐,而單品頁統一服務架構可以參考《京東商品詳情頁服務閉環實踐》。

5、Web應用

此處所說的Web應用指的是頁面模闆渲染類型應用或者API服務類型應用;比如京東清單頁/商品詳情頁就是一個模闆渲染類型的應用,核心業務邏輯都是使用Lua寫的,部署到Nginx容器。目前核心業務代碼行數有5000多行,模闆頁面有2000多行,涉及到大量的計算邏輯,性能資料可以參考《建構需求響應式億級商品詳情頁》。

整體處理過程和普通Web應用沒什麼差別:首先接收請求并進行解析;然後讀取JIMDB叢集資料、如果沒有則回源到Tomcat擷取;然後進行業務邏輯處理;渲染模闆;将響應内容傳回給使用者。

繼續閱讀