1. 程式人生 > >依賴於session的線上人數統計

依賴於session的線上人數統計

最近工作中遇到一個問題,

在做線上人數統計時.我們實現了HttpSessionListner,HttpSessionAttributeListener裡面的attributedAdded()方法和attributeRemoved()的方法以及sessionDestroyed(),來操作一個map,將登陸的使用者資訊放入到map裡面,使用者退出時,再從map裡面移除.

生產環境是在websphere上,出現了一個現象.很多天之前登入的使用者還在線上人數裡面,並且通過日誌發現,此使用者登入之後並沒有做過任何業務操作.

第一步,重現問題.

開發環境是tomcat部署的,使用了一下四種方式產生的結果:

1.登入之後,點選登出,線上人數得到了更新

2.登入之後不做任何操作,等待session失效,線上人數得到了更新

3.登入之後關閉瀏覽器,等待session失效,線上人數得到了更新

4.登入之後,開啟另一個瀏覽器,當前使用者的線上人數資訊得到了更新

在開發環境無法重現此問題,我們轉戰到生產環境:

1.登入一個使用者,不做任何操作,等待到了session失效時間,線上人數沒有更新---經測試,進入sessionDestroyed()方法.沒有進入attributeRemoveded()方法

2.登入一個使用者,關閉瀏覽器,在人數沒有得到更新,等待session到失效時間--經過測試,沒有進入sessionDestroyed()方法.沒有進入attributeRemoveded

()方法

3.登入一個使用者,點選登出,線上人數得到更新

4.登入一個使用者,開啟另一個瀏覽器再次登入,線上人數使用者資訊沒有得到更新.

5.登入一個使用者,不做任何操作,等待session失效後,此時線上人數沒有得到更新.再次登入此使用者,線上人數沒有更新.點選登出,線上人數得到更新.

第二步,分析問題.

也就是說在websphere上面,只有主動呼叫invalidate(),進入監聽方法,執行操作.

問題來了,當到了到了session失效時間之後,有三種可能性:

1.session並沒有失效;

2.session失效了,但是沒有進入監聽執行sessionDestroyed()方法

3.session失效了,也進入了sessionDestroyed()方法,但是此時已經獲取不到session了,所以無法執行移除操作.

測試發現

1.登入一個使用者,不做任何操作---經測試,進入sessionDestroyed()方法.沒有進入attributeRemoveded()方法.在web.xml設定失效時間為2,但是時間是30min才執行的.

2.登入一個使用者,關閉瀏覽器,等待session到失效時間 ---經過測試,沒有進入sessionDestroyed()方法.沒有進入attributeRemoveded()方法 liuqian15:50關閉瀏覽器在web.xml設定失效時間為2,但是時間是90min才執行的.

When timeout, server executerequest.getRemoteUser() and re-login this user automatically, and sent aNew JESSIONID to user browser by including below header.

Set-Cookie JSESSIONID=0000O0OB4W_4sxtn6elSmolMxI9:-1; Path=/; HttpOnly


3.不知道是那個使用者進入sessionDestroyed()方法和attributeRemoveded()方法.但是sessionuser為空.並沒有執行移除操作.

4.更多的情況是不進入這幾個方法.

綜上所述:session注意的點有如下:

1.覆蓋機制

2.session失效後,呼叫監聽方法的時間不確定性.

3.在was上,session失效時間的不確定性.

最終的解決辦法是再實現一個過濾器,每次客戶端傳送請求是,先將所有session檢查一遍,使用系統當前時間與最後一次操作時間相比,如果差大於session的有效時間.則主動呼叫session的失效方法.這樣就會正常觸發監聽方法.達到人數統計的目的.

貼上程式碼:

package com.sunshine.monitor.comm.filter;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sunshine.core.util.DateUtil;
import com.sunshine.monitor.comm.bean.UserSession;
import com.sunshine.monitor.comm.startup.AppSystemListener;
import com.sunshine.monitor.system.manager.bean.SysUser;

public class OnlineUserListener
		implements
			HttpSessionListener, HttpSessionAttributeListener, Filter {
	
	private Logger log = LoggerFactory.getLogger(OnlineUserListener.class);
	
	/**
	 * 使用者Session物件
	 */
	public static final String USER_SESSION_NAME = "userSession";

	/**
	 * 線上人數
	 */
	public static AtomicInteger lineCount = new AtomicInteger(0);
	
	private final ReentrantLock lock = new ReentrantLock();
	
	public final static String LISTENER_NAME = "_login";
	
	private static volatile Map<String, SysUser> users = new ConcurrentHashMap<String, SysUser>();
	private static volatile Map<String, HttpSession> sessions = new ConcurrentHashMap<String, HttpSession>();
	
	/**
	 * 需要人工新增屬性_login,觸發此方法
	 */
	@Override
	public void attributeAdded(HttpSessionBindingEvent hbe) {
		if(LISTENER_NAME.equals(hbe.getName())){
			// 使用者登入成功後,將HttpSession儲存到一個map.
			HttpSession session = hbe.getSession();
			//System.out.println(hbe.getName() + "-HttpSessionAttributeListener(attributeAdded)...........");
			SysUser user = (SysUser) hbe.getValue();
			String yhdh = user.getYhdh();
			final ReentrantLock _lock = this.lock;
			_lock.lock();
			try {
				boolean flag = users.containsKey(yhdh);
				users.put(yhdh, user);
				sessions.put(yhdh, session);
				if(!flag){
					lineCount.addAndGet(1); // 累加器
				}
				System.out.println(yhdh + "-HttpSessionAttributeListener(attributeAdded)..........." + lineCount.intValue());
			} finally {
				_lock.unlock();
			}
		}
		
	}
	
	/**
	 * 需要人工刪除屬性_login,觸發此方法
	 */
	@Override
	public void attributeRemoved(HttpSessionBindingEvent hbe) {
		if(LISTENER_NAME.equals(hbe.getName())){
			System.out.println(hbe.getName() + "-HttpSessionAttributeListener(attributeRemoved)...........");
			SysUser user = (SysUser) hbe.getValue();
			if(user == null)
				return;
			String yhdh = user.getYhdh();
			final ReentrantLock _lock = this.lock;
			_lock.lock();
			try{
				if(users.containsKey(yhdh)){
					users.remove(user.getYhdh());
					sessions.remove(user.getYhdh());
					lineCount.decrementAndGet();
					System.out.println(yhdh + "-HttpSessionAttributeListener(attributeRemoved)..........." + lineCount.intValue());
				}
			} finally {
				_lock.unlock();
			}
		}
	}

	@Override
	public void attributeReplaced(HttpSessionBindingEvent hbe) {
		
	}
	
	/**
	 * 會話建立時執行
	 */
	@Override
	public void sessionCreated(HttpSessionEvent he) {
		/*HttpSession session = he.getSession();
		UserSession userSession = (UserSession)session.getAttribute(USER_SESSION_NAME);
		if(userSession != null){
			final ReentrantLock _lock = this.lock;
			_lock.lock();
			//System.out.println(he.getSource()+ "-HttpSessionListener(sessionCreated)...........");
			SysUser user = userSession.getSysuser();
			if(user == null) return;
			String yhdh =user.getYhdh();
			try{
				boolean flag = users.containsKey(yhdh);
				if(!flag){
					users.put(yhdh, user);
					lineCount.addAndGet(1); // 累加器
//					System.out.println(yhdh+ "-HttpSessionListener(sessionCreated)..........." + lineCount.intValue());
				}
			} finally {
				_lock.unlock();
			}
		}*/
	}
	/**
	 * 會話消毀執行
	 */
	@Override
	public void sessionDestroyed(HttpSessionEvent he) {
		HttpSession session = he.getSession();
		UserSession userSession = (UserSession)session.getAttribute(USER_SESSION_NAME);
		if(userSession != null){
			log.info(he.getSource()+ "-HttpSessionListener(sessionDestroyed)...........");
			SysUser user = userSession.getSysuser();
			String yhdh = user.getYhdh();
			final ReentrantLock _lock = this.lock;
			_lock.lock();
			try{
				if(users.containsKey(yhdh)){
					users.remove(user.getYhdh());
					sessions.remove(user.getYhdh());
					lineCount.decrementAndGet();
					log.info(yhdh+ "-HttpSessionListener(sessionDestroyed)..........." + lineCount.intValue());
				}
			} finally {
				_lock.unlock();
			}
		}
	}

	public static Map<String, SysUser> getUsers() {
		return users;
	}
	
	public static void setUsers(Map<String, SysUser> vaildUsers) {
		users = vaildUsers;
	}
	
	public static Map<String, HttpSession> getSessions() {
		return sessions;
	}
	
	public static void setSessions(Map<String, HttpSession> vaildSessions) {
		sessions = vaildSessions;
	}

	@Override
	public void destroy() {
		
		
	}

	@Override
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		//HttpServletRequest request = (HttpServletRequest)arg0;
		//HttpSession session = request.getSession(false);
		for(Entry<String, HttpSession> entry : sessions.entrySet()){
			HttpSession ss = entry.getValue();
			if(ss!=null){
				log.info(ss.getId());
				log.info("系統當前時間:"+DateUtil.longToTime(System.currentTimeMillis()));
				log.info("最後一次操作時間:"+DateUtil.longToTime(ss.getLastAccessedTime()));
				// 系統當前時間-最後操作時間>則強制使session失效.
				if((System.currentTimeMillis() - ss.getLastAccessedTime())>ss.getMaxInactiveInterval()*1000){// session timeout
					log.info("強制使session:"+ss.getId()+"失效...");
					ss.invalidate();
				}
			}else{
				System.out.println("This session is null....");
			}
		}
		arg2.doFilter(arg0, arg1);
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub
		
	}
}