日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Nginx >内容正文

Nginx

用Nginx+Lua(OpenResty)开发高性能Web应用

發(fā)布時(shí)間:2025/3/21 Nginx 91 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用Nginx+Lua(OpenResty)开发高性能Web应用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在互聯(lián)網(wǎng)公司,Nginx可以說是標(biāo)配組件,但是主要場景還是負(fù)載均衡、反向代理、代理緩存、限流等場景;而把Nginx作為一個(gè)Web容器使用的還不是那么廣泛。Nginx的高性能是大家公認(rèn)的,而Nginx開發(fā)主要是以C/C++模塊的形式進(jìn)行,整體學(xué)習(xí)和開發(fā)成本偏高;如果有一種簡單的語言來實(shí)現(xiàn)Web應(yīng)用的開發(fā),那么Nginx絕對是把好的瑞士軍刀;目前Nginx團(tuán)隊(duì)也開始意識到這個(gè)問題,開發(fā)了nginxScript:可以在Nginx中使用JavaScript進(jìn)行動態(tài)配置一些變量和動態(tài)腳本執(zhí)行;而目前市面上用的非常成熟的擴(kuò)展是由章亦春將Lua和Nginx粘合的ngx_lua模塊,并且將Nginx核心、LuaJIT、ngx_lua模塊、許多有用的Lua庫和常用的第三方Nginx模塊組合在一起成為OpenResty,這樣開發(fā)人員就可以安裝OpenResty,使用Lua編寫腳本,然后部署到Nginx Web容器中運(yùn)行。從而非常輕松就能開發(fā)出高性能的Web服務(wù)。

?

接下來我們就認(rèn)識下Nginx、Lua、ngx_lua模塊和ngx_lua到底能開發(fā)哪些類型的web應(yīng)用。

?

?

一、ngx_lua簡介

1、Nginx優(yōu)點(diǎn)

Nginx設(shè)計(jì)為一個(gè)主進(jìn)程多個(gè)工作進(jìn)程的工作模式,每個(gè)進(jìn)程是單線程來處理多個(gè)連接,而且每個(gè)工作進(jìn)程采用了非阻塞I/O來處理多個(gè)連接,從而減少了線程上下文切換,從而實(shí)現(xiàn)了公認(rèn)的高性能、高并發(fā);因此在生成環(huán)境中會通過把CPU綁定給Nginx工作進(jìn)程從而提升其性能;另外因?yàn)閱尉€程工作模式的特點(diǎn),內(nèi)存占用就非常少了。

Nginx更改配置重啟速度非常快,可以毫秒級,而且支持不停止Nginx進(jìn)行升級Nginx版本、動態(tài)重載Nginx配置。

Nginx模塊也是非常多,功能也很強(qiáng)勁,不僅可以作為http負(fù)載均衡,Nginx發(fā)布1.9.0版本還支持TCP負(fù)載均衡,還可以很容易的實(shí)現(xiàn)內(nèi)容緩存、web服務(wù)器、反向代理、訪問控制等功能。

?

2、Lua的優(yōu)點(diǎn)

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

因?yàn)長ua的小巧輕量級,可以在Nginx中嵌入Lua VM,請求的時(shí)候創(chuàng)建一個(gè)VM,請求結(jié)束的時(shí)候回收VM。

?

3、什么是ngx_lua

ngx_lua是Nginx的一個(gè)模塊,將Lua嵌入到Nginx中,從而可以使用Lua來編寫腳本,這樣就可以使用Lua編寫應(yīng)用腳本,部署到Nginx中運(yùn)行,即Nginx變成了一個(gè)Web容器;這樣開發(fā)人員就可以使用Lua語言開發(fā)高性能Web應(yīng)用了。

ngx_lua提供了與Nginx交互的很多的API,對于開發(fā)人員來說只需要學(xué)習(xí)這些API就可以進(jìn)行功能開發(fā),而對于開發(fā)web應(yīng)用來說,如果接觸過Servlet的話,其開發(fā)和Servlet類似,無外乎就是知道接收請求、參數(shù)解析、功能處理、返回響應(yīng)這幾步的API是什么樣子的。

?

4、開發(fā)環(huán)境

我們可以使用OpenResty來搭建開發(fā)環(huán)境,OpenResty將Nginx核心、LuaJIT、許多有用的Lua庫和Nginx第三方模塊打包在一起;這樣開發(fā)人員只需要安裝OpenResty,不需要了解Nginx核心和寫復(fù)雜的C/C++模塊就可以,只需要使用Lua語言進(jìn)行Web應(yīng)用開發(fā)了。

如何安裝可以參考《跟我學(xué)Nginx+Lua開發(fā)》。

?

5、OpenResty生態(tài)

OpenResty提供了一些常用的ngx_lua開發(fā)模塊:如

? lua-resty-memcached

? lua-resty-mysql

? lua-resty-redis

? lua-resty-dns

? lua-resty-limit-traffic

? lua-resty-template

這些模塊涉及到如mysql數(shù)據(jù)庫、redis、限流、模塊渲染等常用功能組件;另外也有很多第三方的ngx_lua組件供我們使用,對于大部分應(yīng)用場景來說現(xiàn)在生態(tài)環(huán)境中的組件已經(jīng)足夠多了;如果不滿足需求也可以自己去寫來完成自己的需求。

?

6、場景

理論上可以使用ngx_lua開發(fā)各種復(fù)雜的web應(yīng)用,不過Lua是一種腳本/動態(tài)語言,不適合業(yè)務(wù)邏輯比較重的場景,適合小巧的應(yīng)用場景,代碼行數(shù)保持在幾十行到幾千行。目前見到的一些應(yīng)用場景:

web應(yīng)用:會進(jìn)行一些業(yè)務(wù)邏輯處理,甚至進(jìn)行耗CPU的模板渲染,一般流程:mysql/redis/http獲取數(shù)據(jù)、業(yè)務(wù)處理、產(chǎn)生JSON/XML/模板渲染內(nèi)容,比如京東的列表頁/商品詳情頁;

接入網(wǎng)關(guān):實(shí)現(xiàn)如數(shù)據(jù)校驗(yàn)前置、緩存前置、數(shù)據(jù)過濾、API請求聚合、AB測試、灰度發(fā)布、降級、監(jiān)控等功能,比如京東的交易大Nginx節(jié)點(diǎn)、無線部門正在開發(fā)的無線網(wǎng)關(guān)、單品頁統(tǒng)一服務(wù)、實(shí)時(shí)價(jià)格、動態(tài)服務(wù);

Web防火墻:可以進(jìn)行IP/URL/UserAgent/Referer黑名單、限流等功能;

緩存服務(wù)器:可以對響應(yīng)內(nèi)容進(jìn)行緩存,減少到后端的請求,從而提升性能;

其他:如靜態(tài)資源服務(wù)器、消息推送服務(wù)、縮略圖裁剪等。

?

二、基于Nginx+Lua的常用架構(gòu)模式

1、負(fù)載均衡

?

如上圖,我們首先通過LVS+HAProxy將流量轉(zhuǎn)發(fā)給核心Nginx 1和核心Nginx 2,即實(shí)現(xiàn)了流量的負(fù)載均衡,此處可以使用如輪訓(xùn)、一致性哈希等調(diào)度算法來實(shí)現(xiàn)負(fù)載的轉(zhuǎn)發(fā);然后核心Nginx會根據(jù)請求特征如“Host:item.jd.com”,轉(zhuǎn)發(fā)給相應(yīng)的業(yè)務(wù)Nginx節(jié)點(diǎn)如單品頁Nginx 1。此處為什么分兩層呢?

1、核心Nginx層是無狀態(tài)的,可以在這一層實(shí)現(xiàn)流量分組(內(nèi)網(wǎng)和外網(wǎng)隔離、爬蟲和非爬蟲流量隔離)、內(nèi)容緩存、請求頭過濾、故障切換(機(jī)房故障切換到其他機(jī)房)、限流、防火墻等一些通用型功能;

2、業(yè)務(wù)Nginx如單品頁Nginx,可以在在業(yè)務(wù)Nginx實(shí)現(xiàn)業(yè)務(wù)邏輯、或者反向代理到如Tomcat,在這一層可以實(shí)現(xiàn)內(nèi)容壓縮(放在這一層的目的是減少核心Nginx的CPU壓力,將壓力分散到各業(yè)務(wù)Nginx)、AB測試、降級;即這一層的Nginx跟業(yè)務(wù)有關(guān)聯(lián),實(shí)現(xiàn)業(yè)務(wù)的一些通用邏輯。

不管是核心Nginx還是業(yè)務(wù)Nginx,都應(yīng)該是無狀態(tài)設(shè)計(jì),可以水平擴(kuò)容。



?

業(yè)務(wù)Nginx一般會把請求直接轉(zhuǎn)發(fā)給后端的業(yè)務(wù)應(yīng)用,如Tomcat、PHP,即將請求內(nèi)部轉(zhuǎn)發(fā)到相應(yīng)的業(yè)務(wù)應(yīng)用;當(dāng)有的Tomcat出現(xiàn)問題了,可以在這一層摘掉;或者有的業(yè)務(wù)路徑變了在這一層進(jìn)行rewrite;或者有的后端Tomcat壓力太大也可以在這一層降級,減少對后端的沖擊;或者業(yè)務(wù)需要灰度發(fā)布時(shí)也可以在這一層Nginx上控制。

?

2、單機(jī)閉環(huán)

所謂單機(jī)閉環(huán)即所有想要的數(shù)據(jù)都能從本服務(wù)器直接獲取,在大多數(shù)時(shí)候無需通過網(wǎng)絡(luò)去其他服務(wù)器獲取。



?

如上所示,主要有三種應(yīng)用模式:

2.1、第一張圖應(yīng)用場景是Nginx應(yīng)用誰也不依賴,比如我們的Cookie白名單應(yīng)用,其目的是不在白名單中的Cookie將被清理,防止大家隨便將Cookie寫到j(luò)d.om根下;大家訪問http://www.jd.com時(shí),會看到一個(gè)http://ccc.jd.com/cookie_check的請求用來清理Cookie的;對于這種應(yīng)用非常簡單,不需要依賴數(shù)據(jù)源,直接單應(yīng)用閉環(huán)即可。

?

2.2、第二張圖,是讀取本機(jī)文件系統(tǒng),如靜態(tài)資源合并:比如訪問http://item.jd.com/1856584.html,查看源碼會發(fā)現(xiàn)【<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"/>】這種請求,即多個(gè)請求合并為一個(gè)發(fā)給服務(wù)端,服務(wù)端進(jìn)行了文件資源的合并;


?

目前有成熟的Nginx模塊如nginx-http-concat進(jìn)行靜態(tài)資源合并;因?yàn)槲覀兪褂昧薕penResty,那么我們完全可以使用Lua編寫程序?qū)崿F(xiàn)該功能,比如已經(jīng)有人寫了nginx-lua-static-merger來實(shí)現(xiàn)這個(gè)功能。

?

還一些業(yè)務(wù)型應(yīng)用場景如下圖所示


?

商品頁面是由商品框架和其他維度的頁面片段(面包屑、相關(guān)分類、商家信息、規(guī)格參數(shù)、商品詳情)組成;或者首頁是由首頁框架和一些頁面片段(分類、輪播圖、樓層1、樓層N)組成;分維度是因?yàn)椴煌木S度是獨(dú)立變化的。對于這種靜態(tài)內(nèi)容但是需要進(jìn)行框架內(nèi)容嵌入的方式,Nginx自帶的SSI(Server Side Include)可以很輕松的完成;也可以使用Lua程序更靈活的完成(讀取框架、讀取頁面片段、合并輸出)。

?

比如商品頁面的架構(gòu)我們可以這樣:


?

首先接收到商品變更消息,商品頁面同步Worker會根據(jù)消息維度生成相關(guān)的頁面推送到Nginx服務(wù)器;Nginx應(yīng)用再通過SSI輸出。目前京東商品詳情頁沒有再采用這種架構(gòu),具體架構(gòu)可以參考《構(gòu)建需求響應(yīng)式億級商品詳情頁》。

?

對于首頁的架構(gòu)是類似的,因?yàn)槠涮攸c(diǎn)(框架變化少,樓層變化較頻繁)和個(gè)性化的要求,樓層一般實(shí)現(xiàn)為異步加載。

?

2.3、 第三張圖和第二張圖的不同處是不再直接讀取文件系統(tǒng),而是讀取本機(jī)的Redis或者Redis集群或者如SSDB這種持久化存儲或者其他存儲系統(tǒng)都是可以的,比如直接說的商品頁面可以使用SSDB進(jìn)行存儲實(shí)現(xiàn)。文件系統(tǒng)一個(gè)很大的問題是當(dāng)多臺服務(wù)器時(shí)需要Worker去寫多臺服務(wù)器,而這個(gè)過程可以使用SSDB的主從實(shí)現(xiàn)。



?此處可以看到,不管是圖二還是圖三架構(gòu),都需要Worker去進(jìn)行數(shù)據(jù)推送;假設(shè)本機(jī)數(shù)據(jù)丟了可怎么辦?因此實(shí)際大部分應(yīng)用不會是完全單機(jī)閉環(huán)的,而是會采用如下架構(gòu):

?





???

即首先讀本機(jī),如果沒數(shù)據(jù)會回源到相應(yīng)的Web應(yīng)用從數(shù)據(jù)源拉取原始數(shù)據(jù)進(jìn)行處理。這種架構(gòu)的大部分場景本機(jī)都可以命中數(shù)據(jù),只有很少一部分情況會回源到Web應(yīng)用。

?

如京東的實(shí)時(shí)價(jià)格/動態(tài)服務(wù)就是采用類似架構(gòu)。

?

3、分布式閉環(huán)

單機(jī)閉環(huán)會遇到如下兩個(gè)主要問題: 1、數(shù)據(jù)不一致問題(比如沒有采用主從架構(gòu)導(dǎo)致不同服務(wù)器數(shù)據(jù)不一致);2、遇到存儲瓶頸(磁盤或者內(nèi)存遇到了天花板)。

解決數(shù)據(jù)不一致的比較好的辦法是采用主從或者分布式集中存儲;而遇到存儲瓶頸就需要進(jìn)行按照業(yè)務(wù)鍵進(jìn)行分片,將數(shù)據(jù)分散到多臺服務(wù)器。

?

如采用如下架構(gòu),按照尾號將內(nèi)容分布到多臺服務(wù)器。


?

即第一步先讀取分布式存儲(JIMDB是京東的一個(gè)分布式緩存/存儲系統(tǒng),類似于Redis);如果不命中則回源到Tomcat集群(其會調(diào)用數(shù)據(jù)庫、服務(wù)總線獲取相關(guān)數(shù)據(jù))來獲取相關(guān)數(shù)據(jù)。可以參考《構(gòu)建需求響應(yīng)式億級商品詳情頁》來獲取更詳細(xì)的架構(gòu)實(shí)現(xiàn)。

?

JIMDB集群會進(jìn)行多機(jī)房主從同步,各自機(jī)房讀取自己機(jī)房的從JIMDB集群,如下圖


?

?

4、接入網(wǎng)關(guān)

接入網(wǎng)關(guān)也可以叫做接入層,即接收到流量的入口,在入口我們可以進(jìn)行如下事情:


?

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

1、動態(tài)負(fù)載均衡;1、普通流量走一致性哈希,提升命中率;熱點(diǎn)流量走輪訓(xùn)減少單服務(wù)器壓力;2、根據(jù)請求特征將流量分配到不同分組并限流(爬蟲、或者流量大的IP);3、動態(tài)流量(動態(tài)增加upstream或者減少upstream或者動態(tài)負(fù)載均衡)可以使用balancer_by_lua或者微博開源的upsync;

2、防DDOS攻擊限流:可以將請求日志推送到實(shí)時(shí)計(jì)算集群,然后將需要限流的IP推送到核心Nginx進(jìn)行限流;

3、非法請求過濾:比如應(yīng)該有Referer卻沒有,或者應(yīng)該帶著Cookie卻沒有Cookie;

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

5、請求頭過濾:有些業(yè)務(wù)是不需要請求頭的,因此可以在往業(yè)務(wù)Nginx轉(zhuǎn)發(fā)時(shí)把這些數(shù)據(jù)過濾掉;

6、緩存服務(wù):使用Nginx Proxy Cache實(shí)現(xiàn)內(nèi)容頁面的緩存;

?

4.2、業(yè)務(wù)Nginx會做如下事情:

1、緩存:對于讀服務(wù)會使用大量的緩存來提升性能,我們在設(shè)計(jì)時(shí)主要有如下緩存應(yīng)用:首先讀取Nginx本地緩存? Shared Dict或者Nginx Proxy Cache,如果有直接返回內(nèi)容給用戶;如果本地緩存不命中,則會讀取分布式緩存如Redis,如果有直接返回;如果還是不命中則回源到Tomcat應(yīng)用讀取DB或調(diào)用服務(wù)獲取數(shù)據(jù)。另外我們會按照維度進(jìn)行數(shù)據(jù)的緩存。

2、業(yè)務(wù)邏輯:我們會進(jìn)行一些數(shù)據(jù)校驗(yàn)/過濾邏輯前置(如商品ID必須是數(shù)字)、業(yè)務(wù)邏輯前置(獲取原子數(shù)據(jù),然后在Nginx上寫業(yè)務(wù)邏輯)。

3、細(xì)粒度限流:按照接口特征和接口吞吐量來實(shí)現(xiàn)動態(tài)限流,比如后端服務(wù)快扛不住了,那我們就需要進(jìn)行限流,被限流的請求作為降級請求處理;通過lua-resty-limit-traffic可以通過編程實(shí)現(xiàn)更靈活的降級邏輯,如根據(jù)用戶、根據(jù)URL等等各種規(guī)則,如降級了是讓用戶請求等待(比如sleep 100ms,這樣用戶請求就慢下來了,但是服務(wù)還是可用)還是返回降級內(nèi)容。

4、降級:降級主要有兩種:主動降級和被動降級;如請求量太大扛不住了,那我們需要主動降級;如后端掛了或者被限流了或者后端超時(shí)了,那我們需要被動降級。降級方案可以是:1、返回默認(rèn)數(shù)據(jù)如庫存默認(rèn)有貨;2、返回靜態(tài)頁如預(yù)先生成的靜態(tài)頁;3、部分用戶降級,告訴部分用戶等待下再操作;4、直接降級,服務(wù)沒數(shù)據(jù),比如商品頁面的規(guī)格參數(shù)不展示;5、只降級回源服務(wù),即可以讀取緩存的數(shù)據(jù)返回,實(shí)現(xiàn)部分可用,但是不會回源處理;

5、AB測試/灰度發(fā)布:比如要上一個(gè)新的接口,可以通過在業(yè)務(wù)Nginx通過Lua寫復(fù)雜的業(yè)務(wù)規(guī)則實(shí)現(xiàn)不同的人看到不同的版本。

6、服務(wù)質(zhì)量監(jiān)控:我們可以記錄請求響應(yīng)時(shí)間、緩存響應(yīng)時(shí)間、反向代理服務(wù)響應(yīng)時(shí)間來詳細(xì)了解到底哪塊服務(wù)慢了;另外記錄非200狀態(tài)碼錯(cuò)誤來了解服務(wù)的可用率。

?

京東的交易大Nginx節(jié)點(diǎn)、無線部門正在開發(fā)的無線Nginx網(wǎng)關(guān)、和單品頁統(tǒng)一服務(wù)都是接入網(wǎng)關(guān)的實(shí)踐,而單品頁統(tǒng)一服務(wù)架構(gòu)可以參考《京東商品詳情頁服務(wù)閉環(huán)實(shí)踐》。

?

5、Web應(yīng)用

此處所說的Web應(yīng)用指的是頁面模板渲染類型應(yīng)用或者API服務(wù)類型應(yīng)用;比如京東列表頁/商品詳情頁就是一個(gè)模板渲染類型的應(yīng)用,核心業(yè)務(wù)邏輯都是使用Lua寫的,部署到Nginx容器。目前核心業(yè)務(wù)代碼行數(shù)有5000多行,模板頁面有2000多行,涉及到大量的計(jì)算邏輯,性能數(shù)據(jù)可以參考《構(gòu)建需求響應(yīng)式億級商品詳情頁》。



?

整體處理過程和普通Web應(yīng)用沒什么區(qū)別:首先接收請求并進(jìn)行解析;然后讀取JIMDB集群數(shù)據(jù)、如果沒有則回源到Tomcat獲取;然后進(jìn)行業(yè)務(wù)邏輯處理;渲染模板;將響應(yīng)內(nèi)容返回給用戶。

?

三、如何使用Nginx+Lua開發(fā)Web應(yīng)用

開發(fā)一個(gè)Web應(yīng)用我們需要從項(xiàng)目搭建、功能開發(fā)、項(xiàng)目部署幾個(gè)層面完成。

?

3.1、項(xiàng)目搭建?

Java代碼 ?

  • /export/App/nginx-app ??
  • ?-------bin(腳本) ??
  • ?------------start.sh ??
  • ?------------stop.sh ??
  • ?-------config(配置文件) ??
  • ?------------nginx.conf ??
  • ?------------domain ??
  • ?----------------nginx_product.conf ??
  • ?------------resources.properties ??
  • ?-------lua(業(yè)務(wù)代碼) ??
  • ?------------init.lua ??
  • ?------------product_controller.lua ??
  • ?-------template(模板) ??
  • ?--------------prodoct.html ??
  • ?-------lualib(公共Lua庫) ??
  • ?------------jd ??
  • ?----------------product_util.lua ??
  • ?----------------product_data.lua ??
  • ?------------resty ??
  • ?----------------redis.lua ??
  • ?----------------template.lua??
  • /export/App/nginx-app-------bin(腳本)------------start.sh------------stop.sh-------config(配置文件)------------nginx.conf------------domain----------------nginx_product.conf------------resources.properties-------lua(業(yè)務(wù)代碼)------------init.lua------------product_controller.lua-------template(模板)--------------prodoct.html-------lualib(公共Lua庫)------------jd----------------product_util.lua----------------product_data.lua------------resty----------------redis.lua----------------template.lua

    ??

    ?整個(gè)項(xiàng)目結(jié)構(gòu)從啟停腳本、配置文件、公共組件、業(yè)務(wù)代碼、模板代碼幾塊進(jìn)行劃分。

    ?

    1、啟停腳本

    啟停腳本放在項(xiàng)目目錄/export/App/nginx-app/bin/下。

    start.sh是啟動和更新腳本,即如果nginx沒有啟動則啟動起來,否則reload:?

    Java代碼 ?

  • if?nginx沒啟動?then ??
  • ??sudo?/export/servers/nginx/sbin/nginx??-t?-c?/export/App/nginx-app/config/nginx.conf ??
  • ??sudo?/export/servers/nginx/sbin/nginx??-c?/export/App/nginx-app/config/nginx.conf ??
  • else??
  • ??sudo?/export/servers/nginx/sbin/nginx??-t ??
  • ??sudo?/export/servers/nginx/sbin/nginx??-s?reload ??
  • end????
  • if nginx沒啟動 thensudo /export/servers/nginx/sbin/nginx -t -c /export/App/nginx-app/config/nginx.confsudo /export/servers/nginx/sbin/nginx -c /export/App/nginx-app/config/nginx.conf elsesudo /export/servers/nginx/sbin/nginx -tsudo /export/servers/nginx/sbin/nginx -s reload end ?

    stop.sh是停止Nginx腳本:?

    Java代碼 ?

  • sudo?/export/servers/nginx/sbin/nginx??-s?quit???
  • sudo /export/servers/nginx/sbin/nginx -s quit?

    ?

    2、配置文件

    配置文件放在/export/App/nginx-app/config目錄下,包括了nginx.conf配置文件、nginx項(xiàng)目配置文件和資源配置文件。

    ?

    nginx.confg配置文件??

    Java代碼 ?

  • worker_processes??1; ??
  • events?{ ??
  • ????worker_connections??1024; ??
  • } ??
  • http?{ ??
  • ???include???????mime.types; ??
  • ???default_type??text/html; ??
  • ???#gzip相關(guān) ??
  • ???#超時(shí)時(shí)間 ??
  • ???#日志格式 ??
  • ???#反向代理配置 ??
  • ??
  • ???#lua依賴路徑 ??
  • ???lua_package_path??"/export/App/nginx-app/lualib/?.lua;;"; ??
  • ???lua_package_cpath??"/export/App/nginx-app/lualib/?.so;;"; ??
  • ??
  • ???#server配置 ??
  • ???include?/export/App/nginx-app/config/domains/*; ??
  • ??
  • ???#初始化腳本 ??
  • ???init_by_lua_file?"/export/App/nginx-app/lua/init.lua"; ??
  • }???
  • worker_processes 1; events {worker_connections 1024; } http {include mime.types;default_type text/html;#gzip相關(guān)#超時(shí)時(shí)間#日志格式#反向代理配置#lua依賴路徑lua_package_path "/export/App/nginx-app/lualib/?.lua;;";lua_package_cpath "/export/App/nginx-app/lualib/?.so;;";#server配置include /export/App/nginx-app/config/domains/*;#初始化腳本init_by_lua_file "/export/App/nginx-app/lua/init.lua"; }?

    對于nginx.conf會進(jìn)行一些通用的配置,如工作進(jìn)程數(shù)、超時(shí)時(shí)間、壓縮、日志格式、反向代理等相關(guān)配置;另外需要指定如下配置:

    lua_package_path、lua_package_cpath指定我們依賴的通用Lua庫從哪里加載;

    include /export/App/nginx-app/config/domains/*:用于加載server相關(guān)的配置,此處通過*可以在一個(gè)nginx下指定多個(gè)server配置;

    init_by_lua_file "/export/App/nginx-app/lua/init.lua":執(zhí)行項(xiàng)目的一些初始化配置,比如加載配置文件。

    ?

    nginx項(xiàng)目配置文件

    /export/App/nginx-app/config/domains/nginx_product.conf用于配置當(dāng)前web應(yīng)用的一些server相關(guān)的配置:?

    Java代碼 ?

  • #upstream ??
  • upstream?item_http_upstream?{ ??
  • ????server?192.168.1.1?max_fails=2?fail_timeout=30s?weight=5; ??
  • ????server?192.168.1.2?max_fails=2?fail_timeout=30s?weight=5; ??
  • } ??
  • #緩存 ??
  • lua_shared_dict?item_local_shop_cache?600m; ??
  • server?{ ??
  • ?????listen???????????????????80; ??
  • ?????server_name??????????????item.jd.com?item.jd.hk; ??
  • ?????#模板文件從哪加載 ??
  • ????set?$template_root?"/export/App/nginx-app/template?"; ??
  • ?????#url映射 ??
  • ????????location?~*?"^/product/(\d+)\.html$"?{ ??
  • ????????????rewrite?/product/(.*)????http://item.jd.com/$1?permanent; ??
  • ????????} ??
  • ????location?~*?"^/(\d{6,12})\.html$"?{ ??
  • ????????????default_type?text/html; ??
  • ????????????charset?gbk; ??
  • ????????????lua_code_cache?on; ??
  • ????????????content_by_lua_file?"/export/App/nginx-app/lua/product_controller.lua"; ??
  • ????????} ??
  • }???
  • #upstream upstream item_http_upstream {server 192.168.1.1 max_fails=2 fail_timeout=30s weight=5;server 192.168.1.2 max_fails=2 fail_timeout=30s weight=5; } #緩存 lua_shared_dict item_local_shop_cache 600m; server {listen 80;server_name item.jd.com item.jd.hk;#模板文件從哪加載set $template_root "/export/App/nginx-app/template ";#url映射location ~* "^/product/(\d+)\.html$" {rewrite /product/(.*) http://item.jd.com/$1 permanent;}location ~* "^/(\d{6,12})\.html$" {default_type text/html;charset gbk;lua_code_cache on;content_by_lua_file "/export/App/nginx-app/lua/product_controller.lua";} }?

    我們需要指定如upstream、共享字典配置、server配置、模板文件從哪加載、url映射,比如我們訪問http://item.jd.com/1856584.html將交給/export/App/nginx-app/lua/product_controller.lua處理;也就是說我們項(xiàng)目的入口就有了。

    ?

    資源配置文件resources.properties包含了我們的一些比如開關(guān)的配置、緩存服務(wù)器地址的配置等等。

    ?

    3、業(yè)務(wù)代碼

    /export/App/nginx-app/lua/目錄里存放了我們的lua業(yè)務(wù)代碼,init.lua用于讀取如resources.properties來進(jìn)行一些項(xiàng)目初始化;product_controller.lua可以看成Java Web中的Servlet,接收、處理、響應(yīng)用戶請求。

    ?

    4、模板

    模板文件放在/export/App/nginx-app/template/目錄下,使用相應(yīng)的模板引擎進(jìn)行編寫頁面模板,然后渲染輸出。

    ?

    5、公共Lua庫

    存放了一些如redis、template等相關(guān)的公共Lua庫,還有一些我們項(xiàng)目中通用的工具庫如product_util.lua。

    ?

    到此一個(gè)簡單的項(xiàng)目的結(jié)構(gòu)就介紹完了,對于開發(fā)一個(gè)項(xiàng)目來說還會牽扯到分模塊等工作,不過對于我們這種Lua應(yīng)用來說,建議不要過度抽象,盡量小巧即可。

    ?

    3.2、功能開發(fā)

    接下來就需要使用相應(yīng)的API來實(shí)現(xiàn)我們的業(yè)務(wù)了,比如product_controller.lua:

    Java代碼 ?

  • --加載Lua模塊庫 ??
  • local?template?=?require("resty.template")?? ??
  • --1、獲取請求參數(shù)中的商品ID ??
  • local?skuId?=?ngx.req.get_uri_args()["skuId"]; ??
  • --2、調(diào)用相應(yīng)的服務(wù)獲取數(shù)據(jù) ??
  • local?data?=?api.getData(skuId) ??
  • ??
  • --3、渲染模板 ??
  • local?func?=?template.compile("product.html")?? ??
  • local?content?=?func(data)?? ??
  • --4、通過ngx?API輸出內(nèi)容?? ??
  • ngx.say(content)?????
  • --加載Lua模塊庫 local template = require("resty.template") --1、獲取請求參數(shù)中的商品ID local skuId = ngx.req.get_uri_args()["skuId"]; --2、調(diào)用相應(yīng)的服務(wù)獲取數(shù)據(jù) local data = api.getData(skuId)--3、渲染模板 local func = template.compile("product.html") local content = func(data) --4、通過ngx API輸出內(nèi)容 ngx.say(content) ?

    開發(fā)完成后將項(xiàng)目部署到測試環(huán)境,執(zhí)行start.sh啟動nginx然后進(jìn)行測試。

    詳細(xì)的開發(fā)過程和API的使用,請參考《跟我學(xué)Nginx+Lua開發(fā)》。此處不做具體編碼實(shí)現(xiàn)。

    ?

    參考源碼:Nginx+Lua(OpenResty) HelloWorld

    ?

    四、基于Nginx+Lua的常用功能總結(jié)

    到此我們對于Nginx開發(fā)已經(jīng)有了一個(gè)整體的認(rèn)識,對于Nginx粘合Lua來開發(fā)應(yīng)用可以說是一把鋒利的瑞士軍刀,可以幫我們很容易的解決很多問題,可以開發(fā)Web應(yīng)用、接入網(wǎng)關(guān)、API網(wǎng)關(guān)、消息推送、日志采集等應(yīng)用,不過個(gè)人認(rèn)為適合開發(fā)業(yè)務(wù)邏輯單一、核心代碼行數(shù)較少的應(yīng)用,不適合業(yè)務(wù)邏輯復(fù)雜、功能繁多的業(yè)務(wù)型或者企業(yè)級應(yīng)用;最后我們總結(jié)下基于Nginx+Lua的常用架構(gòu)模式中一些常見實(shí)踐和場景:

    ? 動態(tài)負(fù)載均衡;

    ? 防火墻(DDOS、IP/URL/UserAgent/Referer黑名單、防盜鏈等)

    ? 限流;

    ? 降級;

    ? AB測試/灰度發(fā)布;

    ? 多級緩存模式;

    ? 服務(wù)端請求聚合;

    ? 服務(wù)質(zhì)量監(jiān)控。

    ?

    一些問題

    1、在開發(fā)nginx應(yīng)用時(shí)使用UTF-8編碼可以減去很多麻煩;

    2、GBK轉(zhuǎn)碼解碼時(shí)使用GB18030,否則一些特殊字符會出現(xiàn)亂碼;

    3、cjson庫對于如\uab1這種錯(cuò)誤的unicode轉(zhuǎn)碼會失敗,可以使用純Lua編寫的dkjson;

    4、社區(qū)版nginx不支持upstream的域名動態(tài)解析;可以考慮proxy_pass http://p.3.local/prices/mgets$is_args$args,然后配合resolver來實(shí)現(xiàn);或者在lua中進(jìn)行http調(diào)用;如果DNS遇到性能瓶頸可以考慮在本機(jī)部署如dnsmasq來緩存;或者考慮使用balancer_by_lua功能實(shí)現(xiàn)動態(tài)upstream;

    5、為響應(yīng)添加處理服務(wù)器IP的響應(yīng)頭,方便定位問題;

    6、根據(jù)業(yè)務(wù)設(shè)置合理的超時(shí)時(shí)間;

    7、走CDN的業(yè)務(wù)當(dāng)發(fā)生錯(cuò)誤時(shí)返回的500/503/302/301等非正常響應(yīng)不要設(shè)置緩存。

    ?

    五、參考文資料

    深入 Nginx:我們是如何為性能和規(guī)模做設(shè)計(jì)的

    ??http://blog.jobbole.com/88766/

    Nginx變量漫談/配置指令的執(zhí)行順序

    ??http://blog.sina.com.cn/openresty

    ngx_lua文檔

    ??https://github.com/openresty/lua-nginx-module#readme

    OpenResty最佳實(shí)踐

    ??https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/brief.html

    跟我學(xué)Nginx+Lua開發(fā)

    ??http://jinnianshilongnian.iteye.com/blog/2190344

    構(gòu)建需求響應(yīng)式億級商品詳情頁

    ??http://jinnianshilongnian.iteye.com/blog/2235572

    京東商品詳情頁服務(wù)閉環(huán)實(shí)踐

    ??http://jinnianshilongnian.iteye.com/blog/2258111?

    Upsync:微博開源基于Nginx容器動態(tài)流量管理方案

    ??http://toutiao.com/a6254279391729139970

    摘自

    在互聯(lián)網(wǎng)公司,Nginx可以說是標(biāo)配組件,但是主要場景還是負(fù)載均衡、反向代理、代理緩存、限流等場景;而把Nginx作為一個(gè)Web容器使用的還不是那么廣泛。Nginx的高性能是大家公認(rèn)的,而Nginx開發(fā)主要是以C/C++模塊的形式進(jìn)行,整體學(xué)習(xí)和開發(fā)成本偏高;如果有一種簡單的語言來實(shí)現(xiàn)Web應(yīng)用的開發(fā),那么Nginx絕對是把好的瑞士軍刀;目前Nginx團(tuán)隊(duì)也開始意識到這個(gè)問題,開發(fā)了nginxScript:可以在Nginx中使用JavaScript進(jìn)行動態(tài)配置一些變量和動態(tài)腳本執(zhí)行;而目前市面上用的非常成熟的擴(kuò)展是由章亦春將Lua和Nginx粘合的ngx_lua模塊,并且將Nginx核心、LuaJIT、ngx_lua模塊、許多有用的Lua庫和常用的第三方Nginx模塊組合在一起成為OpenResty,這樣開發(fā)人員就可以安裝OpenResty,使用Lua編寫腳本,然后部署到Nginx Web容器中運(yùn)行。從而非常輕松就能開發(fā)出高性能的Web服務(wù)。

    ?

    接下來我們就認(rèn)識下Nginx、Lua、ngx_lua模塊和ngx_lua到底能開發(fā)哪些類型的web應(yīng)用。

    ?

    ?

    一、ngx_lua簡介

    1、Nginx優(yōu)點(diǎn)

    Nginx設(shè)計(jì)為一個(gè)主進(jìn)程多個(gè)工作進(jìn)程的工作模式,每個(gè)進(jìn)程是單線程來處理多個(gè)連接,而且每個(gè)工作進(jìn)程采用了非阻塞I/O來處理多個(gè)連接,從而減少了線程上下文切換,從而實(shí)現(xiàn)了公認(rèn)的高性能、高并發(fā);因此在生成環(huán)境中會通過把CPU綁定給Nginx工作進(jìn)程從而提升其性能;另外因?yàn)閱尉€程工作模式的特點(diǎn),內(nèi)存占用就非常少了。

    Nginx更改配置重啟速度非常快,可以毫秒級,而且支持不停止Nginx進(jìn)行升級Nginx版本、動態(tài)重載Nginx配置。

    Nginx模塊也是非常多,功能也很強(qiáng)勁,不僅可以作為http負(fù)載均衡,Nginx發(fā)布1.9.0版本還支持TCP負(fù)載均衡,還可以很容易的實(shí)現(xiàn)內(nèi)容緩存、web服務(wù)器、反向代理、訪問控制等功能。

    ?

    2、Lua的優(yōu)點(diǎn)

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

    因?yàn)長ua的小巧輕量級,可以在Nginx中嵌入Lua VM,請求的時(shí)候創(chuàng)建一個(gè)VM,請求結(jié)束的時(shí)候回收VM。

    ?

    3、什么是ngx_lua

    ngx_lua是Nginx的一個(gè)模塊,將Lua嵌入到Nginx中,從而可以使用Lua來編寫腳本,這樣就可以使用Lua編寫應(yīng)用腳本,部署到Nginx中運(yùn)行,即Nginx變成了一個(gè)Web容器;這樣開發(fā)人員就可以使用Lua語言開發(fā)高性能Web應(yīng)用了。

    ngx_lua提供了與Nginx交互的很多的API,對于開發(fā)人員來說只需要學(xué)習(xí)這些API就可以進(jìn)行功能開發(fā),而對于開發(fā)web應(yīng)用來說,如果接觸過Servlet的話,其開發(fā)和Servlet類似,無外乎就是知道接收請求、參數(shù)解析、功能處理、返回響應(yīng)這幾步的API是什么樣子的。

    ?

    4、開發(fā)環(huán)境

    我們可以使用OpenResty來搭建開發(fā)環(huán)境,OpenResty將Nginx核心、LuaJIT、許多有用的Lua庫和Nginx第三方模塊打包在一起;這樣開發(fā)人員只需要安裝OpenResty,不需要了解Nginx核心和寫復(fù)雜的C/C++模塊就可以,只需要使用Lua語言進(jìn)行Web應(yīng)用開發(fā)了。

    如何安裝可以參考《跟我學(xué)Nginx+Lua開發(fā)》。

    ?

    5、OpenResty生態(tài)

    OpenResty提供了一些常用的ngx_lua開發(fā)模塊:如

    ? lua-resty-memcached

    ? lua-resty-mysql

    ? lua-resty-redis

    ? lua-resty-dns

    ? lua-resty-limit-traffic

    ? lua-resty-template

    這些模塊涉及到如mysql數(shù)據(jù)庫、redis、限流、模塊渲染等常用功能組件;另外也有很多第三方的ngx_lua組件供我們使用,對于大部分應(yīng)用場景來說現(xiàn)在生態(tài)環(huán)境中的組件已經(jīng)足夠多了;如果不滿足需求也可以自己去寫來完成自己的需求。

    ?

    6、場景

    理論上可以使用ngx_lua開發(fā)各種復(fù)雜的web應(yīng)用,不過Lua是一種腳本/動態(tài)語言,不適合業(yè)務(wù)邏輯比較重的場景,適合小巧的應(yīng)用場景,代碼行數(shù)保持在幾十行到幾千行。目前見到的一些應(yīng)用場景:

    web應(yīng)用:會進(jìn)行一些業(yè)務(wù)邏輯處理,甚至進(jìn)行耗CPU的模板渲染,一般流程:mysql/redis/http獲取數(shù)據(jù)、業(yè)務(wù)處理、產(chǎn)生JSON/XML/模板渲染內(nèi)容,比如京東的列表頁/商品詳情頁;

    接入網(wǎng)關(guān):實(shí)現(xiàn)如數(shù)據(jù)校驗(yàn)前置、緩存前置、數(shù)據(jù)過濾、API請求聚合、AB測試、灰度發(fā)布、降級、監(jiān)控等功能,比如京東的交易大Nginx節(jié)點(diǎn)、無線部門正在開發(fā)的無線網(wǎng)關(guān)、單品頁統(tǒng)一服務(wù)、實(shí)時(shí)價(jià)格、動態(tài)服務(wù);

    Web防火墻:可以進(jìn)行IP/URL/UserAgent/Referer黑名單、限流等功能;

    緩存服務(wù)器:可以對響應(yīng)內(nèi)容進(jìn)行緩存,減少到后端的請求,從而提升性能;

    其他:如靜態(tài)資源服務(wù)器、消息推送服務(wù)、縮略圖裁剪等。

    ?

    二、基于Nginx+Lua的常用架構(gòu)模式

    1、負(fù)載均衡

    ?

    如上圖,我們首先通過LVS+HAProxy將流量轉(zhuǎn)發(fā)給核心Nginx 1和核心Nginx 2,即實(shí)現(xiàn)了流量的負(fù)載均衡,此處可以使用如輪訓(xùn)、一致性哈希等調(diào)度算法來實(shí)現(xiàn)負(fù)載的轉(zhuǎn)發(fā);然后核心Nginx會根據(jù)請求特征如“Host:item.jd.com”,轉(zhuǎn)發(fā)給相應(yīng)的業(yè)務(wù)Nginx節(jié)點(diǎn)如單品頁Nginx 1。此處為什么分兩層呢?

    1、核心Nginx層是無狀態(tài)的,可以在這一層實(shí)現(xiàn)流量分組(內(nèi)網(wǎng)和外網(wǎng)隔離、爬蟲和非爬蟲流量隔離)、內(nèi)容緩存、請求頭過濾、故障切換(機(jī)房故障切換到其他機(jī)房)、限流、防火墻等一些通用型功能;

    2、業(yè)務(wù)Nginx如單品頁Nginx,可以在在業(yè)務(wù)Nginx實(shí)現(xiàn)業(yè)務(wù)邏輯、或者反向代理到如Tomcat,在這一層可以實(shí)現(xiàn)內(nèi)容壓縮(放在這一層的目的是減少核心Nginx的CPU壓力,將壓力分散到各業(yè)務(wù)Nginx)、AB測試、降級;即這一層的Nginx跟業(yè)務(wù)有關(guān)聯(lián),實(shí)現(xiàn)業(yè)務(wù)的一些通用邏輯。

    不管是核心Nginx還是業(yè)務(wù)Nginx,都應(yīng)該是無狀態(tài)設(shè)計(jì),可以水平擴(kuò)容。



    ?

    業(yè)務(wù)Nginx一般會把請求直接轉(zhuǎn)發(fā)給后端的業(yè)務(wù)應(yīng)用,如Tomcat、PHP,即將請求內(nèi)部轉(zhuǎn)發(fā)到相應(yīng)的業(yè)務(wù)應(yīng)用;當(dāng)有的Tomcat出現(xiàn)問題了,可以在這一層摘掉;或者有的業(yè)務(wù)路徑變了在這一層進(jìn)行rewrite;或者有的后端Tomcat壓力太大也可以在這一層降級,減少對后端的沖擊;或者業(yè)務(wù)需要灰度發(fā)布時(shí)也可以在這一層Nginx上控制。

    ?

    2、單機(jī)閉環(huán)

    所謂單機(jī)閉環(huán)即所有想要的數(shù)據(jù)都能從本服務(wù)器直接獲取,在大多數(shù)時(shí)候無需通過網(wǎng)絡(luò)去其他服務(wù)器獲取。



    ?

    如上所示,主要有三種應(yīng)用模式:

    2.1、第一張圖應(yīng)用場景是Nginx應(yīng)用誰也不依賴,比如我們的Cookie白名單應(yīng)用,其目的是不在白名單中的Cookie將被清理,防止大家隨便將Cookie寫到j(luò)d.om根下;大家訪問http://www.jd.com時(shí),會看到一個(gè)http://ccc.jd.com/cookie_check的請求用來清理Cookie的;對于這種應(yīng)用非常簡單,不需要依賴數(shù)據(jù)源,直接單應(yīng)用閉環(huán)即可。

    ?

    2.2、第二張圖,是讀取本機(jī)文件系統(tǒng),如靜態(tài)資源合并:比如訪問http://item.jd.com/1856584.html,查看源碼會發(fā)現(xiàn)【<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"/>】這種請求,即多個(gè)請求合并為一個(gè)發(fā)給服務(wù)端,服務(wù)端進(jìn)行了文件資源的合并;


    ?

    目前有成熟的Nginx模塊如nginx-http-concat進(jìn)行靜態(tài)資源合并;因?yàn)槲覀兪褂昧薕penResty,那么我們完全可以使用Lua編寫程序?qū)崿F(xiàn)該功能,比如已經(jīng)有人寫了nginx-lua-static-merger來實(shí)現(xiàn)這個(gè)功能。

    ?

    還一些業(yè)務(wù)型應(yīng)用場景如下圖所示


    ?

    商品頁面是由商品框架和其他維度的頁面片段(面包屑、相關(guān)分類、商家信息、規(guī)格參數(shù)、商品詳情)組成;或者首頁是由首頁框架和一些頁面片段(分類、輪播圖、樓層1、樓層N)組成;分維度是因?yàn)椴煌木S度是獨(dú)立變化的。對于這種靜態(tài)內(nèi)容但是需要進(jìn)行框架內(nèi)容嵌入的方式,Nginx自帶的SSI(Server Side Include)可以很輕松的完成;也可以使用Lua程序更靈活的完成(讀取框架、讀取頁面片段、合并輸出)。

    ?

    比如商品頁面的架構(gòu)我們可以這樣:


    ?

    首先接收到商品變更消息,商品頁面同步Worker會根據(jù)消息維度生成相關(guān)的頁面推送到Nginx服務(wù)器;Nginx應(yīng)用再通過SSI輸出。目前京東商品詳情頁沒有再采用這種架構(gòu),具體架構(gòu)可以參考《構(gòu)建需求響應(yīng)式億級商品詳情頁》。

    ?

    對于首頁的架構(gòu)是類似的,因?yàn)槠涮攸c(diǎn)(框架變化少,樓層變化較頻繁)和個(gè)性化的要求,樓層一般實(shí)現(xiàn)為異步加載。

    ?

    2.3、 第三張圖和第二張圖的不同處是不再直接讀取文件系統(tǒng),而是讀取本機(jī)的Redis或者Redis集群或者如SSDB這種持久化存儲或者其他存儲系統(tǒng)都是可以的,比如直接說的商品頁面可以使用SSDB進(jìn)行存儲實(shí)現(xiàn)。文件系統(tǒng)一個(gè)很大的問題是當(dāng)多臺服務(wù)器時(shí)需要Worker去寫多臺服務(wù)器,而這個(gè)過程可以使用SSDB的主從實(shí)現(xiàn)。



    ?此處可以看到,不管是圖二還是圖三架構(gòu),都需要Worker去進(jìn)行數(shù)據(jù)推送;假設(shè)本機(jī)數(shù)據(jù)丟了可怎么辦?因此實(shí)際大部分應(yīng)用不會是完全單機(jī)閉環(huán)的,而是會采用如下架構(gòu):

    ?





    ???

    即首先讀本機(jī),如果沒數(shù)據(jù)會回源到相應(yīng)的Web應(yīng)用從數(shù)據(jù)源拉取原始數(shù)據(jù)進(jìn)行處理。這種架構(gòu)的大部分場景本機(jī)都可以命中數(shù)據(jù),只有很少一部分情況會回源到Web應(yīng)用。

    ?

    如京東的實(shí)時(shí)價(jià)格/動態(tài)服務(wù)就是采用類似架構(gòu)。

    ?

    3、分布式閉環(huán)

    單機(jī)閉環(huán)會遇到如下兩個(gè)主要問題: 1、數(shù)據(jù)不一致問題(比如沒有采用主從架構(gòu)導(dǎo)致不同服務(wù)器數(shù)據(jù)不一致);2、遇到存儲瓶頸(磁盤或者內(nèi)存遇到了天花板)。

    解決數(shù)據(jù)不一致的比較好的辦法是采用主從或者分布式集中存儲;而遇到存儲瓶頸就需要進(jìn)行按照業(yè)務(wù)鍵進(jìn)行分片,將數(shù)據(jù)分散到多臺服務(wù)器。

    ?

    如采用如下架構(gòu),按照尾號將內(nèi)容分布到多臺服務(wù)器。


    ?

    即第一步先讀取分布式存儲(JIMDB是京東的一個(gè)分布式緩存/存儲系統(tǒng),類似于Redis);如果不命中則回源到Tomcat集群(其會調(diào)用數(shù)據(jù)庫、服務(wù)總線獲取相關(guān)數(shù)據(jù))來獲取相關(guān)數(shù)據(jù)。可以參考《構(gòu)建需求響應(yīng)式億級商品詳情頁》來獲取更詳細(xì)的架構(gòu)實(shí)現(xiàn)。

    ?

    JIMDB集群會進(jìn)行多機(jī)房主從同步,各自機(jī)房讀取自己機(jī)房的從JIMDB集群,如下圖


    ?

    ?

    4、接入網(wǎng)關(guān)

    接入網(wǎng)關(guān)也可以叫做接入層,即接收到流量的入口,在入口我們可以進(jìn)行如下事情:


    ?

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

    1、動態(tài)負(fù)載均衡;1、普通流量走一致性哈希,提升命中率;熱點(diǎn)流量走輪訓(xùn)減少單服務(wù)器壓力;2、根據(jù)請求特征將流量分配到不同分組并限流(爬蟲、或者流量大的IP);3、動態(tài)流量(動態(tài)增加upstream或者減少upstream或者動態(tài)負(fù)載均衡)可以使用balancer_by_lua或者微博開源的upsync;

    2、防DDOS攻擊限流:可以將請求日志推送到實(shí)時(shí)計(jì)算集群,然后將需要限流的IP推送到核心Nginx進(jìn)行限流;

    3、非法請求過濾:比如應(yīng)該有Referer卻沒有,或者應(yīng)該帶著Cookie卻沒有Cookie;

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

    5、請求頭過濾:有些業(yè)務(wù)是不需要請求頭的,因此可以在往業(yè)務(wù)Nginx轉(zhuǎn)發(fā)時(shí)把這些數(shù)據(jù)過濾掉;

    6、緩存服務(wù):使用Nginx Proxy Cache實(shí)現(xiàn)內(nèi)容頁面的緩存;

    ?

    4.2、業(yè)務(wù)Nginx會做如下事情:

    1、緩存:對于讀服務(wù)會使用大量的緩存來提升性能,我們在設(shè)計(jì)時(shí)主要有如下緩存應(yīng)用:首先讀取Nginx本地緩存? Shared Dict或者Nginx Proxy Cache,如果有直接返回內(nèi)容給用戶;如果本地緩存不命中,則會讀取分布式緩存如Redis,如果有直接返回;如果還是不命中則回源到Tomcat應(yīng)用讀取DB或調(diào)用服務(wù)獲取數(shù)據(jù)。另外我們會按照維度進(jìn)行數(shù)據(jù)的緩存。

    2、業(yè)務(wù)邏輯:我們會進(jìn)行一些數(shù)據(jù)校驗(yàn)/過濾邏輯前置(如商品ID必須是數(shù)字)、業(yè)務(wù)邏輯前置(獲取原子數(shù)據(jù),然后在Nginx上寫業(yè)務(wù)邏輯)。

    3、細(xì)粒度限流:按照接口特征和接口吞吐量來實(shí)現(xiàn)動態(tài)限流,比如后端服務(wù)快扛不住了,那我們就需要進(jìn)行限流,被限流的請求作為降級請求處理;通過lua-resty-limit-traffic可以通過編程實(shí)現(xiàn)更靈活的降級邏輯,如根據(jù)用戶、根據(jù)URL等等各種規(guī)則,如降級了是讓用戶請求等待(比如sleep 100ms,這樣用戶請求就慢下來了,但是服務(wù)還是可用)還是返回降級內(nèi)容。

    4、降級:降級主要有兩種:主動降級和被動降級;如請求量太大扛不住了,那我們需要主動降級;如后端掛了或者被限流了或者后端超時(shí)了,那我們需要被動降級。降級方案可以是:1、返回默認(rèn)數(shù)據(jù)如庫存默認(rèn)有貨;2、返回靜態(tài)頁如預(yù)先生成的靜態(tài)頁;3、部分用戶降級,告訴部分用戶等待下再操作;4、直接降級,服務(wù)沒數(shù)據(jù),比如商品頁面的規(guī)格參數(shù)不展示;5、只降級回源服務(wù),即可以讀取緩存的數(shù)據(jù)返回,實(shí)現(xiàn)部分可用,但是不會回源處理;

    5、AB測試/灰度發(fā)布:比如要上一個(gè)新的接口,可以通過在業(yè)務(wù)Nginx通過Lua寫復(fù)雜的業(yè)務(wù)規(guī)則實(shí)現(xiàn)不同的人看到不同的版本。

    6、服務(wù)質(zhì)量監(jiān)控:我們可以記錄請求響應(yīng)時(shí)間、緩存響應(yīng)時(shí)間、反向代理服務(wù)響應(yīng)時(shí)間來詳細(xì)了解到底哪塊服務(wù)慢了;另外記錄非200狀態(tài)碼錯(cuò)誤來了解服務(wù)的可用率。

    ?

    京東的交易大Nginx節(jié)點(diǎn)、無線部門正在開發(fā)的無線Nginx網(wǎng)關(guān)、和單品頁統(tǒng)一服務(wù)都是接入網(wǎng)關(guān)的實(shí)踐,而單品頁統(tǒng)一服務(wù)架構(gòu)可以參考《京東商品詳情頁服務(wù)閉環(huán)實(shí)踐》。

    ?

    5、Web應(yīng)用

    此處所說的Web應(yīng)用指的是頁面模板渲染類型應(yīng)用或者API服務(wù)類型應(yīng)用;比如京東列表頁/商品詳情頁就是一個(gè)模板渲染類型的應(yīng)用,核心業(yè)務(wù)邏輯都是使用Lua寫的,部署到Nginx容器。目前核心業(yè)務(wù)代碼行數(shù)有5000多行,模板頁面有2000多行,涉及到大量的計(jì)算邏輯,性能數(shù)據(jù)可以參考《構(gòu)建需求響應(yīng)式億級商品詳情頁》。



    ?

    整體處理過程和普通Web應(yīng)用沒什么區(qū)別:首先接收請求并進(jìn)行解析;然后讀取JIMDB集群數(shù)據(jù)、如果沒有則回源到Tomcat獲取;然后進(jìn)行業(yè)務(wù)邏輯處理;渲染模板;將響應(yīng)內(nèi)容返回給用戶。

    ?

    三、如何使用Nginx+Lua開發(fā)Web應(yīng)用

    開發(fā)一個(gè)Web應(yīng)用我們需要從項(xiàng)目搭建、功能開發(fā)、項(xiàng)目部署幾個(gè)層面完成。

    ?

    3.1、項(xiàng)目搭建?

    Java代碼 ?

  • /export/App/nginx-app ??
  • ?-------bin(腳本) ??
  • ?------------start.sh ??
  • ?------------stop.sh ??
  • ?-------config(配置文件) ??
  • ?------------nginx.conf ??
  • ?------------domain ??
  • ?----------------nginx_product.conf ??
  • ?------------resources.properties ??
  • ?-------lua(業(yè)務(wù)代碼) ??
  • ?------------init.lua ??
  • ?------------product_controller.lua ??
  • ?-------template(模板) ??
  • ?--------------prodoct.html ??
  • ?-------lualib(公共Lua庫) ??
  • ?------------jd ??
  • ?----------------product_util.lua ??
  • ?----------------product_data.lua ??
  • ?------------resty ??
  • ?----------------redis.lua ??
  • ?----------------template.lua??
  • /export/App/nginx-app-------bin(腳本)------------start.sh------------stop.sh-------config(配置文件)------------nginx.conf------------domain----------------nginx_product.conf------------resources.properties-------lua(業(yè)務(wù)代碼)------------init.lua------------product_controller.lua-------template(模板)--------------prodoct.html-------lualib(公共Lua庫)------------jd----------------product_util.lua----------------product_data.lua------------resty----------------redis.lua----------------template.lua

    ??

    ?整個(gè)項(xiàng)目結(jié)構(gòu)從啟停腳本、配置文件、公共組件、業(yè)務(wù)代碼、模板代碼幾塊進(jìn)行劃分。

    ?

    1、啟停腳本

    啟停腳本放在項(xiàng)目目錄/export/App/nginx-app/bin/下。

    start.sh是啟動和更新腳本,即如果nginx沒有啟動則啟動起來,否則reload:?

    Java代碼 ?

  • if?nginx沒啟動?then ??
  • ??sudo?/export/servers/nginx/sbin/nginx??-t?-c?/export/App/nginx-app/config/nginx.conf ??
  • ??sudo?/export/servers/nginx/sbin/nginx??-c?/export/App/nginx-app/config/nginx.conf ??
  • else??
  • ??sudo?/export/servers/nginx/sbin/nginx??-t ??
  • ??sudo?/export/servers/nginx/sbin/nginx??-s?reload ??
  • end????
  • if nginx沒啟動 thensudo /export/servers/nginx/sbin/nginx -t -c /export/App/nginx-app/config/nginx.confsudo /export/servers/nginx/sbin/nginx -c /export/App/nginx-app/config/nginx.conf elsesudo /export/servers/nginx/sbin/nginx -tsudo /export/servers/nginx/sbin/nginx -s reload end ?

    stop.sh是停止Nginx腳本:?

    Java代碼 ?

  • sudo?/export/servers/nginx/sbin/nginx??-s?quit???
  • sudo /export/servers/nginx/sbin/nginx -s quit?

    ?

    2、配置文件

    配置文件放在/export/App/nginx-app/config目錄下,包括了nginx.conf配置文件、nginx項(xiàng)目配置文件和資源配置文件。

    ?

    nginx.confg配置文件??

    Java代碼 ?

  • worker_processes??1; ??
  • events?{ ??
  • ????worker_connections??1024; ??
  • } ??
  • http?{ ??
  • ???include???????mime.types; ??
  • ???default_type??text/html; ??
  • ???#gzip相關(guān) ??
  • ???#超時(shí)時(shí)間 ??
  • ???#日志格式 ??
  • ???#反向代理配置 ??
  • ??
  • ???#lua依賴路徑 ??
  • ???lua_package_path??"/export/App/nginx-app/lualib/?.lua;;"; ??
  • ???lua_package_cpath??"/export/App/nginx-app/lualib/?.so;;"; ??
  • ??
  • ???#server配置 ??
  • ???include?/export/App/nginx-app/config/domains/*; ??
  • ??
  • ???#初始化腳本 ??
  • ???init_by_lua_file?"/export/App/nginx-app/lua/init.lua"; ??
  • }???
  • worker_processes 1; events {worker_connections 1024; } http {include mime.types;default_type text/html;#gzip相關(guān)#超時(shí)時(shí)間#日志格式#反向代理配置#lua依賴路徑lua_package_path "/export/App/nginx-app/lualib/?.lua;;";lua_package_cpath "/export/App/nginx-app/lualib/?.so;;";#server配置include /export/App/nginx-app/config/domains/*;#初始化腳本init_by_lua_file "/export/App/nginx-app/lua/init.lua"; }?

    對于nginx.conf會進(jìn)行一些通用的配置,如工作進(jìn)程數(shù)、超時(shí)時(shí)間、壓縮、日志格式、反向代理等相關(guān)配置;另外需要指定如下配置:

    lua_package_path、lua_package_cpath指定我們依賴的通用Lua庫從哪里加載;

    include /export/App/nginx-app/config/domains/*:用于加載server相關(guān)的配置,此處通過*可以在一個(gè)nginx下指定多個(gè)server配置;

    init_by_lua_file "/export/App/nginx-app/lua/init.lua":執(zhí)行項(xiàng)目的一些初始化配置,比如加載配置文件。

    ?

    nginx項(xiàng)目配置文件

    /export/App/nginx-app/config/domains/nginx_product.conf用于配置當(dāng)前web應(yīng)用的一些server相關(guān)的配置:?

    Java代碼 ?

  • #upstream ??
  • upstream?item_http_upstream?{ ??
  • ????server?192.168.1.1?max_fails=2?fail_timeout=30s?weight=5; ??
  • ????server?192.168.1.2?max_fails=2?fail_timeout=30s?weight=5; ??
  • } ??
  • #緩存 ??
  • lua_shared_dict?item_local_shop_cache?600m; ??
  • server?{ ??
  • ?????listen???????????????????80; ??
  • ?????server_name??????????????item.jd.com?item.jd.hk; ??
  • ?????#模板文件從哪加載 ??
  • ????set?$template_root?"/export/App/nginx-app/template?"; ??
  • ?????#url映射 ??
  • ????????location?~*?"^/product/(\d+)\.html$"?{ ??
  • ????????????rewrite?/product/(.*)????http://item.jd.com/$1?permanent; ??
  • ????????} ??
  • ????location?~*?"^/(\d{6,12})\.html$"?{ ??
  • ????????????default_type?text/html; ??
  • ????????????charset?gbk; ??
  • ????????????lua_code_cache?on; ??
  • ????????????content_by_lua_file?"/export/App/nginx-app/lua/product_controller.lua"; ??
  • ????????} ??
  • }???
  • #upstream upstream item_http_upstream {server 192.168.1.1 max_fails=2 fail_timeout=30s weight=5;server 192.168.1.2 max_fails=2 fail_timeout=30s weight=5; } #緩存 lua_shared_dict item_local_shop_cache 600m; server {listen 80;server_name item.jd.com item.jd.hk;#模板文件從哪加載set $template_root "/export/App/nginx-app/template ";#url映射location ~* "^/product/(\d+)\.html$" {rewrite /product/(.*) http://item.jd.com/$1 permanent;}location ~* "^/(\d{6,12})\.html$" {default_type text/html;charset gbk;lua_code_cache on;content_by_lua_file "/export/App/nginx-app/lua/product_controller.lua";} }?

    我們需要指定如upstream、共享字典配置、server配置、模板文件從哪加載、url映射,比如我們訪問http://item.jd.com/1856584.html將交給/export/App/nginx-app/lua/product_controller.lua處理;也就是說我們項(xiàng)目的入口就有了。

    ?

    資源配置文件resources.properties包含了我們的一些比如開關(guān)的配置、緩存服務(wù)器地址的配置等等。

    ?

    3、業(yè)務(wù)代碼

    /export/App/nginx-app/lua/目錄里存放了我們的lua業(yè)務(wù)代碼,init.lua用于讀取如resources.properties來進(jìn)行一些項(xiàng)目初始化;product_controller.lua可以看成Java Web中的Servlet,接收、處理、響應(yīng)用戶請求。

    ?

    4、模板

    模板文件放在/export/App/nginx-app/template/目錄下,使用相應(yīng)的模板引擎進(jìn)行編寫頁面模板,然后渲染輸出。

    ?

    5、公共Lua庫

    存放了一些如redis、template等相關(guān)的公共Lua庫,還有一些我們項(xiàng)目中通用的工具庫如product_util.lua。

    ?

    到此一個(gè)簡單的項(xiàng)目的結(jié)構(gòu)就介紹完了,對于開發(fā)一個(gè)項(xiàng)目來說還會牽扯到分模塊等工作,不過對于我們這種Lua應(yīng)用來說,建議不要過度抽象,盡量小巧即可。

    ?

    3.2、功能開發(fā)

    接下來就需要使用相應(yīng)的API來實(shí)現(xiàn)我們的業(yè)務(wù)了,比如product_controller.lua:

    Java代碼 ?

  • --加載Lua模塊庫 ??
  • local?template?=?require("resty.template")?? ??
  • --1、獲取請求參數(shù)中的商品ID ??
  • local?skuId?=?ngx.req.get_uri_args()["skuId"]; ??
  • --2、調(diào)用相應(yīng)的服務(wù)獲取數(shù)據(jù) ??
  • local?data?=?api.getData(skuId) ??
  • ??
  • --3、渲染模板 ??
  • local?func?=?template.compile("product.html")?? ??
  • local?content?=?func(data)?? ??
  • --4、通過ngx?API輸出內(nèi)容?? ??
  • ngx.say(content)?????
  • --加載Lua模塊庫 local template = require("resty.template") --1、獲取請求參數(shù)中的商品ID local skuId = ngx.req.get_uri_args()["skuId"]; --2、調(diào)用相應(yīng)的服務(wù)獲取數(shù)據(jù) local data = api.getData(skuId)--3、渲染模板 local func = template.compile("product.html") local content = func(data) --4、通過ngx API輸出內(nèi)容 ngx.say(content) ?

    開發(fā)完成后將項(xiàng)目部署到測試環(huán)境,執(zhí)行start.sh啟動nginx然后進(jìn)行測試。

    詳細(xì)的開發(fā)過程和API的使用,請參考《跟我學(xué)Nginx+Lua開發(fā)》。此處不做具體編碼實(shí)現(xiàn)。

    ?

    參考源碼:Nginx+Lua(OpenResty) HelloWorld

    ?

    四、基于Nginx+Lua的常用功能總結(jié)

    到此我們對于Nginx開發(fā)已經(jīng)有了一個(gè)整體的認(rèn)識,對于Nginx粘合Lua來開發(fā)應(yīng)用可以說是一把鋒利的瑞士軍刀,可以幫我們很容易的解決很多問題,可以開發(fā)Web應(yīng)用、接入網(wǎng)關(guān)、API網(wǎng)關(guān)、消息推送、日志采集等應(yīng)用,不過個(gè)人認(rèn)為適合開發(fā)業(yè)務(wù)邏輯單一、核心代碼行數(shù)較少的應(yīng)用,不適合業(yè)務(wù)邏輯復(fù)雜、功能繁多的業(yè)務(wù)型或者企業(yè)級應(yīng)用;最后我們總結(jié)下基于Nginx+Lua的常用架構(gòu)模式中一些常見實(shí)踐和場景:

    ? 動態(tài)負(fù)載均衡;

    ? 防火墻(DDOS、IP/URL/UserAgent/Referer黑名單、防盜鏈等)

    ? 限流;

    ? 降級;

    ? AB測試/灰度發(fā)布;

    ? 多級緩存模式;

    ? 服務(wù)端請求聚合;

    ? 服務(wù)質(zhì)量監(jiān)控。

    ?

    一些問題

    1、在開發(fā)nginx應(yīng)用時(shí)使用UTF-8編碼可以減去很多麻煩;

    2、GBK轉(zhuǎn)碼解碼時(shí)使用GB18030,否則一些特殊字符會出現(xiàn)亂碼;

    3、cjson庫對于如\uab1這種錯(cuò)誤的unicode轉(zhuǎn)碼會失敗,可以使用純Lua編寫的dkjson;

    4、社區(qū)版nginx不支持upstream的域名動態(tài)解析;可以考慮proxy_pass http://p.3.local/prices/mgets$is_args$args,然后配合resolver來實(shí)現(xiàn);或者在lua中進(jìn)行http調(diào)用;如果DNS遇到性能瓶頸可以考慮在本機(jī)部署如dnsmasq來緩存;或者考慮使用balancer_by_lua功能實(shí)現(xiàn)動態(tài)upstream;

    5、為響應(yīng)添加處理服務(wù)器IP的響應(yīng)頭,方便定位問題;

    6、根據(jù)業(yè)務(wù)設(shè)置合理的超時(shí)時(shí)間;

    7、走CDN的業(yè)務(wù)當(dāng)發(fā)生錯(cuò)誤時(shí)返回的500/503/302/301等非正常響應(yīng)不要設(shè)置緩存。

    ?

    五、參考文資料

    深入 Nginx:我們是如何為性能和規(guī)模做設(shè)計(jì)的

    ??http://blog.jobbole.com/88766/

    Nginx變量漫談/配置指令的執(zhí)行順序

    ??http://blog.sina.com.cn/openresty

    ngx_lua文檔

    ??https://github.com/openresty/lua-nginx-module#readme

    OpenResty最佳實(shí)踐

    ??https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/brief.html

    跟我學(xué)Nginx+Lua開發(fā)

    ??http://jinnianshilongnian.iteye.com/blog/2190344

    構(gòu)建需求響應(yīng)式億級商品詳情頁

    ??http://jinnianshilongnian.iteye.com/blog/2235572

    京東商品詳情頁服務(wù)閉環(huán)實(shí)踐

    ??http://jinnianshilongnian.iteye.com/blog/2258111?

    Upsync:微博開源基于Nginx容器動態(tài)流量管理方案

    ??http://toutiao.com/a6254279391729139970

    總結(jié)

    以上是生活随笔為你收集整理的用Nginx+Lua(OpenResty)开发高性能Web应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。