Chỉnh sửa: Trong Socket.IO 1.0+, thay vì thiết lập một cửa hàng có nhiều máy khách Redis, mô-đun bộ điều hợp Redis đơn giản hơn hiện có thể được sử dụng.
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
Ví dụ được hiển thị bên dưới sẽ trông giống như thế này:
var cluster = require('cluster');
var os = require('os');
if (cluster.isMaster) {
// we create a HTTP server, but we do not use listen
// that way, we have a socket.io server that doesn't accept connections
var server = require('http').createServer();
var io = require('socket.io').listen(server);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
setInterval(function() {
// all workers will receive this in Redis, and emit
io.emit('data', 'payload');
}, 1000);
for (var i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
}
if (cluster.isWorker) {
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
io.on('connection', function(socket) {
socket.emit('data', 'connected to worker: ' + cluster.worker.id);
});
app.listen(80);
}
Nếu bạn có một nút chính cần xuất bản lên các quy trình Socket.IO khác, nhưng không chấp nhận bản thân các kết nối socket, hãy sử dụng socket.io-emitter thay vì socket.io-redis.
Nếu bạn gặp sự cố khi mở rộng quy mô, hãy chạy các ứng dụng Node của bạn với DEBUG=*
. Socket.IO hiện thực hiện gỡ lỗi cũng sẽ in ra thông báo gỡ lỗi bộ điều hợp Redis. Đầu ra mẫu:
socket.io:server initializing namespace / +0ms
socket.io:server creating engine.io instance with opts {"path":"/socket.io"} +2ms
socket.io:server attaching client serving req handler +2ms
socket.io-parser encoding packet {"type":2,"data":["event","payload"],"nsp":"/"} +0ms
socket.io-parser encoded {"type":2,"data":["event","payload"],"nsp":"/"} as 2["event","payload"] +1ms
socket.io-redis ignore same uid +0ms
Nếu cả quy trình chính và quy trình con của bạn đều hiển thị các thông báo phân tích cú pháp giống nhau, thì ứng dụng của bạn đang mở rộng quy mô phù hợp.
Sẽ không có vấn đề gì với thiết lập của bạn nếu bạn sử dụng một nhân viên duy nhất. Những gì bạn đang làm là từ tất cả bốn công nhân và do Redis xuất bản / đăng ký, các thông báo không bị trùng lặp mà được viết bốn lần, như bạn đã yêu cầu ứng dụng thực hiện. Đây là một sơ đồ đơn giản về những gì Redis làm:
Client <-- Worker 1 emit --> Redis
Client <-- Worker 2 <----------|
Client <-- Worker 3 <----------|
Client <-- Worker 4 <----------|
Như bạn có thể thấy, khi bạn phát ra từ một worker, nó sẽ xuất bản phát tới Redis và nó sẽ được sao chép từ các worker khác, những người đã đăng ký vào cơ sở dữ liệu của Redis. Điều này cũng có nghĩa là bạn có thể sử dụng nhiều máy chủ socket được kết nối cùng một phiên bản và một bộ phát trên một máy chủ sẽ được kích hoạt trên tất cả các máy chủ được kết nối.
Với cluster, khi một máy khách kết nối, nó sẽ kết nối với một trong bốn nhân viên của bạn, không phải cả bốn. Điều đó cũng có nghĩa là bất kỳ thứ gì bạn phát ra từ công nhân đó sẽ chỉ được hiển thị một lần cho khách hàng. Vì vậy, có, ứng dụng đang mở rộng quy mô, nhưng theo cách bạn đang làm, bạn đang phát ra từ tất cả bốn công nhân và cơ sở dữ liệu Redis đang làm cho nó như thể bạn đang gọi nó bốn lần trên một nhân công. Nếu khách hàng thực sự kết nối với tất cả bốn phiên bản socket của bạn, họ sẽ nhận được mười sáu thông báo mỗi giây, không phải bốn.
Loại xử lý ổ cắm phụ thuộc vào loại ứng dụng bạn sẽ có. Nếu bạn định xử lý các máy khách riêng lẻ, thì bạn sẽ không gặp vấn đề gì, bởi vì sự kiện kết nối sẽ chỉ kích hoạt cho một nhân viên trên một máy khách. Nếu bạn cần một "nhịp tim" chung, thì bạn có thể có một trình xử lý ổ cắm trong quy trình chính của mình. Vì các công nhân chết khi quy trình chính chết, bạn nên bù tải kết nối của quy trình chính và để các con xử lý các kết nối. Đây là một ví dụ:
var cluster = require('cluster');
var os = require('os');
if (cluster.isMaster) {
// we create a HTTP server, but we do not use listen
// that way, we have a socket.io server that doesn't accept connections
var server = require('http').createServer();
var io = require('socket.io').listen(server);
var RedisStore = require('socket.io/lib/stores/redis');
var redis = require('socket.io/node_modules/redis');
io.set('store', new RedisStore({
redisPub: redis.createClient(),
redisSub: redis.createClient(),
redisClient: redis.createClient()
}));
setInterval(function() {
// all workers will receive this in Redis, and emit
io.sockets.emit('data', 'payload');
}, 1000);
for (var i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
}
if (cluster.isWorker) {
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var RedisStore = require('socket.io/lib/stores/redis');
var redis = require('socket.io/node_modules/redis');
io.set('store', new RedisStore({
redisPub: redis.createClient(),
redisSub: redis.createClient(),
redisClient: redis.createClient()
}));
io.sockets.on('connection', function(socket) {
socket.emit('data', 'connected to worker: ' + cluster.worker.id);
});
app.listen(80);
}
Trong ví dụ, có năm phiên bản Socket.IO, một phiên bản chính và bốn phiên bản con. Máy chủ chính không bao giờ gọi listen()
vì vậy không có chi phí kết nối trên quá trình đó. Tuy nhiên, nếu bạn gọi một hàm phát trên quy trình chính, nó sẽ được xuất bản lên Redis và bốn quy trình công nhân sẽ thực hiện phát trên các máy khách của họ. Điều này bù trừ tải kết nối cho công nhân và nếu một công nhân chết, thì logic ứng dụng chính của bạn sẽ không bị ảnh hưởng trong bản chính.
Lưu ý rằng với Redis, tất cả các lần phát, ngay cả trong không gian tên hoặc phòng sẽ được xử lý bởi các quy trình công nhân khác như thể bạn đã kích hoạt phát từ quá trình đó. Nói cách khác, nếu bạn có hai phiên bản Socket.IO với một phiên bản Redis, hãy gọi emit()
trên một socket trong worker đầu tiên sẽ gửi dữ liệu đến các máy khách của nó, trong khi worker hai sẽ thực hiện tương tự như khi bạn gọi lệnh phát ra từ worker đó.