设为首页 收藏本站
查看: 1973|回复: 0

[经验分享] tomcat集群时统计session与在线人数

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-3-24 14:18:49 | 显示全部楼层 |阅读模式
本帖最后由 tythg 于 2014-3-24 14:21 编辑

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

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

  于是把源码下来看看 (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 = children.getName();

        TreeMap sortedContextPathsMap = new TreeMap();

        for (int i = 0; i < contextPaths.length; i++) {
            // 应用部署路径
            String displayPath = contextPaths;
            sortedContextPathsMap.put(displayPath, contextPaths);
        }

        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] = " ";
                } 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




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-16098-1-1.html 上篇帖子: 把tomcat服务器配置为windows服务的方法 下篇帖子: jdk-tomcat环境变量设置 在线人数 统计
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表