Java Tomcat—如何使用PersistentManager+;将会话立即持久化到磁盘;文件存储

Java Tomcat—如何使用PersistentManager+;将会话立即持久化到磁盘;文件存储,java,tomcat,openshift,tomcat8,openshift-enterprise,Java,Tomcat,Openshift,Tomcat8,Openshift Enterprise,我想将Tomcat的HttpSessions持久化到磁盘,以便它可以在可伸缩的云环境中使用。关键是(在云PaaS中)会有许多Tomcat节点,客户机可以被定向到其中的任何一个。我们希望持久化并从共享磁盘单元加载会话 我已通过以下方式配置PersistentManager: context.xml <Manager className="org.apache.catalina.session.PersistentManager"> <Store className="org

我想将Tomcat的HttpSessions持久化到磁盘,以便它可以在可伸缩的云环境中使用。关键是(在云PaaS中)会有许多Tomcat节点,客户机可以被定向到其中的任何一个。我们希望持久化并从共享磁盘单元加载会话

我已通过以下方式配置PersistentManager:

context.xml

<Manager className="org.apache.catalina.session.PersistentManager">
   <Store className="org.apache.catalina.session.FileStore" directory="c:/somedir"/>
</Manager>
有没有办法立即将会话刷新到磁盘?是否可以立即保持会话中的任何更改

更新:


我曾尝试使用
maxIdleBackup=“0”minIdleSwap=“0”maxIdleSwap=“1”
强制对会话进行钝化并刷新到磁盘,但仍然需要将近一分钟的时间。

我终于解决了这个问题:

  • 我扩展了
    org.apache.catalina.session.ManagerBase
    覆盖使用超类会话映射的每个方法,以便它直接攻击文件(或缓存) 例如:

    @Override
    public HashMap<String, String> getSession(String sessionId) {
        Session s = getSessionFromStore(sessionId);
        if (s == null) {
            if (log.isInfoEnabled()) {
                log.info("Session not found " + sessionId);
            }
            return null;
        }
    
        Enumeration<String> ee = s.getSession().getAttributeNames();
        if (ee == null || !ee.hasMoreElements()) {
            return null;
        }
    
        HashMap<String, String> map = new HashMap<>();
        while (ee.hasMoreElements()) {
            String attrName = ee.nextElement();
            map.put(attrName, getSessionAttribute(sessionId, attrName));
        }
    
        return map;
    
    }
    
    @Override
    public void setAttribute(String name, Object value, boolean notify) {
        super.setAttribute(name, value, notify);
        ((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
    }
    
    @Override
    public void removeAttribute(String name, boolean notify) {
        super.removeAttribute(name, notify);
        ((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
    }
    
    您必须覆盖startInternal和stopInternal以防止生命周期错误:

    @Override
    protected synchronized void startInternal() throws LifecycleException {
    
        super.startInternal();
    
        // Load unloaded sessions, if any
        try {
            load();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("standardManager.managerLoad"), t);
        }
    
        setState(LifecycleState.STARTING);
    }
    
    @Override
    protected synchronized void stopInternal() throws LifecycleException {
    
        if (log.isDebugEnabled()) {
            log.debug("Stopping");
        }
    
        setState(LifecycleState.STOPPING);
    
        // Write out sessions
        try {
            unload();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("standardManager.managerUnload"), t);
        }
    
        // Expire all active sessions
        Session sessions[] = findSessions();
        for (int i = 0; i < sessions.length; i++) {
            Session session = sessions[i];
            try {
                if (session.isValid()) {
                    session.expire();
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
            } finally {
                // Measure against memory leaking if references to the session
                // object are kept in a shared field somewhere
                session.recycle();
            }
        }
    
        // Require a new random number generator if we are restarted
        super.stopInternal();
    } 
    
    重要提示:

    在我们的例子中,真正的会话备份最终是一个缓存(而不是一个文件),当我们从中读取扩展的Tomcat会话(在我们的ManagerBase impl类中)时,我们必须以一种丑陋的方式对其进行调整,以便一切都正常工作:

        private Session getSessionFromStore(String sessionId){
            DataGridSession s = (DataGridSession)cacheManager.getCache("sessions").get(sessionId);
            if(s!=null){
                try {
                    Field notesField;
                    notesField = StandardSession.class.getDeclaredField("notes");
                    notesField.setAccessible(true);
                    notesField.set(s, new HashMap<String, Object>());
                    s.setManager(this);
                } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
                    throw new RuntimeException(e);
                }
            }
            return s;
        }
    
    专用会话getSessionFromStore(字符串sessionId){
    DataGridSessions=(DataGridSession)cacheManager.getCache(“sessions”).get(sessionId);
    如果(s!=null){
    试一试{
    字段notesField;
    notesField=StandardSession.class.getDeclaredField(“备注”);
    notesField.setAccessible(true);
    set(s,newhashmap());
    s、 setManager(本);
    }捕获(IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e){
    抛出新的运行时异常(e);
    }
    }
    返回s;
    }
    
    您也可以使用此阀,它是Tomcat分配系统的一部分(至少在版本8中):

    
    
    必须在
    context.xml
    文件中的
    节点之前插入此节点

    然后,它将使用存储来维护每个http请求上的会话。注意,文档假定同一客户机一次只发出一个http请求


    这将允许您在java ee服务器前使用非粘性会话负载平衡器。

    我遇到了这个问题,因为我在配置中添加了
    PersistentManager
    后,Tomcat花了一分钟时间关闭,但它也与您的问题有关:

    使用
    PersistentManager需要一分钟的原因是您没有调整
    processExpiresFrequency
    。此设置调节
    PersistentManager
    运行后台进程以使会话过期、持久化等的频率。默认值为6。(见文件:)

    根据代码,此值乘以您在
    元素上设置的
    引擎.backgroundProcessorDelay
    。它的默认值是10。所以6*10等于60秒。如果在
    元素上添加
    processExpiresFrequency=“1”
    ,您将看到它将更快地关闭(10秒)。如果速度不够快,您也可以将
    backgroundProcessorDelay
    调整得更低。您还需要将
    maxIdleBackup
    设置为
    1
    。你不会得到绝对直接的持久性,但它非常快,不需要在公认的答案中自我描述的“丑陋的调整”


    (请参阅中有关setMaxIdleBackup方法的BackgroundProcessor延迟的评论)

    在“从不”和“几乎一分钟”之间有很大的区别。我猜时间单位是分钟,不是秒。但它确实有效。我使用这种技术进行扩展,而不是将每个会话放入每个Tomcat的多播,这在我看来是完全疯狂的,我很高兴能够忍受偶尔的货币损失。我感谢您的输入,我来试试。有人能告诉DataGridSession和DataGridManager使用的maven他们认为一次只能满足一个请求吗?css/js资产呢?这个时候是没有用的谢谢你,你是我的英雄。这让我快发疯了。
    @Override
    protected synchronized void startInternal() throws LifecycleException {
    
        super.startInternal();
    
        // Load unloaded sessions, if any
        try {
            load();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("standardManager.managerLoad"), t);
        }
    
        setState(LifecycleState.STARTING);
    }
    
    @Override
    protected synchronized void stopInternal() throws LifecycleException {
    
        if (log.isDebugEnabled()) {
            log.debug("Stopping");
        }
    
        setState(LifecycleState.STOPPING);
    
        // Write out sessions
        try {
            unload();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("standardManager.managerUnload"), t);
        }
    
        // Expire all active sessions
        Session sessions[] = findSessions();
        for (int i = 0; i < sessions.length; i++) {
            Session session = sessions[i];
            try {
                if (session.isValid()) {
                    session.expire();
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
            } finally {
                // Measure against memory leaking if references to the session
                // object are kept in a shared field somewhere
                session.recycle();
            }
        }
    
        // Require a new random number generator if we are restarted
        super.stopInternal();
    } 
    
    @Override
    public void setAttribute(String name, Object value, boolean notify) {
        super.setAttribute(name, value, notify);
        ((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
    }
    
    @Override
    public void removeAttribute(String name, boolean notify) {
        super.removeAttribute(name, notify);
        ((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
    }
    
        private Session getSessionFromStore(String sessionId){
            DataGridSession s = (DataGridSession)cacheManager.getCache("sessions").get(sessionId);
            if(s!=null){
                try {
                    Field notesField;
                    notesField = StandardSession.class.getDeclaredField("notes");
                    notesField.setAccessible(true);
                    notesField.set(s, new HashMap<String, Object>());
                    s.setManager(this);
                } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
                    throw new RuntimeException(e);
                }
            }
            return s;
        }
    
    <Valve className="org.apache.catalina.valves.PersistentValve"/>