`

tomcat集群时统计session与在线人数

阅读更多
    tomcat集群时,原来通过HttpSessionListener实现类监听session的创建和销毁来统计在线人数的方法不再有效,因为不是每个人登陆都会在同一个tomcat服务器上,而在另一台tomcat上登陆的人的session是通过session复制创建的,而复制过程不会调用HttpSessionListener接口的方法,也一直没找着如何监听session复制的方法,所以就没法统计在线人了。

     今天突然回想起tomcat下的manager应用上面就能看到session数和session的内容,于是本文的实现原理就是,做一个类似这样的servlet,此servlet把tomcat上负责管理应用的对象保存下来,供我任意使用。在tomcat上看应用的信息时,使用的是http://localhost:8080/manager/html/list这个路径,页面信息见下图:



    于是把源码下来看看(最新版原码下载地址http://apache.etoak.com/tomcat/tomcat-6/v6.0.26/src/apache-tomcat-6.0.26-src.zip),细看tomcat下webapps/manager/WEB-INF/web.xml文件配置,发现原来tomcat是通过org.apache.catalina.manager.ManagerServlet这个类来提供以上服务的,跟踪此类doGet方法代码


 public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException {

        ......

        //取得访问路径,
        String command = request.getPathInfo();
        if (command == null)
            command = request.getServletPath();

        ......
        ......

        if (command == null) {
            writer.println(sm.getString("managerServlet.noCommand"));
        } else if (command.equals("/deploy")) {
            if (war != null || config != null) {
                deploy(writer, config, path, war, update);
            } else {
                deploy(writer, path, tag);
            }
        } else if (command.equals("/install")) {
            // Deprecated
            deploy(writer, config, path, war, false);
        } else if (command.equals("/list")) {//找到了,就是这个路径,往下看list方法
            list(writer);
        } else if ......
                  ......

        } else if (command.equals("/findleaks")) {
            findleaks(writer);
        } else {
            writer.println(sm.getString("managerServlet.unknownCommand",
                                        command));
        }

        // Finish up the response
        writer.flush();
        writer.close();

    }


    //就是这个方法生成上面的那个页面
    protected void list(PrintWriter writer) {

        ......

      //host就当是当前的tomcat吧,那么contexts就此tomcat下的所有应用
        Container[] contexts = host.findChildren();
        for (int i = 0; i < contexts.length; i++) {//循环每个应用
            Context context = (Context) contexts[i];
          //应用路径
            String displayPath = context.getPath();
            if( displayPath.equals("") )
                displayPath = "/";
            if (context != null ) {
                if (context.getAvailable()) {//如果应用已启动
                   /*打印出一行关于此应用的信息,应用的URL,当前状态,session数等,具体见上图 */
                    writer.println(sm.getString("managerServlet.listitem",
                                                displayPath,
                                                "running",
                                      "" + context.getManager().findSessions().length,
                                                context.getDocBase()));
                } else {
                    writer.println(sm.getString("managerServlet.listitem",
                                                displayPath,
                                                "stopped",
                                                "0",
                                                context.getDocBase()));
                }
            }
        }
    }



     注意:context.getManager().findSessions()可以取得所有session,但这是个org.apache.catalina.Session[]数组,不是HttpSession[]数组,但这个Session接口里面有个getSession方法,返回结果正是HttpSession类型,没错,就是循环这个数组并调用其getSession方法就可以取得所有在线用户了

     上面的Session[]数组是从context对象里面来的,而context是从host对象来的,host是个初始值为NULL的成员变量,是什么时候赋上值的?是在init方法执行前,setWrapper方法执行时赋的值,请看setWrapper方法代码

    public void setWrapper(Wrapper wrapper) {

        this.wrapper = wrapper;
        if (wrapper == null) {
            context = null;
            host = null;
            oname = null;
        } else {
            //这里所有需要的对象都有了,其实下面我们需要拿到wrapper就够了
            context = (Context) wrapper.getParent();
            host = (Host) context.getParent();
            Engine engine = (Engine) host.getParent();
            try {
                oname = new ObjectName(engine.getName() 
                        + ":type=Deployer,host=" + host.getName());
            } catch (Exception e) {
                // ?
            }
        }

        // Retrieve the MBean server
        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
        
    }


     setWrapper会在初始化时被调用,怎么实现的,首先看web.xml中对此servlet的配置,没什么特别,我们可以发散一下思维,struts2里面action如何能自动注入request对象?Spring如何让service监听事件?答案是一样的,那就是让你的类实现某个接口,你要的东西就给你了,对的,这里也一样,此servlet实现了ContainerServlet接口,初始的时候setWrapper方法才会被调用。

      是JAVA新手的看这里,我提出上面这些问题,不是想卖什么关子,只是想启发JAVA初学者们,当某天你们做设计时,可以参考这种方法,一句概括就是:只要你实现我的接口,我就可以让你做某事,而不需要任何额外的配置。当然这种设计的缺点就是入侵、偶合。举个简单的应用场景:每天晚上,我用一个定时器通过Spring搜索所有实现了“GarbageCleaner”接口的service bean,并调用其clean方法清理对应模块的垃圾数据,那么任何模块的service只要实现了此接口,就会被调用。

      回到正题,我也自已写个servlet并且实ContainerServlet接口吧,使用静态方法取得所有的session,具体代码如下:


import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.catalina.ContainerServlet;
import org.apache.catalina.Context;
import org.apache.catalina.Session;
import org.apache.catalina.Wrapper;

import com.esc.common.exception.BusinessException;

public class TomcatWrapperServlet
    extends HttpServlet implements ContainerServlet {
	private static final long serialVersionUID = 1L;
	
	//弄个静态变量,初始化后就记下来,以备随时使用
    private static Wrapper wrapper = null;
    public Wrapper getWrapper() {
        return wrapper;
    }
    public void setWrapper(Wrapper wrapper) {
    	TomcatWrapperServlet.wrapper = wrapper;
    }
    
    //doGet不做任何事情,只需要接收第一次请求,触发初始动作就完成它的使命了
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    		throws ServletException, IOException {
    	resp.getWriter().println("Hello world!");
    	resp.getWriter().flush();
    	resp.getWriter().close();
    }
    
    //初始化后可通过此静态方法取得所有session
    public static Map<String,HttpSession> fillSessions() {
    	if(wrapper==null){//没有初始化
			throw new BusinessException("本servlet未被初始化,您必须先通过URL访问本servlet后,才可以调用这个方法");
    	}
    	Map<String,HttpSession> sessions = new LinkedHashMap<String, HttpSession>();
    	
    	//取得本应用
    	Context context = (Context) wrapper.getParent();
    	//取得Session[]数组
    	Session[] temps = context.getManager().findSessions();
    	if(temps!=null && temps.length>0){
    		for (int j = 0; j < temps.length; j++) {
    			//Map<sessionId,session>
    			sessions.put(temps[j].getSession().getId(), temps[j].getSession());
			}
    	}
        return sessions;
    }

}



      在web.xml配置一下,然后启动应用,访问之,结果出现异常,是一个安全异常:TomcatWrapperServlet is privileged and cannot be loaded by this web application,说我的类是个特权类,不能被普通的web应用加载,为何manager这个应用又可以呢?把manager/META-INF/context.xml复制到我的应用,再加载,再访问,一切搞定,此文件内容只有一句
<Context antiResourceLocking="false" privileged="true" />


     其实在doGet方法中还可以学到很多管理tomcat应用的方法,也许自动构建系统也可以从这里入手,如果找到什么好的点子,再续下一篇。

     小弟出道几年,很不幸一直没有碰到很牛的师傅,一切都靠自已,找到这个方法未必是正道(也许能监听session复制,或者使用单点登陆等其它的办法),如果哪位有好的解决方案,欢迎拍砖!

     ----后记,session的复制是可以用HttpSessionListner监听到的,之前可能是因为某些集群配置属性未能配置完全,具体的配置见http://lgdlgd.iteye.com/blog/615307
  • 大小: 37.5 KB
分享到:
评论
22 楼 schweigen 2010-12-09  
用JMX 获取Tomcat session 数,这是最简单的一种方法了,而且对应用没有任何侵入性
21 楼 wzju64676266 2010-08-07  
Cookie,那么怎么统计?
mercyblitz 写道
lgdlgd 写道
wujiazhao88 写道
用cookie来统计

这为兄台能不能把你的想法说更详细些?



不行,如果浏览器不支持Cookie,那么怎么统计?

现在大型网站大多是用cookie,可能也有更好的方案,集群情况下不能用普通的做法,如果你的浏览器禁用cookie,那很多网站是不能正常浏览的
20 楼 tangbo530 2010-03-17  
wujiazhao88 写道
用cookie来统计

何解?
19 楼 25262875 2010-03-15  
我也不知道现在流行怎么计算在线人数
我做的时候是在重启tomcat的时候,将人员表所有的人员状态标记为离线,然后每个用户登陆的时候,就标记成在线状态,并把用户的sessionid存在用户表,然后统计在线人数,当用户正常退出,那就清空sessionid并且标记为离线,非正常的话,那就监听session失效,失效的时候,用sessionid来做唯一标识将用户状态更改为离线。
18 楼 lgdlgd 2010-03-15  
myreligion 写道
开启tomcat的JMX管理功能,然后查看bean,tomcat自己会监控session数。

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;


ObjectName on = new ObjectName("Standalone:type=Manager,path=/,host=localhost");
MBeanServer s_BeanServer = ManagementFactory.getPlatformMBeanServer();
Object val = s_BeanServer.getAttribute(on, "activeSessions");

if (val != null) {
	return ((Number) val).intValue();
}




这个挺有意思,又开眼界了...
17 楼 myreligion 2010-03-15  
开启tomcat的JMX管理功能,然后查看bean,tomcat自己会监控session数。

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;


ObjectName on = new ObjectName("Standalone:type=Manager,path=/,host=localhost");
MBeanServer s_BeanServer = ManagementFactory.getPlatformMBeanServer();
Object val = s_BeanServer.getAttribute(on, "activeSessions");

if (val != null) {
	return ((Number) val).intValue();
}


16 楼 mercyblitz 2010-03-15  
changeItMore 写道
这个可以参考Spring里面的代码,有HttpSessionListener的实现,监听session的创建与销毁


Spring爱莫能助。
15 楼 lgdlgd 2010-03-14  
changeItMore 写道
这个可以参考Spring里面的代码,有HttpSessionListener的实现,监听session的创建与销毁

本文不是在讨论HttpSessionListener如何实现......
14 楼 cnheroooooo 2010-03-14  
将session记录在数据库表中是一个好方法,一个问题是当我们的全部或者部分应用重启时,需要删除此表中的对应的数据,否则此记录表中所记录的数据就不正确了。
13 楼 changeItMore 2010-03-14  
这个可以参考Spring里面的代码,有HttpSessionListener的实现,监听session的创建与销毁
12 楼 lgdlgd 2010-03-14  
我已参照ZHH2009的提示,重从新配置集群,并且session的复制确实可以用原来的方法监听到,再次谢谢ZHH2009。
具体配置过程及文档参见 http://www.iteye.com/topic/615307
11 楼 mercyblitz 2010-03-14  
lgdlgd 写道
wujiazhao88 写道
用cookie来统计

这为兄台能不能把你的想法说更详细些?



不行,如果浏览器不支持Cookie,那么怎么统计?
10 楼 lgdlgd 2010-03-14  
wujiazhao88 写道
用cookie来统计

这为兄台能不能把你的想法说更详细些?
9 楼 lgdlgd 2010-03-14  
清晨阳光 写道
可以设置为内存表,加快读写速度。


内存表集群时又一堆同步的麻烦事,想过没?
8 楼 wujiazhao88 2010-03-14  
用cookie来统计
7 楼 清晨阳光 2010-03-14  
还有一点,如果不用tomcat,换作其他服务器呢,所以啊,还是在线用户表通用
6 楼 清晨阳光 2010-03-14  
最简单的办法,建立一个在线用户表,在session创建和销毁的时候实时更新这张表即可。SpringSecutiry打开会话保护的话,登录的时候,是先销毁session(很变态,但就是这样子的),然后再创建一个session,此种情况,如果要统计在线人数,甚至管理员要具体查看究竟哪些人在线,唯一的方案就是在线用户表。可以设置为内存表,加快读写速度。
5 楼 lgdlgd 2010-03-14  
   非常感谢ZHH2009的回复,一会实践一下,我用的是6.014版本,不过我是参照两三年前别人配置5.0版本的教程配的集群,原来在想,我发这些贴,高不承低不就的,很少人会回复。
    另外,不知道ZHH2009或哪位高人有没有配置过程的文档,有的话麻烦发我一份,再次感谢。邮箱:leegd@126.com
 
4 楼 ZHH2009 2010-03-14  
lgdlgd 写道
    tomcat集群时,原来通过HttpSessionListener实现类监听session的创建和销毁来统计在线人数的方法不再有效,因为不是每个人登陆都会在同一个tomcat服务器上,而在另一台tomcat上登陆的人的session是通过session复制创建的,而复制过程不会调用HttpSessionListener接口的方法,也一直没找着如何监听session复制的方法,所以就没法统计在线人了。


不知道你是用哪个Tomcat版本,
Tomcat6.0在进行session复制时是会触发HttpSessionListener的,
在org\apache\catalina\ha\session\DeltaManager.java类
的“handleSESSION_CREATED(SessionMessage msg,Member sender)”方法
会通过“session.setId(msg.getSessionID())”这一行代码间接触发所有的HttpSessionListener。

当然,在配置方面要注意三点:
1:在你的应用程序的WEB-INF\web.xml文件中要加入"<distributable/>"

2.在配置conf\server.xml文件时要加入<Cluster>配置项,
<Cluster>配置项还有一个<Manager>子项,
在<Manager>中可以设置一个"notifySessionListenersOnReplication"属性,
这个"notifySessionListenersOnReplication"属性默认是"true",
不能把它设为"false".

3.最好不要像下面这样简单的配置<Cluster>:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

而是要一个一个的配置<Manager>、<Channel>、<Channel>\<Membership>...

否则有时会出现奇怪的问题,
问题主要是由下面两个类产生的
org\apache\catalina\tribes\membership\McastService.java
org\apache\catalina\tribes\membership\McastServiceImpl.java

因为<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
默认情况下没有为“<Channel>\<Membership>”子项设置"bind"属性。

比如在我的电脑上测试<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>时,
只要连上宽带,当我启动第二个Tomcat实例时,并不能得到第一个Tomcat实例的地址信息,
因此两个Tomcat实例之间并不能相互收发信息,
断开宽带后又正常了。

如果显示加入<Membership>子项后总是能正常工作:
<Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
bind="127.0.0.1"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>

更具体的细节可以看看McastService类的start()方法,
以及McastServiceImpl类的setupSocket()方法。

有关Tomcat集群、Session复制的东西都在
org.apache.catalina.tribes、org.apache.catalina.ha这两个包中
3 楼 mercyblitz 2010-03-13  
继承这个类~

相关推荐

    tomcat集群实现session复制

    在IT领域,特别是Web应用服务器的管理与优化中,Tomcat集群实现Session复制是一个关键的技术点,它确保了高可用性和负载均衡,特别是在处理大量并发请求的场景下。本文将深入探讨这一主题,涵盖其原理、配置方法以及...

    tomcat集群session共享jar tomcat7专用jar

    这就是“Tomcat集群session共享”的核心需求。本篇将详细介绍如何在Tomcat7中实现基于Redis的Session共享,并涉及相关的jar包。 首先,让我们了解为何需要session共享。在单个Tomcat服务器中,Session信息存储在...

    tomcat集群session共享解决方案

    在学习这些解决方案时,可以参考“Tomcat集群资料”中的文档,它们通常包含配置示例、最佳实践和常见问题解答,帮助你更好地理解和实施session共享策略。同时,理解负载均衡原理、分布式系统设计以及安全性问题也是...

    redis+tomcat集群配置session共享

    本篇文章将详细探讨如何在Redis+Tomcat集群配置中实现Session共享。 首先,我们需要理解什么是Session。Session是Web应用中用于存储用户状态的一种机制,通常用于保存用户的登录信息、购物车等数据。在单台服务器...

    tomcat集群session共享

    以下是实现Tomcat集群session共享与Redis结合的步骤和知识点: 1. **配置Redis服务器**:首先,你需要安装并运行一个Redis实例。确保其稳定运行,并且能够被集群中的所有Tomcat服务器访问。 2. **选择session复制...

    Nginx+Tomcat+Memcached实现tomcat集群和session共享

    在构建高性能、高可用性的Web应用系统时,通常会采用Nginx作为反向代理和负载均衡器,Tomcat作为应用服务器处理Java Servlet和JSP,而Memcached用于存储和共享Session数据。这个配置可以有效地分发用户请求,提高...

    tomcat8集群实现session共享,内含session共享包

    在这个场景中,我们关注的是如何在Tomcat集群环境中实现Session共享,以便提高应用程序的可扩展性和可用性。标题和描述提到的“session共享包”是解决这一问题的关键。 **什么是Session?** 在Web应用中,Session是...

    tomcat8 redis集群 session共享 jar

    tomcat8 Redis集群 同步Session 中用到的jar 附带tomcat content.xml配置文件

    tomcat cluster 集群 session复制

    而实际情况下,采取Apache 加Tomcat进行负载均衡集群的时候,是可以不用将Session复制到所有的节点里, 比如有六个Tomcat实例 Tomcat1,Tomcat2,Tomcat3,Tomcat4,Tomcat5,Tomcat6 是可以配置成 三组互相复制...

    tomcat集群基于redis共享session使用到的jar包

    tomcat集群基于redis共享session使用到的所有jar包,放到tomcat的lib下即可使用 apache-tomcat-7.0.56+nginx-1.8.0+redis-3.0.6集群部署所需JAR包,session共享 tomcat-redis-session-manager1.2.jar jedis-2.6.2....

    tomcat7集群session共享memcache依赖包1.8.3

    在构建高可用的Web服务时,Tomcat集群是常见的选择,它可以提高系统的容错性和可扩展性。然而,集群中的各个节点之间如何有效地共享用户Session信息成为了一个关键问题。为了解决这个问题,我们可以利用第三方缓存...

    Tomcat7集群共享Session 基于redis进行统一管理

    在IT行业中,尤其是在Web...总的来说,利用Redis进行Tomcat集群Session共享是一种高效且灵活的方法,能够提高系统的可用性和用户体验。通过深入理解这一技术,开发者可以更好地构建和维护大规模分布式Web应用程序。

    tomcat集群使用redis解决session共享问题

    tomcat集群使用redis解决session共享问题,压缩包包含redisclient-win32.x86.2.0客户端、Redis-x64-3.2.100服务、tomcat-redis-session以及部署安装文档

    tomcat-redis-session-manager的jar包-包含Tomcat7和Tomcat8

    这两个jar包包含了实现session与Redis交互的所有必要组件,如session序列化和反序列化,以及与Redis服务器的通信接口。 在配置Tomcat-Redis-Session-Manager时,开发者需要在Tomcat的`context.xml`文件中添加相关的...

    tomcat8.5.29 redisclouder集群 session共享集群相关配置文件

    tomcat8.5.29 redisclouder集群 session共享集群相关配置文件 亲测可用tomcat8.5.29 redisclouder集群 session共享集群相关配置文件 亲测可用 tomcat8.5.29 redisclouder集群 session共享集群相关配置文件 亲测可用 ...

    Tomcat7集群实现共享session,已配置好。

    总之,通过合理配置Tomcat集群和使用Redis作为session存储,我们可以实现跨服务器的session共享,确保用户在多台服务器间的会话一致性。这个压缩包提供了一个预配置的解决方案,对于快速搭建和测试session共享环境...

    tomcat7集群实现session共享

    当我们谈论“Tomcat7集群实现session共享”时,我们关注的是如何在多个Tomcat实例之间有效地同步用户会话信息,以便在集群环境中提供高可用性和负载均衡。 首先,理解session共享的重要性。在Web应用中,session是...

    tomcat8集群session共享(redis处理)

    在构建高可用的Web服务时,Tomcat集群是常见的选择,但随之而来的一个问题就是如何在集群中的各个节点间共享Session。"Tomcat8集群session共享(redis处理)"的主题正是针对这一问题,通过集成Redis作为分布式缓存来...

    apache tomcat 集群 负责均衡 session复制

    NULL 博文链接:https://xueweiabcok.iteye.com/blog/1841448

    tomcat8集群redis实现session共享jar包

    5. **测试与优化**:启动Tomcat集群并验证用户会话是否可以在不同服务器之间正确共享。如果一切正常,可以考虑对Redis进行性能调优,例如调整Redis的内存限制、启用持久化策略等。 通过这种方式,你可以利用Redis的...

Global site tag (gtag.js) - Google Analytics