您的位置:首页 > 运维架构 > Tomcat

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

2013-07-23 08:58 369 查看
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方法代码

Java代码


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方法代码

Java代码


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方法才会被调用。

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

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

Java代码


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复制到我的应用,再加载,再访问,一切搞定,此文件内容只有一句

Xml代码


<Context antiResourceLocking="false" privileged="true" />

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

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

----后记,session的复制是可以用HttpSessionListner监听到的,之前可能是因为某些集群配置属性未能配置完全,具体的配置见http://lgdlgd.iteye.com/blog/615307
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: