Oto jak rozwiązałem to za pomocą skryptów Redis. Wymaga wersji 2.6 lub nowszej, więc najprawdopodobniej nadal wymaga skompilowania własnej instancji na teraz.
Za każdym razem, gdy proces się uruchamia, generuję nowy identyfikator UUID i pozostawiam go w zasięgu globalnym. Mógłbym użyć pid, ale czuję się trochę bezpieczniej.
# Pardon my coffeescript
processId = require('node-uuid').v4()
Kiedy łączy użytkownika (zdarzenie połączenie socket.io), I następnie wcisnąć identyfikator użytkownika do listy użytkowników na podstawie tego ProcessID. Ustawiam również wygaśnięcie tego klucza na 30 sekund.
Po rozłączeniu użytkownika (zdarzenie rozłączenia) usuwam i aktualizuję termin ważności.
RedisClient.lrem "process:#{processId}", 1, user._id
RedisClient.expire "process:#{processId}", 30
Ustanawiam również funkcję, która działa w 30-sekundowym odstępie, aby w zasadzie "pingować" ten klucz, aby pozostał tam. Jeśli więc proces przypadkowo umrze, wszystkie te sesje użytkownika zasadniczo znikną.
setInterval ->
RedisClient.expire "process:#{processId}", 30
, 30 * 1000
Teraz za magię. Redis 2.6 zawiera skrypty LUA, które zasadniczo zapewniają funkcjonalność procedury przechowywanej. Jest bardzo szybki i niezbyt intensywny procesor (porównują go do "prawie" działającego kodu C).
Moja procedura składowana zasadniczo wykonuje pętle na wszystkich listach procesów i tworzy klucz user_id: user_id z całkowitą liczbą bieżących loginów. Oznacza to, że jeśli są zalogowani w dwóch przeglądarkach itp., Nadal będę mógł używać logiki do sprawdzania, czy całkowicie się rozłączyły, czy tylko jednej z ich sesji.
Uruchomę tę funkcję co 15 sekund na wszystkich moich procesach, a także po zdarzeniu connect/disconnect. Oznacza to, że liczba moich użytkowników będzie najprawdopodobniej dokładna do drugiej, i nigdy niepoprawna przez więcej niż 15 do 30 sekund.
kod, aby wygenerować tę funkcję Redis wygląda następująco:
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
A potem mogę użyć tego w moim skrypcie później z:
reconcileSha().then (sha) ->
RedisClient.evalsha sha, 0, (err, res) ->
# do stuff
Ostatnią rzeczą zrobić, to spróbować i uchwyt niektóre zdarzenia wyłączające się, aby upewnić się, że proces nie próbuje polegać na przekroczeniu limitów czasu i faktycznie zatrzymuje się z gracją.
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')
Do tej pory działało świetnie.
Jedną z rzeczy, którą nadal chcę zrobić, jest sprawienie, aby funkcja redis zwracała wszystkie klucze, które zmieniły swoje wartości. W ten sposób mógłbym wysłać zdarzenie, gdyby liczba zmieniła się dla konkretnego użytkownika, bez wiedzy któregokolwiek z serwerów (np. Gdy proces zginie). Na razie muszę polegać na odpytywaniu użytkownika: * wartości ponownie, aby wiedzieć, że to się zmieniło. Działa, ale może być lepiej ...
Dlaczego po prostu zapytać każdy z serwerów i zsumować liczbę podłączonych klientów? – k00k
Ja również szukam sposobu, aby odpowiedzieć na to pytanie, bez konieczności ustanawiania dla niego jakiegoś obserwatora. Jednak FWIW wygląda na to, że każdy serwer zna wszystkich klientów połączonych ze wszystkimi serwerami - ale może również mieć starych klientów odłączonych od innego serwera. Wygląda na to, że socket.io nie uważał, że warto było wyłapać nieaktualnych klientów na innych serwerach, zamiast tego niektóre serwery nadadzą tylko niektóre puste przestrzenie. – Konklone