我正在运行Tomcat 8,并且正在设置自定义会话管理器以实现对Amazon DynamoDB的会话持久性。我正在将AWS Java session manager library用于Tomcat-DynamoDB,它似乎运行良好(尽管我的问题并不特定于该库)。我正在使用单个Tomcat实例(无群集)进行开发和运行测试。
有一些我不理解的异常行为。当我登录到我的站点时,将创建一个新会话。登录后,我可以立即将请求发送到Web服务器并执行操作,所有这些都需要一个有效的会话(该会话具有我的权限)。
问题是,在我登录后,新的会话条目不会立即持久保存到数据存储(DynamoDB)。实际上,每次创建新会话时,我都观察到,会话条目出现在DynamoDB中大约需要30秒到1分钟,有时甚至更长。
现在,很明显,之所以能够在登录后立即发出需要身份验证的请求,是因为单个Tomcat实例以某种方式缓存了会话数据,然后大概在空闲时将会话持久化到了数据存储中。
我担心的是,当我在带有负载均衡器的自动扩展组中使用实例集群时,如果会话在创建后没有立即持久保存到数据存储中,则登录后立即进行的后续请求可能会导致身份验证失败。如果群集中有2台计算机,则计算机A处理登录并缓存新创建的会话,然后计算机B处理后续请求,该请求将失败,因为计算机B不知道该会话,并且该会话将不在计算机B中。数据存储区。
另一方面,无效请求会立即从数据存储中删除会话。
我还应该提到我知道maxIdleBackup
选项,该选项当前设置为30秒。从该选项的文档中:
自从上一次访问会话以来,有资格保留到会话存储中的时间间隔(以秒为单位)...
我不介意会话数据不会立即更新(例如上次访问时间),如果我不介意将其更新为1或2分钟。但是,立即继续进行创建会话似乎是Tomcat无法提供的关键功能。
最后,我还应该提到,我已经遍历了我正在使用的会话管理器库的所有源代码(对于DynamoDB的持久性,只有几个类),并且它实际上仅处理从Tomcat会话到DynamoDB项目,反之亦然,加上过期的会话收割等。因此,在我看来,这就像是Tomcat问题,除非我误解了某些内容。
我已经经历过clustering how-to了,看来它的目的是通过以下方式来管理计算机集群:内存会话数据被连续复制并在集群中的所有计算机之间保持同步。这不是我想要的,因为我使用的是远程数据库(DynamoDB),因此每个Tomcat实例不必知道或关心群集中的任何其他节点。每个节点都有自己的会话缓存,并在必要时回退/更新数据库。
文档确实谈论了内置的BackupManager
作为<Manager>
的选项,但是我的理解是,它负责将会话数据备份到一台特定的计算机上。
实际上,我设法找到一种解决方法来解决我遇到的新会话未立即持久保存到数据库的问题。我将添加一个包含详细信息的答案,以使这个问题不会变成怪诞的。
最佳答案
这是我正在使用的解决方案,可以为我解决问题。我猜它可以被认为是一种hack,但是它正在运行,并且我认为这样做没有任何问题(但是如果有人担心,请告诉我)。
基本上,我只是在创建会话后立即强制会话保存。问题实际上是在寻找一种获取对会话管理器数据存储的引用的方法,因为Servlet API不会公开对其的访问。因此,首先我必须使用反射来获取对会话管理器的引用:
private static PersistentManagerBase getSessionManager(HttpSession session) {
StandardSession stdSession;
try {
Field facadeSessionField = StandardSessionFacade.class.getDeclaredField("session");
facadeSessionField.setAccessible(true);
stdSession = (StandardSession) facadeSessionField.get(session);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
System.out.println("Error attempting to retrieve session manager => "
+ e.getMessage() + " -- the new session has been created anyway, just not immediately persisted to the database");
e.printStackTrace();
return null;
}
return (PersistentManagerBase) stdSession.getManager();
}
...然后我可以引用
Store
对象。Store store = manager.getStore();
不幸的是,
Store.save(Session session)
调用(将会话持久保存到数据库)将仅接受实现Session
接口的对象。 HttpSession
不会,因此您必须创建一个StandardSession
对象,该对象实质上是新HttpSession
对象的副本,如下所示:private static Session createStandardSession(HttpSession newSession, Manager manager) {
StandardSession stdSession = new StandardSession(manager);
stdSession.setId(newSession.getId());
stdSession.setCreationTime(newSession.getCreationTime());
stdSession.setValid(true);
stdSession.setMaxInactiveInterval(newSession.getMaxInactiveInterval());
Enumeration<String> attrNames = newSession.getAttributeNames();
while ( attrNames.hasMoreElements() ){
String attrName = attrNames.nextElement();
stdSession.setAttribute(attrName, newSession.getAttribute(attrName));
}
return stdSession;
}
因此,将它们放在一起:
HttpSession newSession = request.getSession(true);
PersistentManagerBase manager = getSessionManager(newSession);
if (manager != null) {
manager.getStore().save( createStandardSession(newSession, manager) );
}
您需要Tomcat库
catalina.jar
来访问这些类:org.apache.catalina.session.StandardSession
org.apache.catalina.session.PersistentManagerBase