系统要集群,使用SNA方案。
一、 缓存的处理
缓存要使用统一的缓存服务器,集中式缓存。
原先的实现采用ehcache。
在spring里的配置,以资源缓存为例:
-
- <beanid="cacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
- <propertyname="configLocation">
- <value>classpath:ehcache.xml</value>
- </property>
- </bean>
- <beanid="resourceCacheBackend"
- class="org.springframework.cache.ehcache.EhCacheFactoryBean">
- <propertyname="cacheManager"ref="cacheManager"/>
- <propertyname="cacheName"value="resourceCache"/>
- </bean>
- <beanid="resourceCache"
- class="com.framework.extcomponent.security.authentication.services.acegi.cache.EhCacheBasedResourceCache"
- autowire="byName">
- <propertyname="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进行管理,初始化、启动、停止。
代码:
-
- publicclassMemcachedCacheManagerFactoryBeanimplementsFactoryBean,InitializingBean,DisposableBean{
- protectedfinalLoglogger=LogFactory.getLog(getClass());
- privateICacheManager<IMemcachedCache>cacheManager;
- publicObjectgetObject()throwsException{
- returncacheManager;
- }
- publicClassgetObjectType(){
- returnthis.cacheManager.getClass();
- }
- publicbooleanisSingleton(){
- returntrue;
- }
- publicvoidafterPropertiesSet()throwsException{
- logger.info("InitializingMemcachedCacheManager");
- cacheManager=CacheUtil.getCacheManager(IMemcachedCache.class,
- MemcachedCacheManager.class.getName());
- cacheManager.start();
- }
- publicvoiddestroy()throwsException{
- logger.info("ShuttingdownMemcachedCacheManager");
- cacheManager.stop();
- }
- }
配置:
- <beanid="memcachedManager"
- class="com.framework.extcomponent.cache.MemcachedCacheManagerFactoryBean"/>
resourceCacheBackend负责实际执行缓存操作,put 、get、remove。
代码:
-
- publicclassMemcachedCacheFactoryBeanimplementsFactoryBean,BeanNameAware,InitializingBean{
- protectedfinalLoglogger=LogFactory.getLog(getClass());
- privateICacheManager<IMemcachedCache>cacheManager;
- privateStringcacheName;
- privateStringbeanName;
- privateIMemcachedCachecache;
- publicvoidsetCacheManager(ICacheManager<IMemcachedCache>cacheManager){
- this.cacheManager=cacheManager;
- }
- publicvoidsetCacheName(StringcacheName){
- this.cacheName=cacheName;
- }
- publicObjectgetObject()throwsException{
- returncache;
- }
- publicClassgetObjectType(){
- returnthis.cache.getClass();
- }
- publicbooleanisSingleton(){
- returntrue;
- }
- publicvoidsetBeanName(Stringname){
- this.beanName=name;
- }
- publicvoidafterPropertiesSet()throwsException{
-
- if(this.cacheName==null){
- this.cacheName=this.beanName;
- }
- cache=cacheManager.getCache(cacheName);
- }
- }
配置:
- <beanid="resourceCacheBackend"
- class="com.framework.extcomponent.cache.MemcachedCacheFactoryBean">
- <propertyname="cacheManager"ref="memcachedManager"/>
- <propertyname="cacheName"value="memcache"/>
- </bean>
resourceCache同上,替换新的实现类MemcachedBasedResourceCache即可。
二、 Session失效的处理
采用memcached作为httpsession的存储,并不直接保存httpsession对象,自定义SessionMap,SessionMap直接继承HashMap,保存SessionMap。
会话胶粘:未失败转发的情况下没必要在memcached保存的SessionMap和httpsession之间复制来复制去,眉来眼去。
利用memcached计数器保存在线人数。
系统权限采用了acegi,在acegi的拦截器链里配置snaFilter
- <beanid="filterChainProxy"
- class="org.acegisecurity.util.FilterChainProxy">
- <propertyname="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.
代码:
- publicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,
- FilterChainfilterChain)throwsIOException,ServletException{
- finalHttpServletRequesthrequest=(HttpServletRequest)servletRequest;
- finalHttpServletResponsehresponse=(HttpServletResponse)servletResponse;
- Stringuri=hrequest.getRequestURI();
- logger.debug("开始SNA拦截-----------------"+uri);
- HttpSessionhttpSession=hrequest.getSession();
- StringsessionId=httpSession.getId();
-
- if(uri.equals(logoutUrl)){
- logger.debug("removesessionmap:"+sessionId);
-
- getCache().addOrDecr("userCount",1);
- getCache().remove(sessionId);
- }else{
- Stringcookiesessionid=getSessionIdFromCookie(hrequest,hresponse);
- if(!sessionId.equals(cookiesessionid)){
- createCookie(sessionId,hresponse);
- SessionMapsessionMap=getSessionMap(cookiesessionid);
- if(sessionMap!=null){
- logger.debug("failover--------sessionid:"+sessionId+"cookiesessionid:"+cookiesessionid);
- initialHttpSession(sessionMap,httpSession);
- cache.remove(cookiesessionid);
- }
- }
- }
- filterChain.doFilter(hrequest,hresponse);
- }
利用HttpSessionAttributeListener监听httpsession的属性变化,同步到memecached中的sessionmap。
- publicvoidattributeAdded(HttpSessionBindingEventevent){
- HttpSessionhttpSession=event.getSession();
- StringattrName=event.getName();
- ObjectattrValue=event.getValue();
- StringsessionId=httpSession.getId();
- logger.debug("attributeAddedsessionId:"+sessionId+"name:"+attrName+",value:"+attrValue);
- SessionMapsessionMap=getSessionMap(sessionId);
- if(sessionMap==null){
-
- getCache().addOrIncr("userCount",1);
- sessionMap=newSessionMap();
- }
- logger.debug("name:"+attrName+",value:"+attrValue);
- sessionMap.put(attrName,attrValue);
- getCache().put(sessionId,sessionMap);
- }
- publicvoidattributeRemoved(HttpSessionBindingEventevent){
- HttpSessionhttpSession=event.getSession();
- StringattrName=event.getName();
- StringsessionId=httpSession.getId();
- logger.debug("attributeRemovedsessionId:"+sessionId+"name:"+attrName);
- SessionMapsessionMap=getSessionMap(sessionId);
- if(sessionMap!=null){
- logger.debug("remove:"+attrName);
- sessionMap.remove(attrName);
- getCache().put(sessionId,sessionMap);
- }
- }
- publicvoidattributeReplaced(HttpSessionBindingEventevent){
- attributeAdded(event);
- }
利用HttpSessionListener,sessionDestroyed事件时根据sessionid删除memcached里的sessionMap(如果存在)。不再担心httpsession的过期问题。
- publicvoidsessionDestroyed(HttpSessionEventevent){
- HttpSessionhttpSession=event.getSession();
- StringsessionId=httpSession.getId();
- logger.debug("sessionRemovedsessionId:"+sessionId);
- SessionMapsessionMap=getSessionMap(sessionId);
- if(sessionMap!=null){
- logger.debug("removesessionmap:"+sessionId);
-
- getCache().addOrDecr("userCount",1);
- getCache().remove(sessionId);
- }
- }
三、 文件保存的处理
和缓存类似,采用集中式的文件服务。对于linux,采用nfs。参考文档http://linux.vbird.org/linux_server/0330nfs.php#What_NFS_perm。关键在于对权限的分配。
应用程序本身不用修改。