集群与jetspeed

导读:

  这样你在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">  parent="CustomizerVelocity" application="false">

  

   SessionPortlet

   check infomation in session

  

   org.apache.jetspeed.portal.portlets.CustomizerVelocityPortlet

   cachedOnName="true" cachedOnValue="true"/>  cachedOnName="true" cachedOnValue="true"/>

   hidden="true" 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;

  }

  3.4.2.1 用户登录

  用户在jetspeed的首页中输入用户名和口令,接着点击登录(login)按钮,可以激活JLoginUser.doPerfom->TurbineAuthentication.login->DefaultTurbineRundata.save->JetspeedSessionValidator.doPerform-> DefaultTurbineRundata.populate系列步骤。

  如果properties配置中的配置项automatic.logon.enable 的值为true,JLoginUser.doPerfom还会设置浏览器cookies:username 和logincookie。username是成功登录的用户名, logincookie是一个随机值,会保存到用户数据库中。

  当用户访问jetspeed的首页时,JetspeedSessionValidator.doPerform检查RunData对象中的当前用户,如果没有登录而且automatic.logon.enable 的值为true,它会从cookies中获取username 和logincookie,再从用户数据库中查寻用户的logincookie,如果它们相等则调用下面的代码设置RunData的用户数据:

  data.setUser(user);

  user.setHasLoggedIn(new Boolean(true));

  user.updateLastLogin();

  data.save();

  至于针对不同的用户,首页中显示的portlet由缺省screen模版中调用JetspeedTool的方法(有一套PSML定位算法)来决定。

  3.4.2.2 当session过期之后显示匿名用户的主页

  当session过期,Turbine.doget首先会创建新的session,接着激活JetspeedSessionValidator.doPerform-> JetspeedSecurity.getAnonymousUser->DefaultTurbineRundata.save系列步骤。

  JetspeedSessionValidator.doPerform会设置缺省screen模版。

  3.4.2.3 用户登出

  当用户登录之后,点击Jetspeed系统右上角的登出(logout)按钮,可以激活JLogOut.doPerform-> TurbineAuthentication.logout-> TurbineAuthentication.getAnonymousUser-> DefaultTurbineRundata.save系列步骤。

  TurbineAuthentication.getAnonymousUser从数据库中得到匿名用户的用户数据(根据properties配置中user.anonymous项)。

  如果properties配置中配置项automatic.logon.enable 的值为true,JLogOut.doPerform还会删除浏览器和当前request的cookies:username 和logincookie,防止后面的JetspeedSessionValidator拿着先前的用户数据自动登录。JLogOut.doPerform最后设置data的缺省screen模版。

  3.4.3 JetspeedHttpStateManagerService$StateEntry

  我们从《Session数据的类图(部分)》可以看出StateEntry没有实现了Serializable接口。把它放到session的属性中不是集群安全的。Serializable接口只是个标志接口,它不拥有任何函数和数据成员,

  为了使其集群安全化,首先必须让StateEntry实现Serializable接口。

  DefaultJetspeedRunData拥有下列对StateEntry类型的session数据操作接口:

  用户session接口,获取保存用户session数据的SessionState。这个SessionState保存的session数据以"org.apache.jetspeed.services.statemanager.JetspeedHttpStateManagerService"+ sessionID为key。

  public SessionState getUserSessionState()

  {

  StateManagerService service = (StateManagerService)TurbineServices

  .getInstance().getService(StateManagerService.SERVICE_NAME);

  if (service == null) return null;

  return service.getSessionState(getSession().getId());

  }

  request的Session接口,获取保存当前request session数据的SessionState。这个SessionState保存的session数据以"org.apache.jetspeed.services.statemanager.JetspeedHttpStateManagerService"+ (sessionID和profileID组成的pageSessionID)为key。

  public SessionState getPageSessionState()

  {

  StateManagerService service = (StateManagerService)TurbineServices

  .getInstance().getService(StateManagerService.SERVICE_NAME);

  if (service == null) return null;

  return service.getSessionState(getPageSessionId());

  }

  Portlet的Session接口,获取保存Portlet session数据的SessionState。这个SessionState保存的session数据以"org.apache.jetspeed.services.statemanager.JetspeedHttpStateManagerService"+ pageSessionID+portletID为key。

  public SessionState getPortletSessionState(String id)

  {

  // get the StateManagerService

  StateManagerService service = (StateManagerService)TurbineServices

  .getInstance().getService(StateManagerService.SERVICE_NAME);

  if (service == null) return null;

  String pageInstanceId = getPageSessionId();

  return service.getSessionState(pageInstanceId + id);

  }

  3.4.3.1 类图

  BaseStateManagerService有一个类型为Map的成员变量m_httpSessions,以Thread对象为key,HttpSession对象为值。HttpSession对象中属性的key 是前面DefaultJetspeedRunData的StateEntry类型的session数据操作接口的key,属性的值为StateEntry对象。StateEntry对象的成员变量m_key保存操作接口的key,成员变量m_map是一个Map对象,以后面我们要讲的setAttribute方法的name参数为 key,value参数为值。

  3.4.3.2 初始化

  下面的顺序图是一个简图,主要用于解释BaseStateManagerService的成员变量m_httpSessions的映射如何被填充和清除。

  Turbine是一个servlet,其doGet方法是jetspeed系统的入口。

  填充

  Turbine请求JetspeedRunDataService生成RunData对象,JetspeedRunDataService调用HttpServiceRequest的getSession(true)方法获取与当前请求对应的httpSession对象(以true为参数,getSession在当前session无效时会返回一个新的httpSession对象,否则返回先前请求的httpSession对象),JetspeedRunDataService接着调用JetspeedHttpStateManagerService的setCurrentContext(httpSession对象)方法,这个方法会以当前的Thread为key,参数httpSession对象为值填充BaseStateManagerService的成员变量m_httpSessions。

  清除

  doGet方法填充了m_httpSessions,并作了好多事情之后,在即将退出之前调用了JetspeedRunDataService的putRunData(data)方法,这个方法再调用JetspeedHttpStateManagerService的clearCurrentContext()方法删除BaseStateManagerService的成员变量m_httpSessions中以当前Thread为key的Map项。

  下图显示了m_httpSessions对象经过初始化后的内存状态快照,体现了m_httpSessions对象保留的Thread-〉HttpSession的映射关系。

  3.4.3.3 属性操作

  当DefaultJetspeedRunData通过session操作接口获取SessionState之后,其它就可以使用SessionState对象的成员方法操作状态属性了。这两个方法是:

  public void setAttribute( String name, Object value );

  public void removeAttribute( String name );

  在从RunData对象处获取sessionState对象后,jetspeed代码可以调用这个对象的属性操作方法。

  setAttribute(name,value)操作的大概步骤是:

  (1) 主要步骤:

  1.1sessionState对象利用自己的key,结合参数name,value调用JetspeedHttpStateManagerService的setAttribute(key,name,value)方法;

  1.1.1JetspeedHttpStateManagerService调用自己的getState(key)方法在参数key的帮助下获取保存在当前线程session中的StateEntry对象的m_map变量,这个过程由1.1.1.1-1.1.1.4组成;

  1.1.2得到StateEntry对象的m_map变量后,JetspeedHttpStateManagerService接着先处理m_map中的先前的参数name对应的属性值,再设置参数name对应的属性值新值为参数value。

  (2) 候选步骤:

  1.1.1a 如果session中没有相应的StateEntry对象,则先生成并往一个session中加入一个。

  getAttribute(name)操作的大概步骤是:

  (1) 主要步骤:

  2.1sessionState对象利用自己的key,结合参数name调用JetspeedHttpStateManagerService的getAttribute(key,name)方法;

  2.1.1JetspeedHttpStateManagerService调用自己的getState(key)方法在参数key的帮助下获取保存在当前线程session中的StateEntry对象的m_map变量;

  2.1.2得到StateEntry对象的m_map变量后,JetspeedHttpStateManagerService接着调用m_map对象的get(name)方法获取属性值。

  下图体现了这些方法执行后HttpSession对象保留的key-> StateEntry对象以及StateEntry对象的Name->Value的映射关系。

  3.5 修改建议

  (1) 实现数据库形式的repository。根据前面的集群需求第五条,必须把repository数据库化才能使得集群下的各个jetspeed的资源视图相同。

  (2) StateEntry。根据前面的集群需求第一条,必须让StateEntry实现Serializable接口。目前StateEntry是一个内部类,为了让JVM的Serializer设施能顺利创建StateEntry对象,最好把其public化。

  (3) setAttribute要重设session属性。根据前面的集群需求第二条,session对象的setAttribute是导致复制的引子,我们必须在改变session属性后调用session对象的setAttribute方法重置session属性,如下图所示。

  虽然Jetspeed中这样模式的代码如下:

  更改JetspeedHttpStateManagerService的setAttribute方法。

  对下面类中的doXXX方法按照这个模式进行修改。

  controllers.MultiColumnControllerAction;

  portlets.CustomizeSetAction;

  controllers.RowColumnControllerAction;

  注意StateEntry中的值的序列性。

  4 总结

  可以这样说,目前的jetspeed在设计和实现时没有考虑集群环境下的运行情况,本文的分析突出了jetspeed支持集群的主要症结、但不一定完善,甚至有不正确的地方,另外一个主要内容是分析jetspeed保存在session对象中的数据。希望本文有助于大家加深对集群的理解,有助于提醒大家在设计和开发软件系统时"keep clustering in mind"。

  参考资料

  Memory Session Replication描述了集群的session 复制原理。

  Apache 2.x + Tomcat 4.x 描述了如何利用jk配置集群的负载均衡。

  Jk和jk2的文档讲述了相应的配置细节。

  Bea weblogic 8.1 描述了j2ee应用的集群配置与实现。

  教程告诉你如何写jetspeed portlet。

  《Java Pitfalls中文版》 item 23 描述了java 对象序列化的详细过程。

  在配javagroup时,尽量避开保留用多目地址。



本文转自

http://soa.5d6d.com/redirect.php?fid=9&tid=30&goto=nextoldset
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值