我有多个使用redisstore水平扩展的socket.io服务器。我已经有效地设置了房间,并且成功地能够通过服务器等广播到房间。现在我正在尝试建立一个状态页面,我正在努力弄清楚如何简单地计算连接的用户数量所有服务器。
io.sockets.clients('room')和io.sockets.sockets只会告诉您该服务器上已连接客户端的数量,而不是所有连接到同一RedisStore的服务器。
建议?
感谢。
答案 0 :(得分:3)
当用户连接到聊天室时,您可以在RedisStore中以原子方式递增用户计数器。当用户断开连接时,您将减少该值。这样,Redis可以维护用户数,所有服务器都可以访问。
SET userCount = "0"
当用户连接时:
INCR userCount
当用户断开连接时:
DECR userCount
答案 1 :(得分:3)
以下是我使用Redis脚本解决它的方法。它需要2.6或更高版本,所以很可能现在仍然需要编译自己的实例。
每次进程启动时,我都会生成一个新的UUID并将其保留在全局范围内。我可以使用pid,但这感觉更安全一点。
# Pardon my coffeescript
processId = require('node-uuid').v4()
当用户连接(socket.io连接事件)时,我会根据该processId将该用户的id推送到用户列表中。我还将该密钥的到期时间设置为30秒。
RedisClient.lpush "process:#{processId}", user._id
RedisClient.expire "process:#{processId}", 30
当用户断开连接(断开连接事件)时,我将其删除并更新到期日。
RedisClient.lrem "process:#{processId}", 1, user._id
RedisClient.expire "process:#{processId}", 30
我还设置了一个以30秒的间隔运行的功能,基本上“ping”该键,使其保持在那里。因此,如果该过程意外死亡,那么所有这些用户会话基本上都会消失。
setInterval ->
RedisClient.expire "process:#{processId}", 30
, 30 * 1000
现在为了魔法。 Redis 2.6包括LUA脚本,它本质上提供了存储过程的功能。它非常快,而且处理器密集程度不高(他们将其与“差不多”运行的C代码进行比较)。
我的存储过程基本上遍历所有进程列表,并创建一个user:user_id键及其当前登录的总数。这意味着如果他们使用两个浏览器等登录,它仍然允许我使用逻辑来判断他们是否完全断开连接,或只是他们的一个会话。
我在所有进程上每隔15秒运行一次此函数,并且在连接/断开连接事件之后也是如此。这意味着我的用户计数很可能精确到秒,并且永远不会错误超过15到30秒。
生成该redis函数的代码如下所示:
def = require("promised-io/promise").Deferred
reconcileSha = ->
reconcileFunction = "
local keys_to_remove = redis.call('KEYS', 'user:*')
for i=1, #keys_to_remove do
redis.call('DEL', keys_to_remove[i])
end
local processes = redis.call('KEYS', 'process:*')
for i=1, #processes do
local users_in_process = redis.call('LRANGE', processes[i], 0, -1)
for j=1, #users_in_process do
redis.call('INCR', 'user:' .. users_in_process[j])
end
end
"
dfd = new def()
RedisClient.script 'load', reconcileFunction, (err, res) ->
dfd.resolve(res)
dfd.promise
然后我可以在我的脚本中使用它:
reconcileSha().then (sha) ->
RedisClient.evalsha sha, 0, (err, res) ->
# do stuff
我做的最后一件事是尝试处理一些关闭事件,以确保进程尝试最好不依赖redis超时并实际上正常关闭。
gracefulShutdown = (callback) ->
console.log "shutdown"
reconcileSha().then (sha) ->
RedisClient.del("process:#{processId}")
RedisClient.evalsha sha, 0, (err, res) ->
callback() if callback?
# For ctrl-c
process.once 'SIGINT', ->
gracefulShutdown ->
process.kill(process.pid, 'SIGINT')
# For nodemon
process.once 'SIGUSR2', ->
gracefulShutdown ->
process.kill(process.pid, 'SIGUSR2')
到目前为止,它一直很好用。
我还想做的一件事就是让redis函数返回任何已更改其值的键。这样,如果没有任何服务器主动知道的特定用户的计数已经改变(例如,如果进程死亡),我实际上可以发送事件。现在,我必须依赖轮询用户:*值再次知道它已被更改。它有效,但它可能会更好......
答案 2 :(得分:1)
我解决了这个问题,让每个服务器定期在redis中设置用户数,其中包含自己的pid:
每次setex userCount:<pid> <interval+10> <count>
然后状态服务器可以查询每个密钥,然后获取每个密钥的值:
每个keys userCount*
总计+ = get <key>
因此,如果服务器崩溃或关闭,那么在间隔+ 10后,其计数将从redis中退出
抱歉丑陋的伪代码。 :)
答案 3 :(得分:0)
您可以使用哈希键来存储值。
当用户连接到服务器1时,您可以在名为“userCounts”的键上设置名为“srv1”的字段。只需将值覆盖为使用HSET的当前计数。无需递增/递减。只需设置socket.io已知的当前值。
HSET userCounts srv1 "5"
当另一个用户连接到另一个服务器时,设置一个不同的字段。
HSET userCounts srv2 "10"
然后,任何服务器都可以通过返回“userCounts”中的所有字段并使用HVALS将它们添加到一起来获取总数来返回值列表。
HVALS userCounts
当服务器崩溃时,你需要运行一个脚本来响应崩溃,从userCounts中删除该服务器的字段或将其HSET转换为“0”。
您可以查看Forever以自动重启服务器。