Tomcat的Hazelcast会话存储会话属性消失
我试图安装一个Tomcat集群上的AWS和自AWS不支持IP多播,选项之一是tomcat clustering using DBTomcat的Hazelcast会话存储会话属性消失
,非常然而理解,由于性能损失与数据库调用相关,我目前正在考虑将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 :: [email protected]
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分布式地图。
商店的相应的“负载”的方法如下:
@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秒的默认间隔, 'load'方法永远不会被调用,净影响是所有保存的会话属性在一段时间后都会丢失,这是最不寻常的。从技术上讲,绑定到会话的任何新会话属性都将保存在Hazelcast中,一旦调用“save”方法并且管理器被配置为每5秒换出一次。
但是,会话属性丢失(新的),旧的仍然存在。但不管它是什么“负载”方法都没有被调用(至少我没有看到跟踪)。
对此的一些帮助将非常感激。
希望这可以帮助别人,这个问题实际上是在下面的代码段:
公共无效保存(会话的会话)抛出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);
公共会话负载(字符串的sessionId)抛出的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代码,我们可以看到它包含许多不会被序列化的瞬态属性。因此,在反序列化过程中,必须给这些属性赋予值,这是在'load'方法中完成的,但是,它的做法是错误的,首先将会话从ObjectInputStream的'readObjectData'方法反序列化以初始化瞬态属性。在StandardSession中,'readObjectData'调用'doReadObject'一个受保护的方法来重新初始化瞬态属性,而这又会期望提供的对象输入流是一系列对象。然而在我们的例子中,它是序列化的对象,而不是它期望的一系列对象。
事实上,在Tomcat上启用精细级别日志记录之后,只会看到此异常,否则不会。
解决方法很简单,StandardSession有一个方法'writeObjectData',它内部调用一个受保护的方法'doWriteObject',它将一系列对象中的会话状态写入输出流,读取此序列化的字节可解决问题。
奇怪。 ofc你会做到这一点,但仍然要确认 - 你有打印报告/调试点,看看load()是否被击中? – Dinesh
是的,有打印语句,在'load'中保存,请参考上面的代码,它也打印绑定到会话的属性,谢谢 – Ironluca
我看到了代码。我特别指出了load()后的调试语句(我在这里没有看到)。由于readObject是一个阻塞呼叫。 – Dinesh