【问题标题】:Tomcat Hazelcast Session Store Session Attributes VanishingTomcat Hazelcast 会话存储会话属性消失
【发布时间】:2015-11-03 09:54:30
【问题描述】:

我正在尝试在 AWS 上设置 Tomcat 集群,由于 AWS 不支持 IP 多播,因此其中一个选项是 tomcat clustering using DB

这很好理解,但是,由于与数据库调用相关的性能损失,我目前正在考虑将 Hazelcast 作为会话存储。当前的 Hazelcast 过滤器方法对我不起作用,因为 Web 应用程序上还有其他过滤器,它们有些干扰,更好和更清洁的方法是使用自定义商店实现配置 PersistenceManager 并在 tomcat/ 上配置相同的过滤器conf context.xml,配置部分如下:

   <Manager className="org.apache.catalina.session.PersistentManager"
         distributable="true"
         maxActiveSessions="-1"
         maxIdleBackup="2"
         maxIdleSwap="5"
         processingTime="1000"
         saveOnRestart="true"
         maxInactiveInterval="1200">

         <Store className="com.hm.vigil.platform.session.HC_SessionStore"/>

</Manager>

会话保存在 Hazelcast 实例中,来自 tomcat 的跟踪如下:

---------------------------------------------------------------------------------------
HC_SessionStore == Saving Session ID == C19A496F2BB9E6A4A55E70865261FC9F SESSION == StandardSession[
C19A496F2BB9E6A4A55E70865261FC9F]
SESSION ATTRIBUTE :: USER_IDENTIFIER :: 50
SESSION ATTRIBUTE :: APPLICATION_IDENTIFIER :: APPLICATION_1
SESSION ATTRIBUTE :: USER_EMAIL :: x@y.com
SESSION ATTRIBUTE :: USER_ROLES :: [PLATFORM_ADMIN, CLIENT_ADMIN, PEN_TESTER, USER]
SESSION ATTRIBUTE :: CLIENT_IDENTIFIER :: 1
---------------------------------------------------------------------------------------
03-Nov-2015 15:12:02.562 FINE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.ca
talina.session.PersistentManagerBase.processExpires End expire sessions PersistentManager processing
Time 75 expired sessions: 0
03-Nov-2015 15:12:02.563 FINE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.ca
talina.session.PersistentManagerBase.processExpires Start expire sessions PersistentManager at 14465
43722563 sessioncount 0
03-Nov-2015 15:12:02.577 FINE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.ca
talina.session.PersistentManagerBase.processExpires End expire sessions PersistentManager processing
Time 14 expired sessions: 0

上面的跟踪如果来自被商店实现覆盖的“保存”方法,代码如下:

@Override
    public void save(Session session) throws IOException {

        //System.out.println("HC_SessionStore == Saving Session ID == "+session.getId()+" SESSION == "+session);

        try{

            String sessionId=session.getId();

            ByteArrayOutputStream baos=new ByteArrayOutputStream();
            ObjectOutputStream oos=new ObjectOutputStream(baos);

            oos.writeObject(session);
            oos.close();

            byte[] serializedSession=baos.toByteArray();
            sessionStore.put(sessionId,serializedSession);

            sessionCounter++;

            System.out.println("---------------------------------------------------------------------------------------");
            System.out.println("HC_SessionStore == Saving Session ID == "+sessionId+" SESSION == "+session);
            Enumeration<String> attributeNames=((StandardSession)session).getAttributeNames();
            while(attributeNames.hasMoreElements()){

                String attributeName=attributeNames.nextElement();
                System.out.println("SESSION ATTRIBUTE :: "+attributeName+" :: "+((StandardSession)session).getAttribute(attributeName));

            }//while closing
            System.out.println("---------------------------------------------------------------------------------------");

        }catch(Exception e){throw new IOException(e);}

    }//save closing

“sessionStore”是 Hazelcast 分布式地图。

store对应的'load'方法如下:

@Override
    public Session load(String sessionId) throws ClassNotFoundException, IOException {

        Session session=null;

        try{

            byte[] serializedSession=(byte[])sessionStore.get(sessionId);
            ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(serializedSession));

            //Read the saved session from serialized state
            //StandardSession session_=new StandardSession(manager);
            StandardSession session_=(StandardSession)ois.readObject();
            session_.setManager(manager);
            ois.close();

            //Initialize the transient properties of the session
            ois=new ObjectInputStream(new ByteArrayInputStream(serializedSession));
            session_.readObjectData(ois);
            session=session_;
            ois.close();

            System.out.println("===========================================================");
            System.out.println("HC_SessionStore == Loading Session ID == "+sessionId+" SESSION == "+session);
            Enumeration<String> attributeNames=session_.getAttributeNames();
            while(attributeNames.hasMoreElements()){

                String attributeName=attributeNames.nextElement();
                System.out.println("SESSION ATTRIBUTE :: "+attributeName+" :: "+session_.getAttribute(attributeName));

            }//while closing
            System.out.println("===========================================================");

        }catch(Exception e){throw new IOException(e);}

        return session;

    }//load closing

现在,最有趣的事情之一是,虽然“存储”方法以 60 秒的默认间隔被调用,但“加载”方法从未被调用,其净影响是任何保存的会话属性都会丢失过了一会儿,这是最不寻常的。从技术上讲,一旦调用 'save' 方法并将管理器配置为每 5 秒换出一次,绑定到会话的任何新会话属性都将保存在 Hazelcast 中。

但是,会话属性丢失了(新的),旧的仍然存在。但无论是什么,都不会调用“加载”方法(至少我没有看到跟踪)。

我们将不胜感激。

【问题讨论】:

  • 奇怪。 ofc 你会这样做但仍然要确认 - 你是否放置了一些打印语句/调试点来查看 load() 是否被命中?
  • 是的,有打印语句,'load'和save一样,请参考上面的代码,它也会打印会话绑定的属性,谢谢
  • 我看到了代码。我专门在 load() 之后指向调试语句(我在这里看不到)。由于 readObject 是一个阻塞调用。
  • 您还可以将 MapListener 附加到您的 sessionStore 地图以查看条目被驱逐/删除。 (这是为了调试会话属性丢失的问题。)使用地图监听器,您实际上可以看到添加和更新的内容。
  • Dinesh,以一种有助于诊断问题的方式,所以我升级了它,我将发布实际问题和解决方案

标签: session tomcat hazelcast hazelcast-imap


【解决方案1】:

希望这对某人有所帮助,问题实际上出在以下代码部分:

public void save(Session session) 抛出 IOException 方法:

        String sessionId=session.getId();

        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);

        oos.writeObject(session);
        oos.close();

        byte[] serializedSession=baos.toByteArray();
        sessionStore.put(sessionId,serializedSession);

public Session load(String sessionId) throws ClassNotFoundException, IOException 方法:

        byte[] serializedSession=(byte[])sessionStore.get(sessionId);
        ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(serializedSession));

        //Read the saved session from serialized state
        //StandardSession session_=new StandardSession(manager);
        StandardSession session_=(StandardSession)ois.readObject();
        session_.setManager(manager);
        ois.close();

        //Initialize the transient properties of the session
        ois=new ObjectInputStream(new ByteArrayInputStream(serializedSession));
        session_.readObjectData(ois);
        session=session_;
        ois.close();

如果您注意到,会话会立即序列化并保存到 Hazelcast,这本身不是问题。

现在,如果我们查看 StandardSession 的 Tomcat 代码,我们会看到它包含许多不会被序列化的瞬态属性。所以在反序列化过程中,这些属性必须被赋予值,这是在“加载”方法中完成的,但是,它做错了,首先它从 ObjectInputStream 的“readObjectData”方法中反序列化会话以初始化瞬态属性。在 StandardSession 中,“readObjectData”调用“doReadObject”一个受保护的方法来重新初始化瞬态属性,这反过来又期望提供的对象输入流是一系列对象。然而,在我们的例子中,它是整个序列化的对象,而不是它所期望的一系列对象。

事实上,在 Tomcat 上启用精细级别日志记录后,只会看到此异常,否则不会。

解决方法很简单,StandardSession有一个方法'writeObjectData'方法,它内部调用了一个受保护的方法'doWriteObject',它将一系列对象中的会话状态写入输出流,读取这个序列化字节即可解决问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    • 2014-07-23
    • 1970-01-01
    • 2011-02-12
    • 2012-01-14
    • 1970-01-01
    相关资源
    最近更新 更多