在构建能够灵活地进行水平扩展、高可用性的Java Web应用程序时候,对http session的处理策略很大程度决定了应用程序的扩展性、可用性。一般而言对http session有如下的处理方案:?
1、在服务器端不保存Session,完全无状态?
???? 对于不需要保持用户状态的Web应用,采用Stateless是最为恰当的,因此就不存在Session共享的问题。REST (Representational State Transfer) 算是最为典型的例子。?
2、基于浏览器Cookie的Session共享?
????? 此种方案把用户相关的Session信息存储到浏览器的Cookie中,也称为客户端Session。?
????? 采用Flash Cookie、URL重写的方式传递Session信息的方案也可以归为此类。?
????? 缺点:只能够存储字符串、数值等基本类型的数据;Cookie大小存在限制;安全性;带宽及数据解压缩、网络传输性能问题。?
3、基于数据库的Session共享,实现分布式应用间Session共享?
???? 此种方案把Session信息存储到数据库表,这样实现不同应用服务器间Session信息的共享。诸如Websphere Portal、Weblogic Portal都采用了类似的方案。?
???? Tomcat Persistent Manager 的JDBC Based Store 提供了类似实现机制,表结构如下:?
??????? create table tomcat_sessions (?
??????????? session_id???? varchar(100) not null primary key,?
??????????? valid_session? char(1) not null,?
??????????? max_inactive?? int not null,?
??????????? last_access??? bigint not null,?
??????????? app_name?????? varchar(255),?
??????????? session_data?? mediumblob,?
??????????? KEY kapp_name(app_name)?
????????? );?
??????? 优点:实现简单?
??????? 缺点:由于数据库服务器相对于应用服务器更难扩展且资源更为宝贵,在高并发的Web应用中,最大的性能瓶颈通常在于数据库服务器。因此如果将Session存储到数据库表,频繁的增加、删除、查询操作很容易造成数据库表争用及加锁,最终影响业务。?
4、基于应用服务器/Servlet容器的Clustering机制?
??????? 一些常用的应用服务器及Servlet容器的Clustering机制可以实现Session Replication的功能,例如Tomcat Clustering/Session Replication、Jboss buddy replication。?
???????? 缺点:基于Clustering的Session复制性能很差,扩展性也很不行。?
5、基于NFS的Session共享?
???????? 通过NFS方式来实现各台服务器间的Session共享,各台服务器只需要mount共享服务器的存储Session的磁盘即可,实现较为简单。但NFS对高并发读写的性能并不高,在硬盘I/O性能和网络带宽上存在较大瓶颈,尤其是对于Session这样的小文件的频繁读写操作。?
??????? 基于磁盘阵列/SAN/NAS等共享存储的方案道理也类似。?
6、基于Terracotta、Ehcache、JBossCache等Java Caching方案实现Session共享?
??? 如果系统架构是Java体系,可以考虑采用Terracotta、Ehcache、JbossCache、Oscache等Java Caching方案来实现Session 共享。?
??? 缺点:架构用于非java体系很不方便;对于是诸如静态页面之类的缓存,采用Memcached的方案比Java更为高效?
7、基于Memcached/Tokyo Tyrant 等Key-Value DB的Session共享?
??? 整体说来此种方案扩展性最好,推荐使用。?
??? 原理:Tomcat 服务器提供了org.apache.catalina.session.StandardManager 和org.apache.catalina.session.PersistentManager用于Session对象的管理,可以自定义PersistentManager的?
Store 类来实现自己Memcached、Tokyo Tyrant、Redis等Key-Value DB的客户端。?
??? 以Memcached的客户端为例(摘自Use MemCacheStore in Tomcat):?
Java代码?
?
class="star">
- package?com.yeeach;??
- import?com.danga.MemCached.MemCachedClient;???
- import?com.danga.MemCached.SockIOPool;??
- ??
- public?class?MemCacheStore?extends?StoreBase?implements?Store?{??
- ??
- ?????
- ?
- ??
- ????protected?static?String?info?=?"MemCacheStore/1.0";??
- ??
- ?????
- ?
- ??
- ????private?static?final?ThreadLocal<MemCachedClient>?memclient?=?new?ThreadLocal<MemCachedClient>();??
- ??
- ?????
- ?
- ??
- ????private?List<String>?servers?=?new?ArrayList<String>();??
- ??
- ?????
- ?
- ??
- ????private?List<String>?keys?=?Collections??
- ????????????.synchronizedList(new?ArrayList<String>());??
- ??
- ?????
- ?
- ??
- ????public?String?getInfo()?{??
- ????????return?(info);??
- ????}??
- ??
- ?????
- ?
- ??
- ????public?void?clear()?throws?IOException?{??
- ????????getMemcacheClient().flushAll();??
- ????????keys.clear();??
- ????}??
- ??
- ?????
- ?
- ??
- ????public?int?getSize()?throws?IOException?{??
- ????????return?getKeyList().size();??
- ????}??
- ??
- ?????
- ?
- ??
- ????public?String[]?keys()?throws?IOException?{??
- ????????return?getKeyList().toArray(new?String[]?{});??
- ????}??
- ??
- ?????
- ?
- ?
- ??
- ????public?Session?load(String?sessionId)?throws?ClassNotFoundException,??
- ????????????IOException?{??
- ??
- ????????try?{??
- ??
- ????????????byte[]?bytes?=?(byte[])?getMemcacheClient().get(sessionId);??
- ????????????if?(bytes?==?null)??
- ????????????????return?null;??
- ????????????ObjectInputStream?ois?=?bytesToObjectStream(bytes);??
- ??
- ????????????StandardSession?session?=?(StandardSession)?manager??
- ????????????????????.createEmptySession();??
- ????????????session.setManager(manager);??
- ????????????session.readObjectData(ois);??
- ????????????if?(session.isValid()?&&?!keys.contains(sessionId))?{??
- ????????????????keys.add(sessionId);??
- ????????????}??
- ????????????return?session;??
- ??
- ????????}?catch?(Exception?e)?{??
- ????????????return?(null);??
- ????????}??
- ????}??
- ??
- ?????
- ?
- ?
- ?
- ?
- ?
- ?
- ??
- ????private?ObjectInputStream?bytesToObjectStream(byte[]?bytes)??
- ????????????throws?IOException?{??
- ????????ByteArrayInputStream?bais?=?new?ByteArrayInputStream(bytes);??
- ????????ObjectInputStream?ois?=?null;??
- ????????Loader?loader?=?null;??
- ????????ClassLoader?classLoader?=?null;??
- ????????Container?container?=?manager.getContainer();??
- ????????if?(container?!=?null)??
- ????????????loader?=?container.getLoader();??
- ????????if?(loader?!=?null)??
- ????????????classLoader?=?loader.getClassLoader();??
- ????????if?(classLoader?!=?null)??
- ????????????ois?=?new?CustomObjectInputStream(bais,?classLoader);??
- ????????else??
- ????????????ois?=?new?ObjectInputStream(bais);??
- ????????return?ois;??
- ????}??
- ??
- ?????
- ?
- ??
- ????public?void?remove(String?sessionId)?throws?IOException?{??
- ????????getMemcacheClient().delete(sessionId);??
- ????????List<String>?keyList?=?getKeyList();??
- ????????keyList.remove(sessionId);??
- ????}??
- ??
- ?????
- ?
- ??
- ????public?void?save(Session?session)?throws?IOException?{??
- ????????ByteArrayOutputStream?baos?=?new?ByteArrayOutputStream();??
- ????????ObjectOutputStream?oos?=?new?ObjectOutputStream(baos);??
- ????????StandardSession?standard?=?(StandardSession)?session;??
- ????????standard.writeObjectData(oos);??
- ????????getMemcacheClient().add(session.getId(),?baos.toByteArray());??
- ????????Object?ob?=?getMemcacheClient().get(session.getId());??
- ????????List<String>?keyList?=?getKeyList();??
- ????????keyList.add(session.getId());??
- ????}??
- ??
- ?????
- ?
- ?
- ??
- ????private?List<String>?getKeyList()?{??
- ????????return?keys;??
- ????}??
- ??
- ?????
- ?
- ?
- ??
- ????private?MemCachedClient?getMemcacheClient()?{??
- ????????if?(memclient?==?null)?{??
- ??
- ????????????Integer[]?weights?=?{?1?};??
- ??????????????
- ????????????SockIOPool?pool?=?SockIOPool.getInstance();??
- ????????????if?(!pool.isInitialized())?{??
- ????????????????String[]?serverlist?=?servers.toArray(new?String[]?{});??
- ??????????????????
- ????????????????pool.setServers(serverlist);??
- ????????????????pool.setWeights(weights);??
- ??
- ??????????????????
- ??????????????????
- ??????????????????
- ??????????????????
- ????????????????pool.setInitConn(5);??
- ????????????????pool.setMinConn(5);??
- ????????????????pool.setMaxConn(250);??
- ????????????????pool.setMaxIdle(1000?*?60?*?60?*?6);??
- ??
- ??????????????????
- ??????????????????
- ??????????????????
- ????????????????pool.setMaintSleep(30);??
- ??
- ??????????????????
- ??????????????????
- ??????????????????
- ??????????????????
- ????????????????pool.setNagle(false);??
- ????????????????pool.setSocketTO(3000);??
- ????????????????pool.setSocketConnectTO(0);??
- ??
- ??????????????????
- ????????????????pool.initialize();??
- ????????????}??
- ??
- ??????????????
- ??????????????
- ??
- ????????????memclient.get().setCompressEnable(true);??
- ????????????memclient.get().setCompressThreshold(64?*?1024);??
- ????????}??
- ????????return?memclient.get();??
- ????}??
- ??
- ????public?List<String>?getServers()?{??
- ????????return?servers;??
- ????}??
- ??
- ????public?void?setServers(String?serverList)?{??
- ????????StringTokenizer?st?=?new?StringTokenizer(serverList,?",?");??
- ????????servers.clear();??
- ????????while?(st.hasMoreTokens())?{??
- ????????????servers.add(st.nextToken());??
- ????????}??
- ????}??
- ??
- }??
Tomcat 的配置文件:?
Xml代码?
?
- <Context?path="/test"?docBase="test.war">??
- ????<Manager?className="org.apache.catalina.session.PersistentManager"??
- ????????distributable="true">??
- ????????<Store?className="com.yeeach.MemcachedStore"??
- ????????????servers="192.168.0.111:11211,192.168.0.112:11211"?/>??
- ????</Manager>??
- </Context>??
??? 这里只是作为测试演示了在Tomcat中集成Memcached的实现方案。并没有考虑性能、高可用、Memcached 存储Session的持久化(可以使用Memcachedb实现)、Session管理等问题。?
??? 针对Tomcat与Memcached集成有一个开源项目memcached-session-manager? 功能实现相对完善,尤其是其设计思想值得借鉴。?
??? The memcached session manager installed in a tomcat holds all sessions locally in the own jvm, just like the StandardManager does it as well.?
??? Additionally, after a request was finished, the session (only if existing) is additionally sent to a memcached node for backup.?
??? When the next request for this session has to be served, the session is locally available and can be used, after this second request is finished the session is updated in the memcached node.?
??? 对于采用Tokyo Tyrant、Redis等当下流行的Key-Value DB实现机制类似。 ??