socket.io是目前較爲流行的web實時推送框架,其基於nodejs語言開發,底層用engine.io實現。 藉助nodejs語言異步的特性,其得到了不錯的性能。但單個實例的socket.io依然承載能力有限,最多隻能容納3000個long-polling方式的客戶端進行鏈接。node
將socket.io進行分佈式擴展的難點有兩處:nginx
1. 進行負載均衡時客戶端必須保證始終連到一個節點上git
若是客戶端採用long-polling長輪訓方式進行鏈接,則每次輪訓都會產生一個新的請求,若不進行限制。就有可能鏈接到集羣內新的 socket.io節點上,致使異常的發生。github
解決方法:使用nginx的ip_hash實現session sticky ,讓客戶端始終鏈接到集羣內一臺節點上。web
2. 多個實例之間的消息推送redis
當集羣內某臺節點想要向鏈接到集羣的全部客戶端發送消息時,某些客戶端由於負載均衡時ip_hash可能被分配到了其餘的節點上,這時就須要向其餘節點發布推送消息,讓其餘節點的同時向客戶端進行推送。mongodb
解決方法:使用redis的發佈與訂閱功能與socket.io-redis開源庫,該庫在節點向客戶端羣發消息時會將該消息發佈到redis的訂閱隊列中,讓其餘節點可以訂閱到該消息,從而實現節點間消息推送。shell
上圖是採用該架構的一個聊天服務器集羣示例,每一個chatnode至關於一個socket.io實例,其中的chatModule負責客戶端鏈接,adminModule負責聊天服務器的管理功能。npm
adminnode做爲整個集羣的管理節點,經過redis的消息訂閱功能來與各個chatnode通訊, 並經過開放http接口來與外部系統進行交互。服務器
準備安裝的軟件:
nginx, nodejs, redis以及一個socket.io應用,如一個聊天服務器,例子請見官網這裏。
具體步驟:
1.將socket.io應用部署成兩個實例,如在同一臺主機上爲每一個實例分配不一樣的端口號4000, 5000:
http.listen(4000, function(){ console.log('listening on *:4000'); });
2.配置nginx文件,設置負載均衡proxy
upstream chat_nodes { ip_hash; server 127.0.0.1:4000; server 127.0.0.1:5000; }
以及反向代理設置 (注意爲了支持websocket協議,需將nginx升級至1.3.12版本以上
location / { proxy_pass http://chat_nodes; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
完成配置後,重啓nginx。
3.安裝nodejs模塊 socket.io-redis
sudo npm install socket.io-redis
4.在原來socket.io應用中初始化io的位置加入io的redis適配器:
var redis = require('socket.io-redis'); io.adapter(redis({ host: 'localhost', port: 6379 }));
5. 重啓各個socket.io應用,進行測試。
其餘注意點:
因爲nginx的反向代理機制和socket.io的自動重連機制,上述架構還具有高可用的特性,即當某個節點down機時,原先鏈接到該節點上的客戶端會自動重連至其它節點上。
節點的數量能夠隨時增減,不須要暫停服務,只需修改nginx配置便可。
nginx的ip_hash是基於ip的前三段進行計算的,也就是說ip只有D段不一樣的兩臺客戶端必定會鏈接到同一臺服務器上,這點測試的時候須要注意。
能夠經過redis的訂閱發佈服務來實現其餘系統同集羣的通訊,完成集羣的管理工做。
因爲是分佈式環境,因此節點內存中存儲的信息(如用戶、房間信息)能夠考慮持久化到redis或mongodb中。