2.4.2 测试错误恢复 访问url: http://localhost/examples/servlet/SessionExample 可以得到一个关于session的例子,我们用它来测试 集群的错误恢复能力。 测试步骤如下: 关闭tomcat1和tomcat2; 启动tomcat1 在浏览器中输入属性名tomcat1和属性值tomcat1再提交,返回的页面显示session中有刚刚输入的tomcat1属性; 启动tomcat2; 过一会后(等待tomcat2和tomcat1通信并复制信息)关闭tomcat1; 在浏览器中输入属性名tomcat2和属性值tomcat2再提交,返回的页面显示session中有刚刚输入的tomcat2属性,还有先前输入的tomcat1属性; 启动tomcat1; 过一会后(等待tomcat2和tomcat1通信并复制信息)关闭tomcat2; 在浏览器中输入属性名tomcat11和属性值tomcat11再提交,返回的页面显示session中有刚刚输入的tomcat11属性,还有先前输入的tomcat1和tomcat2属性; …… 2.4.3 测试多目传输的方法 如果运行测试失败,可以使用下面的JAVAGROUP方法测试机器的多目传输性: 启动多目接收器: java org.javagroups.tests.McastReceiverTest -mcast_addr 224.10.10.10 -port 5555 启动多目传输器: java org.javagroups.tests.McastSenderTest -mcast_addr 224.10.10.10 -port 5555 这样你在McastSenderTest窗口中输入内容,应该在McastReceiverWindow中可以看到结果。如果看不到结果,在 McastSenderTest运行参数中加入-ttl 32,如果还不行,可以修改多目地址再试试(注意避开 系统保留用的多目地址);如果还不行,就去问问网管吧! 2.4.4 对tomcat-javagroups的修改 tomcat-javagroups.jar中的org.apache.catalina.session.ReplicatedSession类的removeAttribute方法会导致stackoverflow错误,请按下面的代码对其进行修改: public void removeAttribute(String name, boolean notify, boolean jgnotify) { super.removeAttribute(name); if ( jgnotify ) { SessionMessage msg = new SessionMessage(notify?SessionMessage.EVT_ATTRIBUTE_REMOVED_WNOTIFY:SessionMessage. EVT_ATTRIBUTE_REMOVED_WONOTIFY, null, getId(), name, null, null); sendMessage(msg); } } public void removeAttribute(String name, boolean notify) { removeAttribute(name,notify,true); } 3 jetspeed集群我们现在知道了如何配置、甚至拥有一个集群环境,接下来本文分析Jetspeed的集群现状,主要包括repository和Session数据;为了使分析具有目的,在分析Jetspeed的集群现状之前,先讲述了集群需求和RunData对象。读者可以用集群环境来验证和调试Jetspeed的集群功能。 3.1 集群要求 《Memory Session Replication》一文中讲述了支持集群的应用程序需注意的要点,现在对关于应用系统开发时应注意的事项总结如下: 保存在Session中的对象必须实现java.io.Serializable接口; 从session中获取对象修改后必须用session.setAttribute方法重置session中的属性,因为只有setAttribute能导致session复制。 Java VM不支持类变量的序列化,所以要注意failover不能依赖类变量; 保证各个服务实体的配置完全一样; 保证session状态是唯一决定当前任务状态的东西,临时文件、类变量等会使得错误恢复难以实现、行为可能琢磨不定; 利用request.setAttribute()保存当前请求级的状态,减少服务实体间通信次数。 尽量不要在session中保存大对象,提高服务实体间通信性能。 3.2 RunData对象 RunData对象概念来自于Turbine,在Jetspeed中RunData对象的类型是DefaultJetspeedRunData,这个类扩展了Turbine中的DefaultTurbineRunData类。Jetspeed系统接到用户浏览器的URL请求,进行计算和信息处理,最后返回给浏览器HTTP代码流的整个过程中的代码都可以访问同一个RunData对象。所以RunData对象是Jetspeed系统中各个代码模块共享信息的机制。 3.3 Jetspeed的Repository Repository 一般指一个 软件系统赖以启动、运行的持久性环境,包括启动Repository和运行Repository两部分。启动Repository用于决定系统启动时的参数,系统运行时不会改变它,如果改变了这些参数,软件系统必须重新启动;运行Repository指实时影响软件系统业务操作的参数,这些参数可以被用户或管理员当系统在线时改变。现在的趋势是:尽量减少启动Repository,而扩大运行Repository;针对Repository的修改最好能使用管理性框架,比如SNMP和JMX。Jetspeed的repository主要在Xreg、psml和Properties文件中实现。 Xreg是jetspeed的注册表,用于登记portlet、control、controller、skin、mediatype等原始资源的定义,jetspeed中缺省地把它实现为文件形式,各种类型有自己的注册表文件; Psml 是门户结构标记语言的简称,用于组织xreg中的原始资源形成一个对门户视图的定义,当用户使用 桌面浏览器访问jetspeed系统时,这个系统根据用户的URL定位一个Psml文档,接着解释这个文档形成HTML代码流返回给浏览器,浏览器展现这个代码流从而形成视窗化的门户视图。Jetspeed中包括了对psml的数据库和文件两种实现方式; Properties定义了Jetspeed的重要服务及其参数,目前只有文件实现方式。 Jetspeed的启动Repository主要在Properties文件中,运行Repository在xreg和psml中。文件形式的实现大大阻碍了jetspeed支持集群的能力和表现,因为现在很少的应用 服务器集群能在一个文件系统上运行,如果Repository需要在运行时改变,就必须同步多个服务实体上的文件,这是一个相当麻烦的问题。如果Repository支持数据库实现形式,Jetspeed可以充分利用数据库的 存储和同步机制实现同一个Repository服务于多个Jetspeed。所以要想 jetspeed支持集群、拥有更佳表现,对Repository的数据库化是一个不可忽视的任务。 支持数据库的集群配置如下图:  这个图显示了在数据库集群环境下的jetspeed集群配置,数据库负载均衡器实现数据库集群的单一影像,例子有weblogic server中的multipool datasource,sql server 基于的windows 2000集群的单一集群IP, ORACLE RAC 的支持多连接地址的thin jdbc driver。 3.4 Jetspeed的Session数据 支持集群必须使得各个服务实体针对某个任务的执行环境是相同的,对于jetspeed来说就是针对各个URL请求,session的数据能在各个 jetspeed上复制。这些session被同一个sessionid所标识,这些标识可能来自浏览器的cookies或URL中。我们首先用一个 velocityportlet来显示Jetspeed的session中到底保存了什么数据,这个portlet的注册名字为 SessionPortlet。 3.4.1 SessionPortlet SessionPortlet是一个velocityPortlet,其类名可以是CustomizerVelocityPortlet或 VelocityPortlet,一般情况下没有必要开发一个新的portlet class。关于如何开发部署portlet的教程可见参考部分,现在我们分注册、控制助手、portlet模版和运行来讲述这个portlet。 3.4.1.1 注册 SessionPortlet用于显示目前的session数据。它在xreg中的注册代码为: parent="CustomizerVelocity" application="false">
check infomation in session
org.apache.jetspeed.portal.portlets.CustomizerVelocityPortlet cachedOnName="true" cachedOnValue="true"/> hidden="true" cachedOnName="true" cachedOnValue="true"/>
legend velocity.legend
3.4.1.2 控制助手Action portlets.SessionAction是Velocityportlet模版portlet的控制助手,在velocity解释模版前执行: public class SessionAction extends VelocityPortletAction { protected void buildNormalContext( VelocityPortlet portlet, Context context, RunData rundata ) { Map map = new HashMap(); Enumeration enumeration = rundata.getSession().getAttributeNames(); while (enumeration.hasMoreElements()) { Object key = (Object) enumeration.nextElement(); Object value = (Object)rundata.getSession().getAttribute(key.toString()); map.put(key, value); } context.put("sessions",map); } } 从上面的代码可以看出,这个控制助手在模版的模型(MVC中的M)环境中设置了一个保存了session数据的map数据结构。 3.4.1.3 portlet模版 SessionPortlet的模版文件是session.vm(MVC中的V),这个文件的内容如下: #foreach( $key in $sessions.keySet() )
- Key: $key -> Value: $sessions.get($key)
#end
3.4.1.4 定制psml和运行SessionPortlet 用admin/jetspeed或turbine/turbine帐号/口令登录到jetspeed系统后,可以在velocity.legend portlet分类中找到SessionPortlet,把它加入到你的psml中后可以看到SessionPortlet显示的session数据(你可以多多点击其它的URL,尽量地使jetspeed在session中多放一些数据):  从上面的session快照可以看出,Jetspeed的session数据主要分为两类:BaseJetspeedUser和JetspeedHttpStateManagerService$StateEntry,下面我们就分别来看看这两个类的情况。  3.4.2 BaseJetspeedUser 我们从《Session数据类图(部分)》可以看出BaseJetspeedUser实现了serializable接口。另外分析这个类及其父类的代码可了解到这个类的成员也实现了serializable接口。所以可以初步得出这个类是集群安全的。 DefaultTurbineRundata实现了这个类型的session数据的操作接口: 保存user对象到session中,这个方法登录后由TurbineAuthentication的login调用,登录前由 JetspeedSessionValidator的doPerform调用,它们同时会调用DefaultTurbineRundata的 setUser方法: public void save() { session.putValue(User.SESSION_KEY, (Object) user ); } public void setUser(User user) { this.user = user; } 从session中获取user对象数据,这个方法由JetspeedSessionValidator的doPerform调用: public void populate() { user = getUserFromSession(); if ( user != null ) { user.setLastAccessDate(); user.incrementAccessCounter(); user.incrementAccessCounterForSession(); } } public User getUserFromSession() { return getUserFromSession(session); } public static User getUserFromSession(HttpSession session) { try { return (User) session.getValue(User.SESSION_KEY); } catch ( ClassCastException e ) { return null; } } 删除session中的用户数据,目前没地方调用: public boolean removeUserFromSession() { return removeUserFromSession(session); } public static boolean removeUserFromSession(HttpSession session) { try { session.removeValue(User.SESSION_KEY); } catch ( Exception e ) { return false; } return true; }
|