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

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

  于是把源码下来看看 (apache-tomcat-6.0.39-src),细看tomcat下webapps/manager/WEB-INF/web.xml文件配置,发现原来tomcat是通过org.apache.catalina.manager.ManagerServlet这个类来提供以上服务的,跟踪此类doGet方法代码

 // --------------------------------------------------------- Public Methods

     /**
* Process a GET request for the specified resource.
*
* @param request
* The servlet request we are processing
* @param response
* The servlet response we are creating
*
* @exception IOException
* if an input/output error occurs
* @exception ServletException
* if a servlet-specified error occurs
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException { // Identify the request parameters that we need
// 取得访问路径,
String command = request.getPathInfo(); String path = request.getParameter("path");
String deployPath = request.getParameter("deployPath");
String deployConfig = request.getParameter("deployConfig");
String deployWar = request.getParameter("deployWar"); // Prepare our output writer to generate the response message
response.setContentType("text/html; charset=" + Constants.CHARSET); String message = "";
// Process the requested command
if (command == null || command.equals("/")) {
} else if (command.equals("/deploy")) {
message = deployInternal(deployConfig, deployPath, deployWar);
// 找到了,就是这个路径,往下看list方法
} else if (command.equals("/list")) {
} else if (command.equals("/reload")) {
message = reload(path);
} else if (command.equals("/undeploy")) {
message = undeploy(path);
} else if (command.equals("/expire")) {
message = expireSessions(path, request);
} else if (command.equals("/sessions")) {
try {
doSessions(path, request, response);
return;
} catch (Exception e) {
log("HTMLManagerServlet.sessions[" + path + "]", e);
message = sm
.getString("managerServlet.exception", e.toString());
}
} else if (command.equals("/start")) {
message = start(path);
} else if (command.equals("/stop")) {
message = stop(path);
} else if (command.equals("/findleaks")) {
message = findleaks();
} else {
message = sm.getString("managerServlet.unknownCommand", command);
}
// 就是这个方法生成上面的那个页面
list(request, response, message);
}

list

 /**
* Render a HTML list of the currently active Contexts in our virtual host,
* and memory and server status information.
*
* @param request
* The request
* @param response
* The response
* @param message
* a message to display
*/
public void list(HttpServletRequest request, HttpServletResponse response,
String message) throws IOException { if (debug >= 1)
log("list: Listing contexts for virtual host '" + host.getName()
+ "'"); PrintWriter writer = response.getWriter(); // HTML Header Section
writer.print(Constants.HTML_HEADER_SECTION); // Body Header Section
Object[] args = new Object[2];
args[0] = request.getContextPath();
args[1] = sm.getString("htmlManagerServlet.title");
writer.print(MessageFormat.format(Constants.BODY_HEADER_SECTION, args)); // Message Section
args = new Object[3];
args[0] = sm.getString("htmlManagerServlet.messageLabel");
if (message == null || message.length() == 0) {
args[1] = "OK";
} else {
args[1] = RequestUtil.filter(message);
}
writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args)); // Manager Section
args = new Object[9];
args[0] = sm.getString("htmlManagerServlet.manager");
args[1] = response.encodeURL(request.getContextPath() + "/html/list");
args[2] = sm.getString("htmlManagerServlet.list");
args[3] = response.encodeURL(request.getContextPath() + "/"
+ sm.getString("htmlManagerServlet.helpHtmlManagerFile"));
args[4] = sm.getString("htmlManagerServlet.helpHtmlManager");
args[5] = response.encodeURL(request.getContextPath() + "/"
+ sm.getString("htmlManagerServlet.helpManagerFile"));
args[6] = sm.getString("htmlManagerServlet.helpManager");
args[7] = response.encodeURL(request.getContextPath() + "/status");
args[8] = sm.getString("statusServlet.title");
writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args)); // Apps Header Section
args = new Object[6];
args[0] = sm.getString("htmlManagerServlet.appsTitle");
args[1] = sm.getString("htmlManagerServlet.appsPath");
args[2] = sm.getString("htmlManagerServlet.appsName");
args[3] = sm.getString("htmlManagerServlet.appsAvailable");
args[4] = sm.getString("htmlManagerServlet.appsSessions");
args[5] = sm.getString("htmlManagerServlet.appsTasks");
writer.print(MessageFormat.format(APPS_HEADER_SECTION, args)); // Apps Row Section
// Create sorted map of deployed applications context paths. // host就当是当前的tomcat吧,那么contexts就此tomcat下的所有应用
Container children[] = host.findChildren();
String contextPaths[] = new String[children.length];
// 循环每个应用
for (int i = 0; i < children.length; i++)
// 应用名称
contextPaths[i] = children[i].getName(); TreeMap sortedContextPathsMap = new TreeMap(); for (int i = 0; i < contextPaths.length; i++) {
// 应用部署路径
String displayPath = contextPaths[i];
sortedContextPathsMap.put(displayPath, contextPaths[i]);
} String appsStart = sm.getString("htmlManagerServlet.appsStart");
String appsStop = sm.getString("htmlManagerServlet.appsStop");
String appsReload = sm.getString("htmlManagerServlet.appsReload");
String appsUndeploy = sm.getString("htmlManagerServlet.appsUndeploy");
String appsExpire = sm.getString("htmlManagerServlet.appsExpire"); Iterator iterator = sortedContextPathsMap.entrySet().iterator();
boolean isHighlighted = true;
boolean isDeployed = true;
String highlightColor = null; while (iterator.hasNext()) {
// Bugzilla 34818, alternating row colors
isHighlighted = !isHighlighted;
if (isHighlighted) {
highlightColor = "#C3F3C3";
} else {
highlightColor = "#FFFFFF";
} Map.Entry entry = (Map.Entry) iterator.next();
String displayPath = (String) entry.getKey();
String contextPath = (String) entry.getValue();
Context context = (Context) host.findChild(contextPath);
if (displayPath.equals("")) {
displayPath = "/";
} if (context != null) {
try {
isDeployed = isDeployed(contextPath);
} catch (Exception e) {
// Assume false on failure for safety
isDeployed = false;
} args = new Object[7];
args[0] = URL_ENCODER.encode(contextPath + "/");
args[1] = RequestUtil.filter(displayPath);
if (context.getDisplayName() == null) {
args[2] = "&nbsp;";
} else {
args[2] = RequestUtil.filter(context.getDisplayName());
}
managerServlet
// 应用是否已启动
args[3] = new Boolean(context.getAvailable());
args[4] = response.encodeURL(request.getContextPath()
+ "/html/sessions?path="
+ URL_ENCODER.encode(displayPath));
if (context.getManager() != null) {
args[5] = new Integer(context.getManager()
.getActiveSessions());
} else {
args[5] = new Integer(0);
} args[6] = highlightColor;
//打印出一行关于此应用的信息,应用的URL,当前状态,session数等,具体见上图
writer.print(MessageFormat.format(APPS_ROW_DETAILS_SECTION,
args)); args = new Object[14];
args[0] = response
.encodeURL(request.getContextPath()
+ "/html/start?path="
+ URL_ENCODER.encode(displayPath));
args[1] = appsStart;
args[2] = response.encodeURL(request.getContextPath()
+ "/html/stop?path=" + URL_ENCODER.encode(displayPath));
args[3] = appsStop;
args[4] = response.encodeURL(request.getContextPath()
+ "/html/reload?path="
+ URL_ENCODER.encode(displayPath));
args[5] = appsReload;
args[6] = response.encodeURL(request.getContextPath()
+ "/html/undeploy?path="
+ URL_ENCODER.encode(displayPath));
args[7] = appsUndeploy; args[8] = response.encodeURL(request.getContextPath()
+ "/html/expire?path="
+ URL_ENCODER.encode(displayPath));
args[9] = appsExpire;
args[10] = sm.getString("htmlManagerServlet.expire.explain");
Manager manager = context.getManager();
if (manager == null) {
args[11] = sm.getString("htmlManagerServlet.noManager");
} else {
args[11] = new Integer(context.getManager()
.getMaxInactiveInterval() / 60);
}
args[12] = sm.getString("htmlManagerServlet.expire.unit"); args[13] = highlightColor; if (context.getPath().equals(this.context.getPath())) {
writer.print(MessageFormat.format(
MANAGER_APP_ROW_BUTTON_SECTION, args));
} else if (context.getAvailable() && isDeployed) {
writer.print(MessageFormat.format(
STARTED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args));
} else if (context.getAvailable() && !isDeployed) {
writer.print(MessageFormat.format(
STARTED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args));
} else if (!context.getAvailable() && isDeployed) {
writer.print(MessageFormat.format(
STOPPED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args));
} else {
writer.print(MessageFormat.format(
STOPPED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args));
} }
} // Deploy Section
args = new Object[7];
args[0] = sm.getString("htmlManagerServlet.deployTitle");
args[1] = sm.getString("htmlManagerServlet.deployServer");
args[2] = response.encodeURL(request.getContextPath() + "/html/deploy");
args[3] = sm.getString("htmlManagerServlet.deployPath");
args[4] = sm.getString("htmlManagerServlet.deployConfig");
args[5] = sm.getString("htmlManagerServlet.deployWar");
args[6] = sm.getString("htmlManagerServlet.deployButton");
writer.print(MessageFormat.format(DEPLOY_SECTION, args)); args = new Object[4];
args[0] = sm.getString("htmlManagerServlet.deployUpload");
args[1] = response.encodeURL(request.getContextPath() + "/html/upload");
args[2] = sm.getString("htmlManagerServlet.deployUploadFile");
args[3] = sm.getString("htmlManagerServlet.deployButton");
writer.print(MessageFormat.format(UPLOAD_SECTION, args)); // Diagnostics section
args = new Object[5];
args[0] = sm.getString("htmlManagerServlet.diagnosticsTitle");
args[1] = sm.getString("htmlManagerServlet.diagnosticsLeak");
args[2] = response.encodeURL(request.getContextPath()
+ "/html/findleaks");
args[3] = sm.getString("htmlManagerServlet.diagnosticsLeakWarning");
args[4] = sm.getString("htmlManagerServlet.diagnosticsLeakButton");
writer.print(MessageFormat.format(DIAGNOSTICS_SECTION, args)); // Server Header Section
args = new Object[7];
args[0] = sm.getString("htmlManagerServlet.serverTitle");
args[1] = sm.getString("htmlManagerServlet.serverVersion");
args[2] = sm.getString("htmlManagerServlet.serverJVMVersion");
args[3] = sm.getString("htmlManagerServlet.serverJVMVendor");
args[4] = sm.getString("htmlManagerServlet.serverOSName");
args[5] = sm.getString("htmlManagerServlet.serverOSVersion");
args[6] = sm.getString("htmlManagerServlet.serverOSArch");
writer.print(MessageFormat
.format(Constants.SERVER_HEADER_SECTION, args)); // Server Row Section
args = new Object[6];
args[0] = ServerInfo.getServerInfo();
args[1] = System.getProperty("java.runtime.version");
args[2] = System.getProperty("java.vm.vendor");
args[3] = System.getProperty("os.name");
args[4] = System.getProperty("os.version");
args[5] = System.getProperty("os.arch");
writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args)); // HTML Tail Section
writer.print(Constants.HTML_TAIL_SECTION); // Finish up the response
writer.flush();
writer.close();
}

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

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

public class HostManagerServlet
extends HttpServlet implements ContainerServlet

   /**
* Set the Wrapper with which we are associated.
*
* @param wrapper The new wrapper
*/
public void setWrapper(Wrapper wrapper) { //这里所有需要的对象都有了,其实下面我们需要拿到wrapper就够了
this.wrapper = wrapper;
if (wrapper == null) {
context = null;
host = null;
engine = null;
} else {
context = (Context) wrapper.getParent();
host = (Host) context.getParent();
engine = (Engine) host.getParent();
} // 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,具体代码如下:

 package manager.session.http.servlet;

 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; 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 RuntimeException(
"本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" />  

HTTP Status 500 - Error allocating a servlet instance


type Exception report

message Error allocating a servlet instance

description The server encountered an internal error that prevented it from fulfilling this request.

exception

javax.servlet.ServletException: Error allocating a servlet instance
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:879)
org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:617)
org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1760)
java.lang.Thread.run(Thread.java:722)

root cause

java.lang.SecurityException: Servlet of class manager.session.http.servlet.TomcatWrapperServlet is privileged and cannot be loaded by this web application
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:879)
org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:617)
org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1760)
java.lang.Thread.run(Thread.java:722)

note The full stack trace of the root cause is available in the Apache Tomcat/6.0.37 logs.


Apache Tomcat/6.0.37

来自:http://www.jspspace.com/ResearchTopics/Art-1757-17.html

tomcat集群时统计session与在线人数的更多相关文章

  1. Tomcat集群环境下session共享方案 通过memcached 方法实现

    对于web应用集群的技术实现而言,最大的难点就是:如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块.要实现这一点, 大体上有两种方式:一种是把所有Ses ...

  2. Nginx+tomcat集群中,session的共享

    nginx,tomcat集群后多个session分配到同一个应用 单节点低负荷的情况下,我们通常把一个WEB应用打成WAR包放WEB应用服务器,如TOMCAT下运行就行了(如图1).但随着用户量的增加 ...

  3. 用redis实现TOMCAT集群下的session共享

    上篇实现了 LINUX中NGINX反向代理下的TOMCAT集群(http://www.cnblogs.com/yuanjava/p/6850764.html) 这次我们在上篇的基础上实现session ...

  4. nginx整合tomcat集群并做session共享----测试案例

    最近出于好奇心,研究了一下tomcat集群配置,并整合nginx,实现负载均衡,session共享,写篇记录,防止遗忘.---------菜鸡的自我修炼. 说明:博主采用一个web项目同时部署到两台t ...

  5. Tomcat 集群中 实现session 共享的三种方法

    前两种均需要使用 memcached 或 redis 存储 session ,最后一种使用 terracotta 服务器共享. 建议使用 redis ,不仅仅因为它可以将缓存的内容持久化,还因为它支持 ...

  6. tomcat 集群配置,Session复制共享

    本配置在tomcat7上验证通过.通过此方法配置的集群,session信息将会被自动复制到各个节点. 1.配置Server.xml 在Server.xml中,找到被注释<Cluster/> ...

  7. Nginx+tomcat集群redis共享session应用方案

    部署环境 主机 软件版本 192.168.88.1 nginx-1.12.2+redis-3.2.11 192.168.88.2 apache-tomcat-7.0.79 + jdk1.8 192.1 ...

  8. Tomcat 集群模式下 Session 更新 Bug (redis memcached 及tomcat自已的集群)

    从 excel 中导入数据入系统,我们用的是先上传文件至服务器再分析所上传的文件逐行导入. 就是执行了一循环,在当前循环位置标识一下客户端就知道执行的进度了,以前的方式 是用 session.setA ...

  9. TOMCAT 集群之 PERSISTENT SESSION

    tomcat的session保存在数据库中,不是很复杂,写下来供大家参考. 准备工作: 两架Ubuntu Server 12.04 64位,确定两级服务器可以互相ping的通并属于同一个网段 安装jd ...

随机推荐

  1. Android通过LIstView显示文件列表

    [绥江一百]http://www.sj100.net                                                  欢迎,进入绥江一百感谢点击[我的小网站,请大家多 ...

  2. 每天一道LeetCode--169.Majority Elemen

    Given an array of size n, find the majority element. The majority element is the element that appear ...

  3. 关于对XE7中introduced in an ancestor and cannot be deleted的解决方案

    在Delphi XE7中设计Multi-Device Application 类型窗体中,发现删除一个组件时,提示introduced in an ancestor and cannot be del ...

  4. Cocos2d-x中__Array容器以及实例介绍

    __Array类在Cocos2d-x 2.x时代它就是CCArray类.它是模仿Objective-C中的NSArray类而设计的,通过引用计数管理内存.__Array继承于Ref类,因此它所能容纳的 ...

  5. c# 远程回收IIS应用池

    利用下列代码可实现IIS应用池的远程回收 var serverManager = ServerManager.OpenRemote(ip); var appPools = serverManager. ...

  6. strcpy、strncpy、strlcpy的区别

  7. 0<=i<iLen 在C++中

    for( i=0;0<= i<2; i++)这样的话会出现什么错误呢? 一直循环下去, 因为i>=一直成立

  8. C++ 中 const 和 static 的作用

    目录 const 的主要应用如下: const 关键字使用的注意点: C++中static关键字有三个明显的作用: const的主要应用如下: const 用于定义常量:const定义的常量编译器可以 ...

  9. android浪漫樱花凋零动态壁纸应用源码

    android浪漫樱花凋零动态壁纸应用源码,是从那个安卓教程网拿过来的,本项目是一套基于安卓的樱花动态壁纸项目源码,安装以后桌面没有图标,但是可以在修改壁纸-动态壁纸中找到.我的分辨率是480×854 ...

  10. zabbix3.0.3 设置邮件报警

    在zabbix3.0.3 设置报警这里卡了两天.终于解决了,这里我使用的mailx来作为发送邮件的客户端 1.设置mailx发信账号 yum -y install mailx ln -s /bin/m ...