基于memcached的SNA实现

基于memcached的SNA实现

系统要集群,使用SNA方案。
一、 缓存的处理
缓存要使用统一的缓存服务器,集中式缓存。
原先的实现采用ehcache。
在spring里的配置,以资源缓存为例:

<!--  EhCache Manager  -->
    
< bean  id ="cacheManager"  class ="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" >
        
< property  name ="configLocation" >
            
< value > classpath:ehcache.xml </ value >
        
</ property >
</ bean >

< bean  id ="resourceCacheBackend"
          class
="org.springframework.cache.ehcache.EhCacheFactoryBean" >
        
< property  name ="cacheManager"  ref ="cacheManager" />
        
< property  name ="cacheName"  value ="resourceCache" />
    
</ bean >

    
< bean  id ="resourceCache"
          class
="com.framework.extcomponent.security.authentication.services.acegi.cache.EhCacheBasedResourceCache"
          autowire
="byName" >
        
< property  name ="cache"  ref ="resourceCacheBackend" />
    
</ bean >

 
 

cacheManager负责对ehcache进行管理,初始化、启动、停止。
resourceCacheBackend负责实际执行缓存操作,put 、get、remove。
resourceCache实现具有业务语义的业务应用层面的缓存操作,内部调用resourceCacheBackend操作。

现在采用memcached。
关于客户端,采用文初封装的客户端,地址在http://code.google.com/p/memcache-client-forjava/。
使用spring的FactoryBean进行二次封装。同理:
memcachedManager负责对memcached进行管理,初始化、启动、停止。
代码:

/**
* User: ronghao
* Date: 2008-10-14
* Time: 10:36:30
* 管理Memcached 的CacheManager
*/
public   class  MemcachedCacheManagerFactoryBean  implements  FactoryBean, InitializingBean, DisposableBean {

    
protected   final  Log logger  =  LogFactory.getLog(getClass());

    
private  ICacheManager < IMemcachedCache >  cacheManager;

    
public  Object getObject()  throws  Exception {
        
return  cacheManager;
    }

    
public  Class getObjectType() {
        
return   this .cacheManager.getClass();
    }

    
public   boolean  isSingleton() {
        
return   true ;
    }

    
public   void  afterPropertiesSet()  throws  Exception {
        logger.info(
" Initializing Memcached CacheManager " );
        cacheManager 
=  CacheUtil.getCacheManager(IMemcachedCache. class ,
                MemcachedCacheManager.
class .getName());
        cacheManager.start();
    }

    
public   void  destroy()  throws  Exception {
        logger.info(
" Shutting down Memcached CacheManager " );
        cacheManager.stop();
    }
}
 

配置:


< bean  id ="memcachedManager"
          class
="com.framework.extcomponent.cache.MemcachedCacheManagerFactoryBean" />
   
 

resourceCacheBackend负责实际执行缓存操作,put 、get、remove。
代码:

/**
* User: ronghao
* Date: 2008-10-14
* Time: 10:37:16
* 返回  MemcachedCache
*/
public   class  MemcachedCacheFactoryBean  implements  FactoryBean, BeanNameAware, InitializingBean {

    
protected   final  Log logger  =  LogFactory.getLog(getClass());

    
private  ICacheManager < IMemcachedCache >  cacheManager;
    
private  String cacheName;
    
private  String beanName;
    
private  IMemcachedCache cache;

    
public   void  setCacheManager(ICacheManager < IMemcachedCache >  cacheManager) {
        
this .cacheManager  =  cacheManager;
    }

    
public   void  setCacheName(String cacheName) {
        
this .cacheName  =  cacheName;
    }

    
public  Object getObject()  throws  Exception {
        
return  cache;
    }

    
public  Class getObjectType() {
        
return   this .cache.getClass();
    }

    
public   boolean  isSingleton() {
        
return   true
    }

    
public   void  setBeanName(String name) {
        
this .beanName = name;
    }

    
public   void  afterPropertiesSet()  throws  Exception {
        
//  If no cache name given, use bean name as cache name.
        if  ( this .cacheName  ==   null ) {
        
this .cacheName  =   this .beanName;
    }
        cache 
=  cacheManager.getCache(cacheName);
    }
}

配置:

< bean  id ="resourceCacheBackend"
          class
="com.framework.extcomponent.cache.MemcachedCacheFactoryBean" >
        
< property  name ="cacheManager"  ref ="memcachedManager" />
        
< property  name ="cacheName"  value ="memcache" />
    
</ bean >
  resourceCache同上,替换新的实现类MemcachedBasedResourceCache即可。

二、 Session失效的处理
采用memcached作为httpsession的存储,并不直接保存httpsession对象,自定义SessionMap,SessionMap直接继承HashMap,保存SessionMap。

会话胶粘:未失败转发的情况下没必要在memcached保存的SessionMap和httpsession之间复制来复制去,眉来眼去。

利用memcached计数器保存在线人数。

系统权限采用了acegi,在acegi的拦截器链里配置snaFilter

< bean  id ="filterChainProxy"
          class
="org.acegisecurity.util.FilterChainProxy" >
        
< property  name ="filterInvocationDefinitionSource" >
            
< value >
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /**=snaFilter,httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor
            
</ value >
        
</ property >
</ bean >


注意需要配置在第一个。
snaFilter的职责:
1、 没有HttpSession时,创建HttpSession;
2、 创建Cookie保存HttpSession id;
3、 如果Cookie保存的HttpSession id与当前HttpSession id一致,说明是正常请求;
4、 如果Cookie保存的HttpSession id与当前HttpSession id不一致,说明是失败转发;失败转发的处理:
     4.1、根据Cookie保存的HttpSession id从memcached获取SessionMap;
     4.2、SessionMap属性复制到当前HttpSession;
     4.3、memcached删除SessionMap。
5、 判断当前请求url是否是登出url,是则删除SessionMap,在线人数减1.

代码:

public   void  doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) 
throws  IOException, ServletException {
        
final  HttpServletRequest hrequest  =  (HttpServletRequest) servletRequest;
        
final  HttpServletResponse hresponse  =  (HttpServletResponse) servletResponse;
        String uri 
=  hrequest.getRequestURI();
        logger.debug(
" 开始SNA拦截----------------- "   +  uri);
        HttpSession httpSession 
=  hrequest.getSession();
        String sessionId 
=  httpSession.getId();
        
// 如果是登出,则直接干掉sessionMap
         if  (uri.equals(logoutUrl)) {
            logger.debug(
" remove sessionmap: "   +  sessionId);
            
// 在线人数减1
            getCache().addOrDecr( " userCount " , 1 );
            getCache().remove(sessionId);
        } 
else  {
            String cookiesessionid 
=  getSessionIdFromCookie(hrequest, hresponse);
            
if  ( ! sessionId.equals(cookiesessionid)) {
                createCookie(sessionId, hresponse);
                SessionMap sessionMap 
=  getSessionMap(cookiesessionid);
                
if  (sessionMap  !=   null ) {
                    logger.debug(
" fail over--------sessionid: "   +  sessionId  +   " cookiesessionid: "   +  cookiesessionid);
                    initialHttpSession(sessionMap, httpSession);
                    cache.remove(cookiesessionid);
                }
            }
        }
        filterChain.doFilter(hrequest, hresponse);
}

利用HttpSessionAttributeListener监听httpsession的属性变化,同步到memecached中的sessionmap。
public   void  attributeAdded(HttpSessionBindingEvent event) {
        HttpSession httpSession 
=  event.getSession();
        String attrName 
=  event.getName();
        Object attrValue 
=  event.getValue();
        String sessionId 
=  httpSession.getId();
        logger.debug(
" attributeAdded sessionId: "   +  sessionId  +   " name: "   +  attrName  +   " ,value: "   +  attrValue);
        SessionMap sessionMap 
=  getSessionMap(sessionId);
        
if  (sessionMap  ==   null ){
            
// 在线人数加1
            getCache().addOrIncr( " userCount " , 1 );
            sessionMap 
=   new  SessionMap();
        }
        logger.debug(
" name: "   +  attrName  +   " ,value: "   +  attrValue);
        sessionMap.put(attrName, attrValue);
        getCache().put(sessionId, sessionMap);
    }

    
public   void  attributeRemoved(HttpSessionBindingEvent event) {
        HttpSession httpSession 
=  event.getSession();

        String attrName 
=  event.getName();
        String sessionId 
=  httpSession.getId();
        logger.debug(
" attributeRemoved sessionId: "   +  sessionId  +   " name: "   +  attrName);
        SessionMap sessionMap 
=  getSessionMap(sessionId);
        
if  (sessionMap  !=   null ) {
            logger.debug(
" remove: "   +  attrName);
            sessionMap.remove(attrName);
            getCache().put(sessionId, sessionMap);
        }
    }

    
public   void  attributeReplaced(HttpSessionBindingEvent event) {
        attributeAdded(event);
    }
   利用HttpSessionListener,sessionDestroyed事件时根据sessionid删除memcached里的sessionMap(如果存在)。不再担心httpsession的过期问题。 
 
public   void  sessionDestroyed(HttpSessionEvent event) {
        HttpSession httpSession 
=  event.getSession();
        String sessionId 
=  httpSession.getId();
        logger.debug(
" session Removed sessionId: "   +  sessionId);
        SessionMap sessionMap 
=  getSessionMap(sessionId);
        
if  (sessionMap  !=   null ) {
            logger.debug(
" remove sessionmap: "   +  sessionId);
            
// 在线人数减1
            getCache().addOrDecr( " userCount " , 1 );
            getCache().remove(sessionId);
        }
    }

  三、 文件保存的处理

和缓存类似,采用集中式的文件服务。对于linux,采用nfs。参考文档http://linux.vbird.org/linux_server/0330nfs.php#What_NFS_perm。关键在于对权限的分配。
应用程序本身不用修改。




http://www.blogjava.net/ronghao 荣浩原创,转载请注明出处:)

你可能感兴趣的:(基于memcached的SNA实现)