3

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

我以这种方式配置了 PersistentManager:

上下文.xml

<Manager className="org.apache.catalina.session.PersistentManager">
   <Store className="org.apache.catalina.session.FileStore" directory="c:/somedir"/>
</Manager>

问题是会话显然从未刷新到磁盘。

我更改了<Manager>添加 maxIdleBackup 的配置:

<Manager className="org.apache.catalina.session.PersistentManager maxIdleBackup="1">

这样,我看到会话持续到磁盘需要将近一分钟。奇怪的是,文档指出它应该需要大约一秒钟:

maxIdleBackup:从上次访问会话到有资格持久保存到会话存储之前的时间间隔(以秒为单位),或 -1 禁用此功能。默认情况下,此功能被禁用。

其他配置:

按照文档我设置系统属性

org.apache.catalina.session.StandardSession.ACTIVITY_CHECK -> true

有没有办法立即将会话刷新到磁盘?是否可以立即保留会话中的任何更改?

更新:

我试图强制会话钝化并使用 刷新到磁盘maxIdleBackup="0" minIdleSwap="0" maxIdleSwap="1",但仍然需要将近一分钟。

4

3 回答 3

4

您还可以使用这个阀门,它是 Tomcat 发行版的一部分(至少在版本 8 中):

<Valve className="org.apache.catalina.valves.PersistentValve"/>

该节点必须插入到文件<Manager className="org.apache.catalina.session.PersistentManager">中的节点之前。context.xml

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

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

于 2016-06-02T11:29:51.930 回答
2

我遇到了这个问题,因为一旦我将 Tomcat 添加PersistentManager到配置中,Tomcat 需要一分钟才能关闭,但这也与您的问题有关:

您坚持使用 需要一分钟的原因PersistentManager是因为您没有调整processExpiresFrequency. 此设置调节PersistentManager运行其后台进程以使会话过期、持久化等的频率。默认值为 6。(请参阅文档:http: //tomcat.apache.org/tomcat-8.5-doc/config/manager。 html#Standard_Implementation )

根据代码,此值乘以engine.backgroundProcessorDelay您在<Engine>元素上设置的值。它的默认值是 10。所以 6*10 是 60 秒。如果你添加processExpiresFrequency="1"你的<Manager>元素,你会看到它会更快地关闭(10 秒)。如果这还不够快,您也可以将backgroundProcessorDelay其调低。您还需要设置maxIdleBackup1. 您不会立即获得绝对的持久性,但它非常快,并且不需要在接受的答案中进行自我描述的“丑陋的调整”。

(请参阅http://svn.apache.org/repos/asf/tomcat/tc8.5.x/tags/TOMCAT_8_5_6/java/org/apache/catalina/session/PersistentManagerBase.java中关于 setMaxIdleBackup 方法的 backgroundProcessorDelay 的评论)

于 2017-01-13T14:15:43.710 回答
1

我终于设法解决了这个问题:

  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;

}

重要

load 和 unload 方法必须留空:

    @Override
    public void load() throws ClassNotFoundException, IOException {
        // TODO Auto-generated method stub

    }

    @Override
    public void unload() throws IOException {
        // TODO Auto-generated method stub

    }

您必须覆盖 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();
} 
  1. 以上允许始终从文件(或缓存)中读取,但写操作呢?为此,我扩展了org.apache.catalina.session.StandardSession覆盖public void setAttribute(String name, Object value, boolean notify)public void removeAttribute(String name, boolean notify).

例子:

@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);
}

重要的:

在我们的例子中,真正的会话备份最终成为一个缓存(不是文件),当我们从中读取扩展的 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;
    }
于 2016-03-11T09:42:09.440 回答