Pessoal, como vcs fazem para controlar o número de usuários logados em um sistema ? Preciso restringir um máximo de usuários logados simultaneamente por questões de performance.Pensei em várias coisas mas acho que estou pendendo para uma solucao via banco de dados mesmo… o que vcs acham ?
( RESOLVIDO ) Como controlar número de usuarios logados
12 Respostas
Isso depende ne???
estas a usar o que?? (Desktop, web ( jsf1, jsf2, struct)??? etc.
Cara ja pesquisou por poll de conexões ?
Se for Web, voce pode usar o HttpSessionListener
Lucas, realmente eu resolvi utilizando o HttpSessionListener. Vou postar a solução aqui assim que possível para quem se interessar. Mas obrigado a todos pela ajuda.
Pô cara, posta aí!
Estava procurando por isto nos últimos dias!
Grato!
mrbbm. Ficou legal, da pra controlar cada usuario na sessao em um Map e manter um controle de quantas sessoes abertas, barrar por ex mais de X usuarios logados ao mesmo tempo, etc… estou em curso agora mas hj ainda a tarde eu ja posto ok !
Blz! Ficarei acompanhando o tópico!
Valeu irmão…
mrbbm desculpe pela demora mermao, mas ai vai ! ( Pela segunda vez pq o GUJ fez o favor de dar refresh na pagina antes de eu terminar a resposta… )
Eu tenho um ambiente maven, Spring 2.5 e Struts 2. Mas essa solução serve para qualquer ambiente Java Web. No seu web.xml adicione o listener:
...
<listener>
<listener-class>br.ufu.academico.portalAluno.listeners.SessionListener</listener-class>
</listener>
...
Esse SessionListener é uma classe que implementa a interface HttpSessionListener, que possui 2 métodos importantes: sessionCreated() e sessionDestroyed(). Eles são invocados quando uma sessao é criada e destruida respectivamente. Veja o código abaixo:
package br.ufu.academico.portalAluno.listeners;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.log4j.Logger;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import br.ufu.academico.portalAluno.supportBeans.SessionManager;
public class SessionListener implements HttpSessionListener {
protected static org.apache.log4j.Logger log = Logger.getLogger(SessionListener.class);
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext sc = se.getSession().getServletContext();
if (sc != null) {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
if (context != null) { // SessionManager é um singleton do spring que controla as sessões ativas
SessionManager sessionManager = (SessionManager) context.getBean("sessionManager");
if (sessionManager != null) {
sessionManager.registerLogin(se.getSession().getId(),null);
log.debug("Sessão iniciada - sem autenticação [" + se.getSession().getId() + "] - Número de sessões: "+sessionManager.getTotalNumberOfSessions()+" - Número de usuários logados: "+sessionManager.getNumberOfAuthenticatedUsers());
}
}
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext sc = se.getSession().getServletContext();
if (sc != null) {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
if (context != null) {
// SessionManager é um singleton do spring que controla as sessões ativas
SessionManager sessionManager = (SessionManager) context.getBean("sessionManager");
if (sessionManager != null) {
sessionManager.registerLogout(se.getSession().getId()); // decrementa o contador de sessões ativas
log.debug("Sessão finalizada [" + se.getSession().getId() + "]. Número de sessões: "+sessionManager.getTotalNumberOfSessions()+ " - Número de usuários logados: "+sessionManager.getNumberOfAuthenticatedUsers());
}
}
}
}
}
Ok. O sessionManager é um bean singleton que irá centralizar minha lógica de controle de usuários na sessao. Então no meu applicationContext.xml do Spring eu tenho:
...
<!-- Session Manager -->
<bean id="sessionManager" scope="singleton" class="br.ufu.academico.portalAluno.supportBeans.SessionManager"/>
...
<bean id="userSettings" class="br.ufu.commons.to.UserSettings" scope="session" />
...
Ou seja, todos os beans com scopo de sessão, ao serem criados ou destruidos o container do servidor irá invocar esses métodos de criacao e destruicao da sessao através do listener. No meu caso eu tenho um bean chamado userSettings com scopo de sessao. Nesse Bean eu tenho as informacoes do usuario que me interessam. Agora veja o código do SessionManager:
package br.ufu.academico.portalAluno.supportBeans;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import br.ufu.commons.to.UserSettings;
public class SessionManager {
public static Integer loggedUsersNum = 0;
private Map<String,UserSettings> usersMap = new HashMap<String, UserSettings>();
public synchronized void registerLogout(String sessionId) {
usersMap.remove(sessionId);
}
public synchronized Integer getTotalNumberOfSessions() {
return usersMap.size();
}
public synchronized Integer getNumberOfAuthenticatedUsers() {
int number = 0;
Set<Map.Entry<String, UserSettings>> entries = usersMap.entrySet();
for(Map.Entry<String, UserSettings> entry: entries){
if(null != entry.getValue()){
number++;
}
}
return number;
}
public synchronized void registerLogin(String sessionId, UserSettings user) {
usersMap.put(sessionId, user);
}
}
Agora aqui está a questão. Eu preferi fazer da seguinte forma: quando o usuário cai na minha tela de login, meu userSettings com scopo de sessao sera invocado pelo Spring. Logicamente o metodo sessionCreated é invocado e eu decidi já colocar no meu map de usuários esse id de sessao, mesmo com o usuario sem logar. Quando o usuário loga, no meu método de login eu invoco :
...
sessionManager.registerLogin(ServletActionContext.getRequest().getSession().getId(),userSettings);
LOG.debug("Sessão iniciada . Número de sessões: "+sessionManager.getTotalNumberOfSessions()+" - Número de usuários logados: "+sessionManager.getNumberOfAuthenticatedUsers());
...
ou seja, registrei no meu map um usuário preenchido ( previamente através de uma query… ). Logicamente o usuário é aquele que anteriormente já foi colocado na sessao, eu sobrescrevo ele no map ( pois o id da sessao é o mesmo ). Ao deslogar eu tenho:
...
sessionManager.registerLogout(ServletActionContext.getRequest().getSession().getId());
...
. Não tem problema se o usuário sair sem deslogar. Quando a sessao expirar o método sessionDestroyed do listener sera invocado de toda forma, garantindo o controle da sessao. Já fiz todos os testes e a solução parece que resolveu meu problema. Tenho agora como saber exatamente quantas sessoes estao criadas no momento e quantos usuários estão logados. Logicamente o container não chama o método exatamente no momento que a sessão é destruida, as vezes demora um pouco mais, mas ele chama uma hora ou outra. Da pra ter um controle legal e consistente, o que resolve o problema de carga. No meu método de login eu resolvi barrar mais de 2 usuários logados, então a primeira verificação foi a seguinte:
...
Integer maxNumberUsersInSession = Integer.valueOf(getText("maxNumberUsersInSession"));
if(sessionManager.getNumberOfAuthenticatedUsers() >= maxNumberUsersInSession){
addActionError("login.session.limit");
return Action.ERROR;
}
...
Se o usuário tentar logar e já tiver 2 usuários, o sistema emite uma mensagem de erro. Logicamente o número 2 foi escolhido para testes. Bom, espero ter ajudado. Qualquer dúvida posta ai ! Abraço !
uma dúvida:
o timeout padrão de uma sessão é de 20 minutos…
se os dois usuários logados fecharem o browser sem deslogar, o próximo usuário terá que esperar 20 minutos pra usar o sistema?
AbelBueno sua dúvida é pertinente. O timeout é configurado no web.xml.
...
<session-config>
<session-timeout>10</session-timeout>
</session-config>
...
No cenario anterior sim ( deveriam aguardar os 2 usuários até a sessão expirar ). Mas logicamente não teremos esse cenario em produção. No meu caso haverá 500 slotes disponíveis devido à restrições de arquitetura, então esse tempo será bem menor. Juntamente a isso, meu tempo de expiração como pode ver é de apenas 10 minutos. Considerando esses dois fatores não espero que o usuário em época de pico precisará esperar mais de 1 min. Se voce observar isso é comportamento da web quando se tem restrições, como é o meu caso ( a instituição nao me deu condicoes orcamentarias de fazer uma clusterizacao ). O site do Banco do Brasil tem lógica parecida, note que a expiração deles é de uns 3 min.
Não sei o volume de sua aplicação, é dificil comentar. Mas no pior caso, seu usuário ainda teria que esperar 10 minutos.
Como sugestão eu deixaria a sessão com 1 minuto no web.xml e criaria um mecanismo de “keep-alive”.
Isso eliminaria qualquer chances do usuário esperar mais de 1 minuto quando houver slots disponíveis.
na verdade meu tempo de expiracao varia conforme a época. Na época de pico eu diminuo para 3 minutos. Mas dependendo do que o usuário estiver fazendo eu mantenho um “keep alive” na aplicacao para nao expirar a sessao enquanto o usuario está pensando ou preenchendo formularios etc. Mas quanto a espera, já pensei também em um mecanismo de fila, dizendo ao usuário quanto tempo ele terá de esperar e tudo mais… mas nao acho que seja preciso tudo isso… 