Node.js 應用程序的 5 條性能建議
「如果在 Node 服務前面沒有架設 #nginx ,那你很可能做的就不對」,Bryan Hughes 在 Twitter 上說。
Node.js 是世界上最領先的用 JavaScript — 世界上最流行的編程語言 — 創建服務端應用的工具。Node.js 同時提供了 Web 伺服器和應用伺服器的功能,現在被認為是基於微服務開發和交付的重要工具。(下載免費的關於 Nodejs 和 NGINX 的 Forrester 報告)
Node.js 可以替代或增強 Java 或 .NET 的後端應用程序開發。
Node.js 是單線程(single-threaded)的,使用無阻塞(non-blocking) I/O,便於擴展,支持成千上萬的並發操作。它和 NGINX 共享這些架構特性,解決了 C10K 問題(支持超過 10,000 並發連接),這也是發明 NGINX 要解決的問題。
那麼,這有什麼問題呢?
Node.js 有一些缺陷和弱點會使基於 Node 的服務表現不佳,甚至崩潰。隨著基於 Node 的 Web 應用程序快速增長,這些問題也更加頻繁地顯現出來。
Node.js 同樣是用來構建和運行應用邏輯、生成動態頁面內容的優秀的工具。但是它在處理靜態內容上面不是非常有優勢,比如圖片,JavaScript 文件。還有在多伺服器之間實現負載均衡也不如意。
除了 Node.js,你還需要緩存靜態內容,在多個應用服務中間做代理和負載均衡,在客戶端,Nodejs 和輔助工具(比如 http://Socket.IO 服務) 之間管理埠佔用。NGINX 可以來做這些事情,使得 Node.js 的性能發生重大轉變。
下面這些建議可以提高 Node.js 應用程序的性能:
- 實現一個反向代理伺服器
- 緩存靜態文件
- 多伺服器之間負載均衡
- 代理 WebSocket 連接
- 實現 SSL/TLS 和 HTTP/2
注意:要快速改善 Node.js 應用程序性能的一個方法是修改 Node.js 配置,利用現代多核伺服器的優勢。閱讀這篇文章來了解如何使 Node.js 產生多個獨立的子進程,進程的個數等於 Web 伺服器上 CPU 的內核數。每個進程會神奇地找到自己的位置,使用其中的一個 CPU,這會這性能上有非常大提升。
1. 實現一個反向代理伺服器
在 NGINX 公司,當我們看到應用伺服器直接暴露在外部網路流量中,作為高性能站點的核心,有總會有點吃驚。這包括很多基於 WordPress 的站點,Node.js 網站也是如此。
Node.js 很大程度上比大多數應用伺服器更加容易擴展,其 web 伺服器端可以很好地處理很多網路流量,但是 web 服務不是 Node.js 存在的理由,這不是它要做的事情。
如果你有一個高流量的站點,提高應用服務性能的第一步就是在 Node.js 服務前面架設一個反向理伺服器。這樣做能避免 Node.js 服務直接暴露在網路流量中,並且可以讓你更靈活地處理多個應用伺服器,包括跨伺服器之間的負載均衡和內容緩存 //內部鏈接//。
將 NGINX 架設在已有的服務前面作為一個反向代理伺服器,作為 NGINX 重要的用途之一,與其他用途一道被世界上成千上萬的站點所運用。
使用 NGINX 作為 Node.js 反向代理伺服器有很多具體的好處,其中包括:
- 簡化許可權控制和埠分配
- 更有效地提供靜態文件(見下節)
- 管理 Node.js 崩潰問題
- 減少 DoS 攻擊
注意:一些教程解釋了在 Ubuntu 14.04 和 CentOS 環境下如何把 NGINX 當作為反向代理伺服器使用,對於所有要將 NGINX 架設在 Node.js 前面的人都是非常有用的概述。
2. 緩存靜態文件
隨著使用基於 Node.js 服務的站點的增長,這些服務逐漸承受了很多壓力。這時候你需要做兩件事情:
- 把大部分東西從 Node.js 服務上分離開
- 讓應用伺服器擴容和實現負載均衡變得簡單
事實上這很容易做到,通過像上一節中講的將 NGINX 作為一個反向代理伺服器,很容易實現緩存,負載均衡(當有多個 Node.js 伺服器時)等。
Modulus 網站,是一個應用程序容器平台,上面有一篇文章,是關於用 NGINX 提升 Node.js 應用性能的文章。讓 Node.js 完成所有工作,這位作者的網站可以提供平均每秒大約 900 次請求。用 NGINX 作為反向代理伺服器,提供靜態內容,同樣的站點可以提供每秒超過 1600 次請求,性能提升接近 2 倍。
性能翻倍你就會有時間繼續尋找新的增長點,例如評審(或者改善)網站的設計,優化程序代碼,部署額外的應用伺服器等。
下面是一段運行在 Modulus 上的一個網站的配置代碼:
server {n listen 80;n server_name static-test-47242.onmodulus.net;nn root /mnt/app;n index index.html index.htm;nn location /static/ {n try_files $uri $uri/ =404;n }nn location /api/ {n proxy_pass http://node-test-45750.onmodulus.net;n }n}n
這篇來自 NGINX 公司 Patrick Nommensen 的文章,解釋了他的個人博客是如何緩存靜態內容的,他的博客運行在開源的 Ghost 博客平台上,這是一個 Node.js 應用,儘管一些特定的細節是針對 Ghost 的,你仍然可以在其他 Node.js 應用上復用大部分的代碼。
例如,在 NGINX 配置的 location 塊中,你可能不想讓一些內容被緩存。比如不想緩存博客平台的管理界面。下面是一段禁用 Ghost 管理界面緩存的代碼:
location ~ ^/(?:ghost|signout) { n proxy_set_header X-Real-IP $remote_addr;n proxy_set_header Host $http_host;n proxy_pass http://ghost_upstream;n add_header Cache-Control "no-cache, private, no-store,n must-revalidate, max-stale=0, post-check=0, pre-check=0";n}n
想了解關於提供靜態文件的一般信息,可以參考 NGINX Plus Admin Guide。這篇管理指南包括配置說明、正對請求文件成功或者失敗的多種可選的返回,以及若干優化的方式來實現更好的性能。
使用 NGINX 伺服器提供靜態文件大大減輕了 Node.js 應用伺服器的負擔,使它可以實現更高好的性能。
3. 實現 Node.js 的負載均衡
真正的 Node.js 高性能(就是說幾乎沒有上限)的關鍵是運行多個應用伺服器,在它們中間實現負載均衡。
Node.js 的負載均衡可能會特別棘手,因為 Node.js 採用了 Web 瀏覽器中的 JavaScript 代碼 和 Node.js 應用伺服器上代碼 之間的高層次交互,使用 JSON 對象作為數據交互的媒介。這意味著,給定客戶端的會話持續運行在一個特定的應用伺服器上,多個應用伺服器上 Session 持久性問題在根本上很難解決。
Internet 和 Web 的一個優勢是高度的無狀態性,使得人任何伺服器只要可以訪問被訪問的文件都可以處理來自客戶端的請求。Node.js 是反無狀態的,最擅長在有狀態的環境下工作,同一個伺服器可以不斷地響應來自任何客戶端的請求。
這個需求可以用 NGINX Plus 得到最好的實現,而不是開源的 NGINX 軟體。這兩個版本的 NGINX 非常像,但是其中一個主要的區別是它們支持不同的負載均衡演算法。
NGINX 支持無狀態的負載均衡方法:
- 輪詢調度 一個新的請求轉到列表中的下一個伺服器
- 最少連接 一個新的請求轉到有最少活動連接的伺服器
- IP 哈希 一個新的請求轉到分配了客戶端 IP 地址哈希值的伺服器
IP 哈希,這些方法的其中之一,能可靠地發送一個客戶端的請求到相同的伺服器,這對 Node.js 應用程序是有好處的。然而,IP 哈希很容易導致一個伺服器接收到大量的請求,損失了其他伺服器,正如這篇文章關於負載均衡技術的描述。以潛在的跨伺服器間的資源分配為代價,此方法可支持有狀態性。
和 NGINX 不同,NGINX Plus 支持 session 持久化。使用 session 持久化,同一個伺服器可以可靠地接受來自給定客戶端的所有請求。Node.js 的優勢是支持客戶端和伺服器間有狀態的通信,NGINX Plus 有高級的負載均衡功能,都可以達到最大化。
所以你可以用 NGINX 或者 NGINX Plus 的負載均衡支持 Node.js 跨伺服器間的負載均衡。如果只用 NGINX Plus,你就可以實現最優的負載均衡以及 Node.js 友好的有狀態性。NGINX Plus 內置的應用狀況檢查和監控功能在這裡同樣有用。
NGINX Plus 同樣支持 session 釋放,可以讓應用伺服器結束一個請求之後優雅地完成當前會話。
4. 代理 WebSocket 連接
所有的 HTTP 版本,被設計為「拉取」的通信方式,是客戶端請求伺服器的方式。WebSocket 開啟了「推送」和「推送/拉取」的通信方式,伺服器可以主動推送客戶端沒有請求的文件。
WebSocket 協議使得客戶端和伺服器之間支持更強的交互,同時減少了數據傳輸量和延遲。在需要的時候,就可以開啟一個全雙工的連接,客戶端和伺服器都會啟動並接受請求。
WebSocket 協議有一個穩健的 JavaScript 介面,很適合 Node.js 作為應用伺服器,同樣可作為適度業務量的 web 應用程序, 也適合作為 web 伺服器。當業務量增加時,將 NGINX 設在客戶端和 Node.js 伺服器之間,使用 NGINX 或 NGINX Plus 緩存靜態文件,並且在多個應用伺服器之間配置負載均衡,是很有意義的。
Node.js 經常會和 http://Socket.IO 一起使用,http://Socket.IO 是一個 Node.js 應用程序中很流行的一個 WebSocket API。這可能造成 80埠(HTTP)和443埠(HTTPS)非常擁擠,解決辦法是代理到 http://Socket.IO 伺服器上。如上所述,你可以使用 NGINX 作為代理伺服器,也可以獲得額外的功能,如靜態文件緩存,負載均衡等。
下面的代碼是一個 node 應用程序的 server.js 文件,監聽 5000 埠,它會作為一個代理伺服器(非 web 伺服器)將請求發送適當的埠:
var io = require(socket.io).listen(5000);nnio.sockets.on(connection, function (socket) {n socket.on(set nickname, function (name) {n socket.set(nickname, name, function () {n socket.emit(ready);n });n });nn socket.on(msg, function () {n socket.get(nickname, function (err, name) {n console.log(Chat message by , name);n });n });n});n
在 index.html 文件中,添加一些代碼來連接到伺服器,在應用程序和用戶瀏覽器之間初始化一個 WebSocket 連接。
<script src="/socket.io/socket.io.js"></script>n<script>n var socket = io(); // your initialization code here.n</script>n
要了解完整的說明,包括 NGINX 配置,參見我們的這篇使用 NGINX 和 NGINX Plus 結合 Node.js 和 Socket.IO 的文章。要了解更多關於類似 web 應用程序基礎架構搭建的話題,可以參考我的文章:實時的 web 應用程序和 WebSocket。
5. 實現 SSL/TLS 和 HTTP/2
越來越多的站點使用 SSL/TLS 保證所有用戶交互的安全性。當然是你來決定是否已經適合去這樣做,但是如果你要做的話,NGINX 可支持兩種交互方式:
- 只要使用 NGINX 當作反向代理,就可以在 NGINX 里處理一個 SSL/TLS 連接到客戶端。Node.js 伺服器和 NGINX 反向代理伺服器來回地發送和接收未加密的請求和內容。
- 有跡象表明使用 HTTP/2,新版本的 HTTP 協議,可以大部分或者完全抵消使用 SSL/TLS 的性能損失。NGINX 對 HTTP/2 做了支持,你可以結合 SSL 來處理 HTTP/2,避免修改任何的 Node.js 應用。
在實現的步驟中你需要做的是更新 Node.js 配置文件中的 URL,在 NGINX 配置中建立和優化安全連接,如果想要,可以使用 SPDY 或者 HTTP/2。添加 HTTP/2 支持意味著支持和伺服器使用 HTTP/2 通信的瀏覽器使用新的協議,舊版本的瀏覽器繼續使用 HTTP/1.x。
下面是一個 Ghost 博客使用 SPDY 的配置代碼,可以在這裡看一下介紹。包括了高級特性比如 OCSP 整合。要考慮使用 NGINX 作為 SSL 終端,包括 OCSP 選項,可以參考這裡。要了解同樣主題一般的概述,參考這裡。
目前,或者在 2016 年初 SPDY 不支持的時候,從 SPDY 到 HTTP/2,你只需要做很小修改來配置 Node.js 應用程序。
server {n server_name domain.com;n listen 443 ssl spdy;n spdy_headers_comp 6;n spdy_keepalive_timeout 300;n keepalive_timeout 300;n ssl_certificate_key /etc/nginx/ssl/domain.key;n ssl_certificate /etc/nginx/ssl/domain.crt;n ssl_session_cache shared:SSL:10m; n ssl_session_timeout 24h; n ssl_buffer_size 1400; n ssl_stapling on;n ssl_stapling_verify on;n ssl_trusted_certificate /etc/nginx/ssl/trust.crt;n resolver 8.8.8.8 8.8.4.4 valid=300s;n add_header Strict-Transport-Security max-age=31536000; includeSubDomains;n add_header X-Cache $upstream_cache_status;n location / {n proxy_cache STATIC;n proxy_cache_valid 200 30m;n proxy_cache_valid 404 1m;n proxy_pass http://ghost_upstream;n proxy_ignore_headers X-Accel-Expires Expires Cache-Control;n proxy_ignore_headers Set-Cookie;n proxy_hide_header Set-Cookie;n proxy_hide_header X-powered-by;n proxy_set_header X-Real-IP $remote_addr;n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;n proxy_set_header X-Forwarded-Proto https;n proxy_set_header Host $http_host;n expires 10m;n }n location /content/images {n alias /path/to/ghost/content/images;n access_log off;n expires max;n }n location /assets {n alias /path/to/ghost/themes/uno-master/assets;n access_log off;n expires max;n }n location /public {n alias /path/to/ghost/built/public;n access_log off;n expires max;n }n location /ghost/scripts {n alias /path/to/ghost/core/built/scripts;n access_log off;n expires max;n }n location ~ ^/(?:ghost|signout) { n proxy_set_header X-Real-IP $remote_addr;n proxy_set_header Host $http_host;n proxy_pass http://ghost_upstream;n add_header Cache-Control "no-cache, private, no-store,n must-revalidate, max-stale=0, post-check=0, pre-check=0";n proxy_set_header X-Forwarded-Proto https;n }n}n
總結
這篇文章講述了在 Node.js 應用程序里你可以做的最重要的性能提升建議。關注使用 NGINX 作為反向代理伺服器,緩存靜態文件,增加負載均衡,代理 WebSocket 連接,實現 SSL/TLS 和 HTTP/2 協議,將 Nginx 加入到你的 Node.js 應用程序里。
NGINX 和 Node.js 的結合是一中被廣泛採取的方式來創建微服務友好的應用程序。或者為現有的基於 SOA 的 使用 Java 或 .NET 的應用程序增加靈活性和功能。這篇文章會幫你優化你的 Node.js 應用程序,如果你選擇這些建議,會帶來 Node.js 和 NGINX 之間的合作更有活力。
推薦閱讀:
TAG:前端外刊评论 | 5PerformanceTipsforNodejsApplications | BryanHughes | 最流行 | 下载 | 发明 | 这篇文章 | 基于WordPress的站点 | 反向代理服务器 | Ubuntu1404 | CentOS | 用NGINX提升Nodejs应用性能 | 运行在Modulus上 | 这篇 | NGINXPlusAdminGuide | NGINXPlus | 负载均衡方法 | 文章 | session持久化 | 应用状况检查 | 监控功能 | session释放 | 作为代理服务器 | 结合Nodejs和SocketIO | 实时的web应用程序 | HTTP2 | 这里 | 这里 | 这里 | 广泛采取的方式 |