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. C#编写windows服务程序

    Windows Service这一块并不复杂,但是注意事项太多了,网上资料也很凌乱,偶尔自己写也会丢三落四的.所以本文也就产生了,本文不会写复杂的东西,完全以基础应用的需求来写,所以不会对Window ...

  2. Android环境搭建的步骤

    Android 环境搭建步骤 这里简单介绍一下学习Android之后如何搭建环境的问题 一.    在搭建环境之前,首先你要先下载Java JDK(根据系统位数选择下载是64位或32位的),Eclip ...

  3. 和阿文一起学H5——如何搜到超酷的GIF素材

    方法一: 1.条件搜索法 关键词 + gif 2.dribbble全球顶点设计师殿堂,里面有好多大师神作. https://dribbble.com/ 3.pinterest,号称灵感的春药的网站,收 ...

  4. kettle

    Kettle(中文名称叫水壶)是一款ETL工具,纯java编写,可以在Window.Linux.Unix上运行,绿色无需安装,数据抽取高效稳定.Kettle家族包括4个产品:Spoon.Pan.CHE ...

  5. 使用subst创建虚拟磁盘及设置分区卷标

    最近项目中要研究在"计算机"中添加虚拟磁盘,能够访问某远端目录,同时还要在资源管理器中可以看到创建的虚拟磁盘.关于虚拟磁盘,有几种方式: (1)映射网络磁盘:通过映射网络驱动器,可 ...

  6. css3学习笔记之背景

    background-size background-size指定背景图像的大小 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 &l ...

  7. lucene4入门(1)

    欢迎转载http://www.cnblogs.com/shizhongtao/p/3440325.html lucene你可以理解为一种数据库,他是全文搜索的一种引擎. 1.首先去官网download ...

  8. 代码优化—From <effective C++>

    1.尽可能的延后变量定义式的出现时间 不止应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止. 如果这样不仅能够避免构造和析构非必要对象,还可以避免无 ...

  9. lex&yacc4

    yacc: we cannt use the $$ value dirictly. we need get it irrotly;

  10. Hadoop示例程序WordCount编译运行

    首先确保Hadoop已正确安装及运行. 将WordCount.java拷贝出来 $ cp ./src/examples/org/apache/hadoop/examples/WordCount.jav ...