我正在构建一个部署到Heroku的应用程序,该应用程序使用WebSocket和Redis。
当我仅使用1个dyno时,WebSocket连接正常工作,但是当我缩放到2个dyno时,我发送事件两次,使应用程序执行一次。
const ws = require('ws')
const jwt = require('jsonwebtoken')
const redis = require('redis')
const User = require('../models/user')
function verifyClient (info, callback) {
let token = info.req.headers['sec-websocket-protocol']
if (!token) { callback(false, 401, 'Unauthorized') } else {
jwt.verify(token, Config.APP_SECRET, (err, decoded) => {
if (err) { callback(false, 401, 'Unauthorized') } else {
if (info.req.headers.gameId) { info.req.gameId = info.req.headers.gameId }
info.req.userId = decoded.aud
callback(true)
}
})
}
};
let websocketServer, pub, sub
let clients = {}
let namespaces = {}
exports.initialize = function (httpServer) {
websocketServer = new ws.Server({
server: httpServer,
verifyClient: verifyClient
})
pub = redis.createClient(Config.REDIS_URL, { no_ready_check: true, detect_buffers: true })
pub.auth(Config.REDIS_PASSWORD, function (err) {
if (err) throw err
})
sub = redis.createClient(Config.REDIS_URL, { no_ready_check: true, detect_buffers: true })
sub.auth(Config.REDIS_PASSWORD, function (err) {
if (err) throw err
})
function handleConnection (socket) {
// socket.send(socket.upgradeReq.userId);
socket.userId = socket.upgradeReq.userId // get the user id parsed from the decoded JWT in the middleware
socket.isAlive = true
socket.scope = socket.upgradeReq.url.split('/')[1] // url = "/scope/whatever" => ["", "scope", "whatever"]
console.log('New connection: ' + socket.userId + ', scope: ' + socket.scope)
socket.on('message', (data, flags) => { handleIncomingMessage(socket, data, flags) })
socket.once('close', (code, reason) => { handleClosedConnection(socket, code, reason) })
socket.on('pong', heartbeat)
if (socket.scope === 'gameplay') {
try {
User.findByIdAndUpdate(socket.userId, { $set: { isOnLine: 2, lastSeen: Date.now() } }).select('id').lean()
let key = [socket.userId, socket.scope].join(':')
clients[key] = socket
sub.psubscribe(['dispatch', '*', socket.userId, socket.scope].join(':'))
} catch (e) { console.log(e) }
} else {
console.log('Scope : ' + socket.scope)
}
console.log('Connected Users : ' + Object.keys(clients))
}
function handleIncomingMessage (socket, message, flags) {
let scope = socket.scope
let userId = socket.userId
let channel = ['dispatch', 'in', userId, scope].join(':')
pub.publish(channel, message)
}
function handleClosedConnection (socket, code, reason) {
console.log('Connection with ' + socket.userId + ' closed. Code: ' + code)
if (socket.scope === 'gameplay') {
try {
User.findByIdAndUpdate(socket.userId, { $set: { isOnLine: 1 } }).select('id').lean()
let key = [socket.userId, socket.scope].join(':')
delete clients[key]
} catch (e) {
console.log(e)
}
} else {
console.log('Scope : ' + socket.scope)
}
}
function heartbeat (socket) {
socket.isAlive = true
}
sub.on('pmessage', (pattern, channel, message) => {
let channelComponents = channel.split(':')
let dir = channelComponents[1]
let userId = channelComponents[2]
let scope = channelComponents[3]
if (dir === 'in') {
try {
let handlers = namespaces[scope] || []
if (handlers.length) {
handlers.forEach(h => {
h(userId, message)
})
}
} catch (e) {
console.log(e)
}
} else if (dir === 'out') {
try {
let key = [userId, scope].join(':')
if (clients[key]) { clients[key].send(message) }
} catch (e) {
console.log(e)
}
}
// otherwise ignore
})
websocketServer.on('connection', handleConnection)
}
exports.on = function (scope, callback) {
if (!namespaces[scope]) { namespaces[scope] = [callback] } else { namespaces[scope].push(callback) }
}
exports.send = function (userId, scope, data) {
let channel = ['dispatch', 'out', userId, scope].join(':')
if (typeof (data) === 'object') { data = JSON.stringify(data) } else if (typeof (data) !== 'string') { throw new Error('DispatcherError: Cannot send this type of message ' + typeof (data)) }
pub.publish(channel, data)
}
exports.clients = clients
这在localhost上工作。
请让我知道是否需要提供更多信息或代码。对此表示感谢,谢谢!
答案 0 :(得分:0)
您发布的代码中有很多无关的信息,因此很难准确地理解您的意思。
但是,如果我理解正确,那么您当前有多个工作人员dyno实例订阅某种发布/订阅网络中的相同频道。如果您不希望所有的dyno都订阅相同的频道,则需要加入一些逻辑以确保您的频道在dyno上分布。
一种简单的方法可能是使用类似this answer中所述的逻辑。
在您的情况下,您也许可以使用socket.userId作为键来在dynos上分配频道。