题外话:程序的架构依赖于系统的架构,系统运行的环境决定了程序设计的方式,单单以集群为例,在程序的开发上与集中化部署就有很大的不同,架构的意义就在 于发现这些不同,设计合理和容易扩展的结构以更小代价的适应未来的这种变化,分层的设计其实也是规避环境变化而造成系统整体的变动的一种方法。
Session应该是web开发独有的,必须要面对的。Session让web程序的会话保持变得如此的简单,但是也带来了很多问题,比如:有很多开发人 员享受了session的便利,把大部分东西往session中放,滥用session,造成服务器资源的浪费;跨域session丢失的问题等等。
今天就谈一下session在集群环境下的问题。
- 集群的各个节点无法共享同一个session。虽然现在可以通过Ngnix和apache将会话都路由到同一个应用服务器,但是随之而来带来下面一个问题。
- 在一个节点出现故障时,无法平滑的转移到另外一台应用服务器。虽然现在很多应用服务器都可以实现session复制,但是在集群节点非常多的时候,无疑对服务器的资源和效能都是一个极大的浪费。
为了解决这些问题,一些大型的互联网站点和应用系统,纷纷抛弃session机制,而采用cookie,cache,db等各种手段来替代 session,但是从复杂性和成本上来看,各种手段都需要重新考量。如果您的系统未来有可能成长为很庞大的规模,您不得不考虑这些问题。
OECP平台作为企业应用的业务平台,它是企业应用的核心,关联着组件和平台之间,组件和组件之间,用户和平台之间的交互,在具有一定规模的企业中,它将 承载着巨大的系统负荷,所以平台的开发之初就要设计相关的集群特征。HttpSession则是首要要考虑的。在设计的时候考虑了几种方案:
1、自己实现HttpSession以替换HttpServletRequest下的实现。这是一个比较好的方法,对调用者来说是隐藏的,但是难度比较大,同时requeset下的session依然可用,也不是很好的统一。
2、自己利用session和cache构建一个自己的session map,所有的开发者都需要调用这个session map,session和cache同步是需要必须做到的,否则session存在,cache中却失效了。而且多加了一个东西,依然不能完全替代 session,开发人员需要按照规定的套路走,不是很灵活。
3、依然采用HttpServletRequest的httpsession,但是利用cache做同步,将session同步到分布式cache中。当 一个节点故障需要转移到另外一个节点时,利用全局的cache,依然保证session不会丢失。而且同步cache的操作应该是可插拔的,以适应集中式 部署和分布式部署的环境。
我们采用了第3个方案,利用cache同步缓存,已达到session复制的目的。选用什么作为cache的载体呢?其实方案还是挺多的,可以采用 EHCache/JbossCache等java系的Cache组件,也可以采用流行的memdcache,当然也可以适用类似于MongoDB这样的 NoSQL存储系统。我们暂且采用EHCache来实现,未来不排除增加基于MongoDB的实现。下面就直接上代码了:
1、首先要实现HttpSessionListener,在session创建的时候,创建cache;在session销毁的时候,销毁cache。
java 代码
- @Override
- public void sessionCreated(HttpSessionEvent event) {
- HttpSession httpSession = event.getSession();
- String sessionId = httpSession.getId();
- SessionMap sessionMap = new SessionMap();
- sessionMap.put("creationTime", httpSession.getCreationTime());
- CacheManager.set(WebConstant.OECP_CACHE_SESSION, sessionId, sessionMap);
- }
-
- @Override
- public void sessionDestroyed(HttpSessionEvent event) {
- HttpSession httpSession = event.getSession();
- String sessionId = httpSession.getId();
- CacheManager.evict(WebConstant.OECP_CACHE_SESSION, sessionId);
- httpSession = null;
- }
-
2、其次要实现HttpSessionAttributeListener,用来监听session的变化,以保证将最新的session同步到cache中。
java 代码
- @Override
- public void attributeAdded(HttpSessionBindingEvent event) {
- HttpSession httpSession = event.getSession();
- String attrName = event.getName();
- Object attrValue = event.getValue();
- String sessionId = httpSession.getId();
- SessionMap sessionMap = (SessionMap) CacheManager.get(
- WebConstant.OECP_CACHE_SESSION, sessionId);
- if (sessionMap == null) {
- sessionMap = new SessionMap();
- }
- if (attrValue instanceof Serializable) {
- sessionMap.put(attrName, (Serializable) attrValue);
- }
- CacheManager.set(WebConstant.OECP_CACHE_SESSION, sessionId, sessionMap);
-
- }
-
- @Override
- public void attributeRemoved(HttpSessionBindingEvent event) {
- HttpSession httpSession = event.getSession();
-
- String attrName = event.getName();
- String sessionId = httpSession.getId();
- SessionMap sessionMap = (SessionMap) CacheManager.get(
- WebConstant.OECP_CACHE_SESSION, sessionId);
- if (sessionMap != null) {
- sessionMap.remove(attrName);
- CacheManager.set(WebConstant.OECP_CACHE_SESSION, sessionId,
- sessionMap);
- }
-
- }
-
- @Override
- public void attributeReplaced(HttpSessionBindingEvent event) {
- attributeAdded(event);
-
- }
-
3、最后增加一个SnaFilter,用来做故障时,session的转移(即cache到session的转化)。通过session和cache的同步比较,可以保持cache和session的时效一致性。
java 代码
- public class SnaFilter implements Filter {
-
- private Log logger = LogFactory.getLog(SnaFilter.class);
-
- private static boolean cluster = false;
-
- @Override
- public void destroy() {
-
- }
-
- @Override
- public void doFilter(ServletRequest req, ServletResponse res,
- FilterChain chain) throws IOException, ServletException {
- final HttpServletRequest hrequest = (HttpServletRequest) req;
- final HttpServletResponse hresponse = (HttpServletResponse) res;
- String uri = hrequest.getRequestURI();
- logger.debug("开始SNA拦截-----------------" + uri);
- HttpSession httpSession = hrequest.getSession();
- String sessionId = httpSession.getId();
- long sessionTime = httpSession.getCreationTime();
- SessionMap sessionMap = (SessionMap) CacheManager.get(
- WebConstant.OECP_CACHE_SESSION, sessionId);
- if (sessionMap != null
- && (Long.valueOf(sessionMap.get("creationTime").toString())) != sessionTime) {
-
- initHttpSession(httpSession, sessionMap);
- }
- chain.doFilter(hrequest, hresponse);
-
- }
-
- private void initHttpSession(HttpSession session,
- SessionMap sessionMap) {
- Set<String> keySet = sessionMap.keySet();
- Iterator<String> it = keySet.iterator();
- while (it.hasNext()) {
- String key = it.next();
- session.setAttribute(key, sessionMap.get(key));
- }
- sessionMap.put("creationTime", session.getCreationTime());
- CacheManager.set(WebConstant.OECP_CACHE_SESSION, session.getId(),
- sessionMap);
- }
-
- @Override
- public void init(FilterConfig chain) throws ServletException {
- cluster = true;
- }
-
- public static boolean isCluster() {
- return cluster;
- }
-
- }
-
以上三个步骤已经基本实现了集群化的session处理,但是还需要其他配置相配合。第一就是ehcache的集群化配置,这个有两种方式,一个是广播 式,将cache同步到各个节点;一个是分布集中式,将缓存都同步到一个节点上。同时以上实现都是基于插件思想的,在需要集群的时候只要配置到 web.xml中,即可。不用的时候可以卸载,一避免浪费资源。这两部分的配置我就省略了,有兴趣的朋友可以查一下。
关于OECP平台的最新进展请继续关注:http://prj.oecp.cn/projects/oecp-platform
原文:http://www.oecp.cn/hi/yongtree/blog/2461 (含附件)