用Nginx給網站做反向代理和負載均衡是廣泛使用的一種Web服務器部署技術。不僅能夠保證后端服務器的隱蔽性,還可以提高網站部署靈活性。
今天我們來講一下,如何用Nginx給WebSocket服務器實現反向代理和負載均衡。
什么是反向代理和負載均衡
- 反向代理(Reverse Proxy)方式是指以代理服務器來接受Internet上的連接請求,然后將請求轉發給內部網絡上的服務器。並將內部服務器上得到的結果返回給Internet上請求連接的客戶端,此時代理服務器對外就表現為一個服務器。
- 負載均衡(Load Balancing)建立在現有網絡結構之上,它提供了一種廉價有效透明的方法擴展網絡設備和服務器的帶寬、增加吞吐量、加強網絡數據處理能力、提高網絡的靈活性和可用性。
什么是WebSocket
WebSocket協議相比較於HTTP協議成功握手后可以多次進行通訊,直到連接被關閉。但是WebSocket中的握手和HTTP中的握手兼容,它使用HTTP中的Upgrade協議頭將連接從HTTP升級到WebSocket。這使得WebSocket程序可以更容易的使用現已存在的基礎設施。
WebSocket工作在HTTP的80和443端口並使用前綴ws://
或者wss://
進行協議標注,在建立連接時使用HTTP/1.1的101狀態碼進行協議切換,當前標准不支持兩個客戶端之間不借助HTTP直接建立Websocket連接。
更多Websocket的介紹可參考「WebSocket教程」一文。
創建基於Node的WebSocket服務
Nginx在官方博客上給出了一個實踐樣例「Using Nginx as a Websocket Proxy」,我們以這個例子來演示WebSocket的交互過程。
這個例子中將會使用到nodejs的一個WebSocket的ws模塊。
安裝node.js和npm
- Debian/Ubuntu
$ apt-get install nodejs npm
- RHEL/CentOS
$ yum install nodejs npm
創建nodejs軟鏈
在Ubuntu上創建一個名叫node軟鏈。Centos默認為node,不用在單獨創建了。
# 如果不創建,后面運行wscat時Ubuntu環境中會報錯。
$ ln -s /usr/bin/nodejs /usr/bin/node
安裝ws和wscat模塊
ws
是nodejs的WebSocket實現,我們借助它來搭建簡單的WebSocket Echo Server。wscat
是一個可執行的WebSocket客戶端,用來調試WebSocket服務是否正常。
$ npm install ws wscat
如果訪問官方倉庫比較慢的話,可用淘寶提供的鏡像服務。
$ npm --registry=https://registry.npm.taobao.org install ws wscat
創建一個簡單的服務端
這個簡單的服務端實現的是向客戶端返回客戶端發送的消息。
$ vim server.js
console.log("Server started");
var Msg = '';
var WebSocketServer = require('ws').Server
, wss = new WebSocketServer({port: 8010});
wss.on('connection', function(ws) {
ws.on('message', function(message) {
console.log('Received from client: %s', message);
ws.send('Server received from client: ' + message);
});
});
運行這個簡單的echo
服務
$ node server.js
Server started
驗證服務端是否正常啟動
$ netstat -tlunp|grep 8010
tcp6 0 0 :::8010 :::* LISTEN 23864/nodejs
使用wscat做為客戶端測試
wscat
命令默認安裝當前用戶目錄node_modules/wscat/
目錄,我這里的位置是/root/node_modules/wscat/bin/wscat
。
輸入任意內容進行測試,得到相同返回則說明運行正常。
$ cd /root/node_modules/wscat/bin/
$ ./wscat --connect ws://127.0.0.1:8010
connected (press CTRL+C to quit)
> Hello
< Server received from client: Hello
> Welcome to www.hi-linux.com
< Server received from client: Welcome to www.hi-linux.com
使用Nginx對WebSocket進行反向代理
安裝Nginx
- 下載對應軟件包
Nginx從1.3.13版本就開始支持WebSocket了,並且可以為WebSocket應用程序做反向代理和負載均衡。這里Nginx選用1.9.2版本。
$ cd /root
$ wget 'http://nginx.org/download/nginx-1.9.2.tar.gz'
- 編譯安裝Nginx
$ apt-get install libreadline-dev libncurses5-dev libpcre3-dev libssl-dev perl make build-essential
$ tar xzvf nginx-1.9.2.tar.gz
$ cd nginx-1.9.2
$ ./configure
$ make && make install
配置Nginx
- 修改Nginx主配置文件
$ vim /usr/local/nginx/conf/nginx.conf
# 在http上下文中增加如下配置,確保Nginx能處理正常http請求。
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket {
#ip_hash;
server localhost:8010;
server localhost:8011;
}
# 以下配置是在server上下文中添加,location指用於websocket連接的path。
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/yourdomain.log;
location / {
proxy_pass http://websocket;
proxy_read_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}
最重要的就是在反向代理的配置中增加了如下兩行,其它的部分和普通的HTTP反向代理沒有任何差別。
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
這里面的關鍵部分在於HTTP的請求中多了如下頭部:
Upgrade: websocket
Connection: Upgrade
這兩個字段表示請求服務器升級協議為WebSocket。服務器處理完請求后,響應如下報文:
# 狀態碼為101
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade
告訴客戶端已成功切換協議,升級為Websocket協議。握手成功之后,服務器端和客戶端便角色對等,就像普通的Socket一樣,能夠雙向通信。不再進行HTTP的交互,而是開始WebSocket的數據幀協議實現數據交換。
這里使用map
指令可以將變量組合成為新的變量,會根據客戶端傳來的連接中是否帶有Upgrade頭來決定是否給源站傳遞Connection頭,這樣做的方法比直接全部傳遞upgrade更加優雅。
默認情況下,連接將會在無數據傳輸60秒后關閉,proxy_read_timeout
參數可以延長這個時間或者源站通過定期發送ping幀以保持連接並確認連接是否還在使用。
- 啟動Nginx
Nginx會默認安裝到/usr/local/nginx
目錄下。
$ cd /usr/local/nginx/sbin
$ ./nginx -c /usr/local/nginx/conf/nginx.conf
如果你想以Systemd服務的方式更方便的管理Nginx,可參考「基於Upsync模塊實現Nginx動態配置」 一文。
- 測試通過Nginx訪問WebSocket服務
上面的配置會使NGINX監聽80端口,並把接收到的任何請求傳遞給后端的WebSocket服務器。我們可以使用wscat
作為客戶端來測試一下:
$ cd /root/node_modules/wscat/bin/
$ ./wscat --connect ws://192.168.2.210
connected (press CTRL+C to quit)
> Hello Nginx
< Server received from client: Hello Nginx
> Welcome to www.hi-linux.com
< Server received from client: Welcome to www.hi-linux.com
- 反向代理服務器在支持WebSocket時面臨的挑戰
WebSocket是端對端的,所以當一個代理服務器從客戶端攔截一個Upgrade請求,它需要去發送它自己的Upgrade請求到后端服務器,也包括合適的頭。
因為WebSocket是一個長連接,不像HTTP那樣是典型的短連接,所以反向代理服務器需要允許連接保持着打開,而不是在它們看起來空閑時就將它們關閉。