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