socket.io分布式
socket.io是目前較為流行的web實時推送框架,其基於nodejs語言開發,底層用engine.io實現。 借助nodejs語言異步的特性,其獲得了不錯的性能。但單個實例的socket.io依然承載能力有限,最多只能容納3000個long-polling方式的客戶端進行連接。
將socket.io進行分布式擴展的難點有兩處:
1. 進行負載均衡時客戶端必須保證始終連到一個節點上
如果客戶端采用long-polling長輪訓方式進行連接,則每次輪訓都會產生一個新的請求,若不進行限制。就有可能連接到集群內新的 socket.io節點上,導致異常的發生。
解決方法:使用nginx的ip_hash實現session sticky ,讓客戶端始終連接到集群內一臺節點上。
2. 多個實例之間的消息推送
當集群內某臺節點想要向連接到集群的所有客戶端發送消息時,某些客戶端因為負載均衡時ip_hash可能被分配到了其他的節點上,這時就需要向其他節點發布推送消息,讓其他節點的同時向客戶端進行推送。
解決方法:使用redis的發布與訂閱功能與socket.io-redis開源庫,該庫在節點向客戶端群發消息時會將該消息發布到redis的訂閱隊列中,讓其他節點能夠訂閱到該消息,從而實現節點間消息推送。
上圖是采用該架構的一個聊天服務器集群示例,每個chatnode相當於一個socket.io實例,其中的chatModule負責客戶端連接,adminModule負責聊天服務器的管理功能。
adminnode作為整個集群的管理節點,通過redis的消息訂閱功能來與各個chatnode通信, 並通過開放http接口來與外部系統進行交互。
準備安裝的軟件:
nginx, nodejs, redis以及一個socket.io應用,如一個聊天服務器,例子請見官網這裏。
具體步驟:
1.將socket.io應用部署成兩個實例,如在同一臺主機上為每個實例分配不同的端口號4000, 5000:
[js] view plain copy- 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適配器:
[js] view plain copy- 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中。
socket.io分布式