首页 / 软件开发 / JAVA / Java理论与实践: Web层的状态复制
Java理论与实践: Web层的状态复制2010-12-21 IBM Brian Goetz不管正在构建的是 J2EE 还是 J2SE 服务器应用程序,都有可能以某种方式 使用 Java Servlet —— 可能是直接地通过像 JSP 技术、Velocity 或者 WebMacro 这样的表示层,也可能通过一个基于 servlet 的 Web 服务实现,如 Axis 或者 Glue。Servlet API 提供的一个最重要的功能是会话管理 —— 通过 HttpSession 接口进行用户状态的认证、失效和维护。会话状态几乎每一个 Web 应用程序都有一些会话状态,这些状态有可能像记住您是否 已登录这么简单,也可能是您的会话的更详细的历史,如购物车的内容、以前查 询结果的缓存或者 20 页动态问卷表的完整响应历史。因为 HTTP 协议本身是无 状态的,所以需要将会话状态存储在某处并与浏览会话以某种方式相关联,使得 下次请求同一 Web 应用程序的页面时可以容易地获取。幸运的是,J2EE 提供了 几种管理会话状态的方法 —— 状态可以存储在数据层,用 Servlet API 的 HttpSession 接口存储在 Web 层,用有状态会话 bean 存储在 Enterprise JavaBeans(EJB)层,甚至用 cookie 或者隐藏表单字段将状态存储在客户层。 不幸的是,会话状态管理不当会带来严重的性能问题。如果应用程序能够在 HttpSession 中存储用户状态,这种方法通常比其他方 法更好。在客户端用 HTTP cookie 或者隐藏表单字段存储会话状态有很大的安 全风险 —— 它将应用程序的一部分内部内容暴露给了非受信任的客户层。(一 个早期的电子商务网站将购物车内容(包括价格)存储在隐藏表单字段中,从而 可以很容易被非法利用,让任何了解 HTML 和 HTTP 的用户可以以 0.01 美元购 买任何商品。噢)此外,使用 cookie 或者隐藏表单字段很混乱,容易出错,并 且脆弱(如果用户禁止在浏览器中使用 cookie,那么基于 cookie 的方法就完 全不能工作)。在 J2EE 应用程序中存储服务器端状态的其他方法是使用有状态会话 bean, 或者在数据库中存储会话状态。虽然有状态会话 bean 在会话状态管理方面有更 大的灵活性,但是在可能的情况下,将会话状态存储在 Web 层仍然有好处。如 果业务对象是无状态的,那么通常可以仅仅添加更多 Web 服务器来扩展应用程 序,而不用添加更多 Web 服务器和更多 EJB 容器, 这样的成本一般要低一些 并且容易完成。使用 HttpSession 存储会话状态的另一个好处是 Servlet API 提供了一种会话失效时通知的容易方法。在数据库中存储会话状态的成本可能难 以承受。servlet 规范没有要求 servlet 容器进行某种类型的会话复制或者持久性, 但是它建议将状态复制作为 servlet 首要 存在理由(raison d"etre) 的重要 部分,并且它对作为进行会话复制的容器提出了一些要求。会话复制可以提供大 量好处 —— 负载平衡、伸缩性、容错和高可用性。相应地,大多数 servlet 容器支持某种形式的 HttpSession 复制,但是复制的机制、配置和时间是由实 现决定的。HttpSession API简单地说, HttpSession 接口支持几种方法,servlet、JSP 页或者其他表 示层组件可以用这些方法来跨多个 HTTP 请求维护会话信息。会话绑定到特定的 用户,但是在 Web 应用程序的所有 servlet 中共享 —— 不特定于某一个 servlet。一种考虑会话的有用方法是,会话像一个在会话期间存储对象的 Map —— 可以用 setAttribute 按名字存储会话属性,并用 getAttribute 提取它 们。 HttpSession 接口还包含会话生存周期方法,如 invalidate() (它通知 容器应丢弃会话)。清单 1 显示 HttpSession 接口最常用的元素:清单 1. HttpSession APIpublic interface HttpSession {
Object getAttribute(String s);
Enumeration getAttributeNames();
void setAttribute(String s, Object o);
void removeAttribute(String s);
boolean isNew();
void invalidate();
void setMaxInactiveInterval(int i);
int getMaxInactiveInterval();
...
}理论上,可以跨群集一致性地完全复制会话状态,这样群集中的所有节点都 可以服务任何请求,一个简单的负载平衡器可以以轮询方式传送请求,避开有故 障的主机。不过,这种紧密的复制有很高的性能成本,并且难于实现,当群集接 近某一规模时,还会有伸缩性的问题。一种更常用的方式是将负载平衡与会话相似性(affinity) 结合起来 —— 负载平衡器可以将会话与连接相关联,并将会话中以后的请求发送给同一服务器 。有很多硬件和软件负载平衡器支持这个功能,并且这意味着只有主连接主机和 会话需要故障转移到另一台服务器时才访问复制的会话信息。复制方式复制提供了一些可能的好处,包括可用性、容错和伸缩性。此外,有大量会 话复制的方法可用:方法的选择取决于应用程序群集的规模、复制的目标和 servlet 容器支持的复制设施。复制有性能成本,包括 CPU 周期(存储在会话 中的序列化对象)、网络带宽(广播更新),以及基于磁盘的方案中写入到磁盘 或者数据库的成本。几乎所有 servlet 容器都通过存储在 HttpSession 中的序列化对象进行 HttpSession 复制,所以如果是创建一个分布式应用程序,应当确保只将可序列 化对象放到会话中。(一些容器对像 EJB 引用、事务上下文、还有其他非可序 列化的 J2EE 对象类型有特殊的处理。)