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