论坛首页 Java企业应用论坛

基于memcached的SNA实现

浏览 8796 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-10-28  

系统要集群,使用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。关键在于对权限的分配。
应用程序本身不用修改。

 

   发表时间:2008-10-29  
好文章,讲的很透彻
0 请登录后投票
   发表时间:2009-10-06  
不错啊,不过可能还有问题:
比如原先的Web服务器宕机了,那Memcached 中的相关信息就无法被删除了
0 请登录后投票
   发表时间:2009-10-06  
还有个问题啊,比如有两台WebServer,客户端先接入第一个Server,留下了一个Session,由于种种原因,在第一个Server还正常的情况下接入了第二个Server,过一段时间,第一个Server由于检测到该客户端Session过期,而删除了Memcached中的数据。
0 请登录后投票
   发表时间:2009-10-10  
lvjinhua 写道
不错啊,不过可能还有问题:
比如原先的Web服务器宕机了,那Memcached 中的相关信息就无法被删除了

Memcache采用先进先出策略,所以不用担心。
0 请登录后投票
   发表时间:2009-10-10  
lvjinhua 写道
还有个问题啊,比如有两台WebServer,客户端先接入第一个Server,留下了一个Session,由于种种原因,在第一个Server还正常的情况下接入了第二个Server,过一段时间,第一个Server由于检测到该客户端Session过期,而删除了Memcached中的数据。

接入第二个server 就会产生一个新的session吧,哈哈,首先session默认是不可序列化的。那么请看步骤四:
4、 如果Cookie保存的HttpSession id与当前HttpSession id不一致,说明是失败转发;失败转发的处理:
     4.1、根据Cookie保存的HttpSession id从memcached获取SessionMap;
     4.2、SessionMap属性复制到当前HttpSession;
     4.3、memcached删除SessionMap。
0 请登录后投票
   发表时间:2009-10-10  
建议再配置一个本地缓存,从而提高效率
0 请登录后投票
   发表时间:2009-10-10  
zhangkaitao 写道
lvjinhua 写道
不错啊,不过可能还有问题:
比如原先的Web服务器宕机了,那Memcached 中的相关信息就无法被删除了

Memcache采用先进先出策略,所以不用担心。


Memcached不是FIFO,而是LRU吧。
0 请登录后投票
   发表时间:2009-10-12  
dennis_zane 写道
zhangkaitao 写道
lvjinhua 写道
不错啊,不过可能还有问题:
比如原先的Web服务器宕机了,那Memcached 中的相关信息就无法被删除了

Memcache采用先进先出策略,所以不用担心。


Memcached不是FIFO,而是LRU吧。

是,呵呵。
1 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics