2012-08-28 19 views
7

Mam wiele serwerów socket.io skalowanych w poziomie przy użyciu programu redisstore. Mam efektywnie skonfigurowane pokoje i mogę z powodzeniem nadawać do pokojów na serwerach itp. Teraz próbuję utworzyć stronę stanu, a tym, czego nie rozumiem, jest po prostu policzenie liczby użytkowników połączonych wszystkie serwery.Zliczanie użytkowników socket.io na poziomych serwerach

io.sockets.clients („pokój”) i io.sockets.sockets tylko powiedzieć liczbę podłączonych klientów na tym jednym serwerze, nie wszystkie serwery podłączone do tej samej RedisStore.

Sugestie?

Dzięki.

+0

Dlaczego po prostu zapytać każdy z serwerów i zsumować liczbę podłączonych klientów? – k00k

+0

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

Odpowiedz

1

Rozwiązałem to przez posiadające każdy serwer okresowo ustawić liczbę użytkowników w REDiS z upływem które obejmowały własnym PID:

każdy zrobić setex userCount:<pid> <interval+10> <count>

następnie serwer stan może zapytać każdego z tych klawiszy, a następnie uzyskać wartości dla każdego klucza:

dla każdego keys userCount* zrobić całkowity + = get <key>

więc jeśli jest awaria serwera lub jego wyłączenie następnie liczy spadnie o ut of redis po interwiecie + 10

Przepraszam za brzydki pseudokod. :)

+0

W jaki sposób uzyskujesz liczbę dla użytkowników każdego serwera? Wynik funkcji io.sockets.clients(). Nie zawsze jest poprawny. Na przykład: 1. Proces A jest uruchomiony i połączenie z 2 klientami. io.sockets.clients(). length zwróci poprawnie 2. 2. Rozpocznij nowy proces, B i połącz 2 klientów. B zwróci 2, jednak A będzie teraz zwracał 4, ponieważ subskrybował zdarzenia połączenia B. Liczby wydają się być jeszcze bardziej niedokładne podczas próby ponownego uruchomienia serwera i ponownego połączenia z klientami. – evilcelery

+1

Używam Object.keys (io.sockets.sockets) .length, ale wydaje się, że rośnie i nie kurczy się dokładnie, być może z tych samych powodów, które opisujesz. Musiałem więc podłączyć się do naszego systemu obecności, aby uzyskać dokładną liczbę. W tym celu zapisujemy nasz obiekt użytkownika na redis za pomocą socket.set, a następnie aktualizujemy ten obiekt aktywnością lub bezczynnością. Tak więc dla zliczania, co teraz robię, pętlę gniazda z pliku io.sockets.sockets i jeśli stan obecności użytkownika jest "aktywny", dodam je do licznika. – rbrc

3

Gdy użytkownik łączy się z pokojem rozmów, możesz atomicznie zwiększyć licznik użytkowników w swoim sklepie RedisStore. Gdy użytkownik się rozłączy, zmniejszasz jego wartość. W ten sposób Redis utrzymuje liczbę użytkowników i jest dostępny dla wszystkich serwerów.

Zobacz INCR i DECR

SET userCount = "0" 

Kiedy użytkownik łączy:

INCR userCount 

gdy rozłącza użytkowniczki:

DECR userCount 
+2

z wyjątkiem wypadków, w których serwer się zawiesza, wtedy te liczby stają się bez znaczenia. – rbrc

+1

Można utrzymywać osobną liczbę dla każdego serwera i podsumować je. Jeśli serwer przestanie działać, ustaw ten licznik tego serwera na 0. – JamesOR

+0

, który wymagałby osobnego procesu, który śledziłby serwery i ich liczniki. Naprawdę miałem nadzieję, że istnieje metoda czysto socket.io robienia tego. – rbrc

0

można użyć klawiszy skrótu do przechowywania wartości.

Gdy użytkownik łączy się z serwerem 1, można ustawić pole o nazwie "srv1" na kluczu o nazwie "userCounts". Wystarczy przesłonić wartość, niezależnie od tego, jaka jest aktualna liczba: HSET. Nie ma potrzeby zwiększania/zmniejszania. Po prostu ustaw aktualną wartość znaną z socket.io.

HSET userCounts srv1 "5" 

Gdy inny użytkownik łączy się z innym serwerem, ustaw inne pole.

HSET userCounts srv2 "10" 

Następnie każdy serwer może uzyskać łącznie wracając wszystkie pola z „userCounts” i dodając je ze sobą za pomocą HVALS zwraca listę wartości.

HVALS userCounts 

Gdy serwer wywala trzeba uruchomić skrypt w odpowiedzi na katastrofy, która usuwa pole tego serwera z userCounts lub HUstaw go do „0”.

Możesz spojrzeć na Forever, aby zautomatyzować ponowne uruchamianie serwera.

+0

Używam upstart, aby zrestartować serwery, które działają o wiele lepiej niż na zawsze (o czym już sporo włożyłem). Próbuję obliczyć kompletną awarię serwera, która zdarza się od czasu do czasu. Mam do tego monitorowanie (zabbix), ale otrzymanie zabbix, aby poinformować panel kontrolny, gdy serwer przestanie działać, wydaje mi się dość hackować. – rbrc

+0

Chociaż może być ustawienie wartości wygaśnięcia na tych wartościach redis może to zrobić .. – rbrc

+0

Niestety, wygasanie jest dostępne tylko dla kluczy, a nie dla poszczególnych pól hase. Ale może uda ci się coś wymyślić za pomocą kombinacji kluczy i pól. – JamesOR

3

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 ...

+0

To interesująca implementacja. Czy martwisz się kosztem 30-sekundowego pingu, jeśli masz podłączonych 10 000 klientów? – rbrc

+0

Niezupełnie. To nie zostało jeszcze przetestowane w tym zakresie. Redis staje się najważniejszym drugorzędnym komponentem w mojej aplikacji, dzięki czemu serwer otrzyma zasoby niezbędne do jego utrzymania. Jeśli widzę, że instancje aplikacji nie ulegają znacznym awariom, mogę zastosować inne podejście, które nie jest tak kosztowne. –

Powiązane problemy