最新版的 Tomcat servlet 容器提供集群和负载均衡的能力,对于部署可升级、健壮的Web应用来说,这是必不可少的。这篇文章的第一部分描述集群与负载均衡的特性和要点。第二部分举出一个如何配置 Tomcat 集群的实例,同时介绍在集群环境中使用内存复制的方式来实现 session 的持久化 。
Tomcat 5 自带一个基于规则的负载均衡应用。根据两种负载均衡策略(扩展自规则API,分别是 round-robin 和 random 算法)来重定向进入的请求。讲述运行在集群环境中的样例Web应用的性能评测。通过负载测试工具 JMeter 模拟多个Web用户的方式来研究负载均衡机制。
+ 大型系统的设计
企业级的 Web 门户应用必须提供可升级能力(scalability)和高可用性(HA),在同一个网站下为数以百万计的用户提供服务。可升级是系统可以通过增加服务器来支持更多的数量的用户。高可靠性是系统提供基本的冗余能力。在集群中的某个成员失效时,其他成员能透明的接替处理对 Web 服务的请求。在集群的环境中部署一个 Web 门户应用能提供门户网站需要的可升级和高可用性的能力。基本上,集群的主要目的是防止某些站点出现当机的问题,应付系统的单点失效。
大型系统的设计目的是在企业应用环境中提供稳定的服务,确保最少的当机时间和最大的可升级能力。运行的不是单一的服务器,有多个协作服务器也同时在运行。为了达到可升级的能力,集群能任意增加机器数量,而为了最少的当机时间,集群中每一个组件都是可冗余的。大型系统的主要因素就是集群,包含负载均衡、容错、session 状态持久化等特性。在集群中,通常负载均衡器(硬件或软件)布置在应用服务器的前面。这些负载均衡器通过使用重定向 Web 交易到相应的集群成员来在集群的节点内分发负载,并且在同时检查集群内是否有服务器失效。
+ 集群
集群的定义好比一组应用服务器透明的运行J2EE应用,就象在一台机器上执行一样。有两种方式的集群:垂直缩放和水平缩放。垂直缩放可以通过提高单台机器上的运行服务的数量来达到,水平缩放就要提高集群内机器的数量。水平缩放比垂直缩放更可靠。使用垂直缩放,机器的处理能力、CPU的使用情况和JVM堆内存配置都是决定应该在这台机器上运行多少的服务器实例的主要因素(众所周知的 server-to-cpu 比率)。
J2EE集群内的服务器一般使用三种配置选项的一种:
独立(independent),每个服务都有属于自己的一份应用程序文件的拷贝;
共享文件系统(shared file system),集群内所有的服务器拥有一个共同的存储设备,所有服务器的应用文件都是从该设备取得;
第三方的配置方法(managed),有一台管理服务器控制对应用内容的访问,通过”pushing“相应的应用内容到管理服务器作为响应。管理服务器能保证集群中所有的成员的应用有效。当部署应用时,所有的服务器都会更新,而反部署时,所有的服务器都会将应用删除。
集群可以在J2EE应用的各个层次使用,包括数据层。一些数据库提供商提供集群数据库,支持多个数据库服务器中的数据复制、客户透明访问( servlet 容器或者应用服务器无须知道从那个数据库服务器中取的数据)。JDBC集群的例子是 Oracle9i’s Real Application Clusters(RAC) 和集群 JDBC(C-JDBC)。RAC 支持数据库连接的失败重启、透明变更JDBC连接、请求到一台恢复后的数据库节点。C-JDBC是一个开放源的数据库集群,允许 Web 应用通过 JDBC 透明的访问数据库集群。实现数据库节点内的负载均衡和失败重启。
++ Tomcat 的集群
在 Tomcat 先前的版本中(4.1)可以通过第三方的jar文件来实现集群。在一个集群内安装和配置多个 Tomcat 实例不是一件容易的事情。将集群的能力增加到开放源的 servlet 容器(Tomcat)和应用服务(JBoss)中,JavaGroups 是一个不错的选择。在最新版本的 Tomcat,集群已经成为主要安装包的一部分。将第三方的集群实现对 Tomcat 服务器的影响减少到最低。
在典型的集群环境中,为了让成员之间相互协作和复制状态,它们之间需要互相通信。
组的通信可以使用 point-to-point RMI(TCP-IP)或者IP多播两种方式。
大部分的J2EE应用服务器(如JBoss、Oracle、WebLogic和Borland) 都是使用IP多播让集群成员进行通信,在集群内发送 state/update/heartbeat 数据给其他成员。
Tomcat 集群成员的通信是如下进行的:所有的集群成员用多播 ping 消息来对话。每一个 Tomcat 实例将发送一个消息,广播其 IP 地址和 TCP 监听端口(为session replication)。如果在给定的时间帧内,某个实例没有接收到这些信息,那么该实例就会被认为是当机。
另一个比较流行的概念是 farming,提供集群范围内的 Web 应用的热部署。在服务器 farm内,一个 Web 应用通过拷贝 war 文件到集群内的一个节点上进行部署,farming将会把这个 Web 应用部署到整个集群中。类似的,从一个集群节点上删除 war 文件,farming 将会在集群内所有的节点上反部署这个 Web 应用。Tomcat的集群文档中讲到在接下来的版本中将会支持 farming 能力。
+ 负载均衡
一种机制,能将服务器的负载被分发到集群的不同节点上。基于负载均衡策略,应用不再在单一的服务器上执行,而是在动态选择的服务器上。当客户请求服务,一个或多个协作服务器处理这个请求。负载均衡为集群提供单一的入口,就如直接和独立的 Web 或者应用服务器交互一样,这对客户来说是透明的。
两种比较流行的负载均衡方法分别是 DNS 轮循 和 硬件负载均衡。 DNS 轮循提供单个逻辑名称,返回集群内某台机器的ip地址。这种方式是廉价、简单并且容易配置,但它并没有提供服务器之间的联系和高可靠性的能力。相对来说,硬件负载均衡通过虚拟 ip 地址来解决 DNS 轮循存在的问题。负载均衡器有一个单独的ip地址,映射到集群内的每一个节点上。负载均衡器接收到请求,然后重写头部来指向集群内的其他机器。如果我们从集群内移出一些机器,那么这个改变马上生效。硬件负载均衡的好处是服务器的联系性和高效。缺点是昂贵并且设置复杂。(作者在这里没有提到这种硬件的负载均衡方式可以用软件实现,这种方式称为 Vitrual Server )。
对于负载的分发有多种算法,下面是一些较为常用的算法:
* round-robin 轮循
* random 随机
* weight-based 权重
* minimum load 最小负载
* last access time 最后访问时间
* programmatic parameter-based 负载均衡器根据方法中的参数来选择服务器
负载均衡算法涉及统计上的差异,速度和简单性。举个例子,weight-based算法比其他的算法需要更长的计算时间。想得到对负载均衡的更详细的解释,参考ONJava的文章 “Load Balancing Web Applications”
++ Tomcat的负载均衡
先前版本的tomcat并没有提供负载均衡的能力。集成 apache web server 和 tomcat servlet container 就是一个不错的处理 web 请求的负载均衡集群。在Apache+Tomcat 中,被称为 Tomcat Worker 的 Tomcat 实例被配置来实现负载均衡。
Tomcat 5 提供三种方法来实现负载均衡:
分别是用JK本地连接器,用 Apache2 的 mod_proxy 和 mod_rewrite,或者用 balancer web app。
在这篇文章中,我们重点使用第三种,使用 balancer web application 来重定向web请求到集群内的各个节点。这个负载均衡的应用是基于规则的。使用servlet filter机制重定向进入的web请求到下一个有效的集群成员上。servlet filter在servlet 2.3 规范中有详细的介绍。过滤器(servlet filter)可以在 web 应用中负责多种不同的任务。例如JAAS认证,加密,记录日志和审核,数据压缩,XSLT 过滤器转换 XML 内容等等。就如 Tomcat 均衡器网站讲述的那样,这个均衡器应用并非设计用来替代其他的强大的负载均衡机制。它用简单并且易于扩展的方法来重定向交易(traffic)到其他的服务器上。检查均衡器应用所提供的样例Java类,了解均衡器如何用不同的规则标准来完成各种不同的任务。
负载均衡配置文件(rules.xml)包含不同的规则和重定向的URLs。balancer filter 检查RuleChain来决定将请求重定向到那里,按照rules.xml中指定的顺序来检查规则。当一条规则匹配时,过滤器停止评估,并且重定向请求到规则指定的URL上。
+ 容错
容错是系统的一种能力,能够做到系统中的一个服务器失效时,另一个有效的服务器能够接管,这对最终用户来讲是透明的。理想的情况是集群服务监测到集群内其中的一个服务器失效而不能处理请求时,停止发送请求到该服务器。然后周期性的检查集群中的该成员是否再次生效,如果生效,将再次将其添加到活动服务器节点池中。
++ Tomcat 的容错
Tomcat 5 并没有提供一个内建的失败重启机制来检查集群成员的崩溃。希望,未来的版本能提供这个功能,用来发现集群内有效的机器,确定那些成员能处理进来的请求。
集群解决方案一般提供两种层次的失败重启能力:
* 请求层次的失败重启
如果集群中的一台服务器挂起,所有接下来的请求将会被重定向到集群中的其他服务器。这包含一种 heartbeat 机制来保持跟踪服务状态和避免发送请求到没有回应的服务器上。在我们的集群设置中,一个 Tomcat 实例扮演着负载均衡器的角色,处理请求层次上的失败重启,并转发 web 请求到集群中的其他节点。
* session 层次的失败重启
一个 web 客户可以拥有一个由HTTP服务器维持的 session。如果集群中的其中一台服务器挂起,集群中的另一台服务器能接手前一台服务器的session,保持连续性。这需要在集群内复制 session 数据。拥有 session 复制能力的 Tomcat 集群能处理 session 层次的失败重启。
+ session 状态的持久化
失败重启和负载均衡都需要集群内不同的服务器之间能进行 session 状态的复制。当原来的服务器失败时,session 状态复制允许客户无缝的从集群中的另外一台服务器上取得 session 信息。这个状态可以包括系统状态和/或应用状态(应用状态包含存储在 HTTP session中的对象和数据)。session复制的主要目的是当集群成员崩溃、为应用升级或者系统维护停止工作时能够不丢失任何 session 的内容。
谈到session的持续化,有一个简单的集群方案,集群成员不知道其他成员的session状态。在这个方案中,用户session完全在一台服务器上,由负载均衡器来选择。这叫做粘性session(或者叫session affinity)。因此seesion数据保存在接收web请求的集群成员上。
从另外一方面来将,集群可以以这样的一种方式实现,每一个集群成员完全明白其他成员的 session 状态,通过 session 状态的周期性传播到其他备用集群成员。这种session 被称为复制 session。
有三种方法实现session的持久化:
* 内存对内存的复制;
* 文件系统 session 持久化, session 信息从一个中央文件系统读写;
* 数据库 session 持久化, session 数据存储在一个JDBC数据存储器。
在内存 session 持久化中,当 HTTP session 中的独立的对象改变,这个对象将会被序列化到其他的备用机器上,而在数据库session持续化中,当 session 中的任何对象改变时,session 中的所有对象将被一起序列化。
数据库/文件系统session持续化的缺点是限制了当在 HttpSession 存储大型或大量对象时的可伸缩性。每一次用户增加一个对象到 HttpSession 中,session中所有的对象都会被序列化并被写到数据库或者共享文件系统中。
++ tomcat 中的 session 复制
当前 Tomcat 版本的 session 复制是一种 all-to-all 的复制,即在任何时,session 中的属性被传播到集群的所有成员。当集群小的情况下,这个算法是高效的,为应付大型集群的情况,Tomcat 的下一个版本将提供主-从复制,session 将仅仅被保存在一个或者两个备份服务器上。
在tomcat中,有三种类型的session复制机制:
* 内存中复制,使用Tomcat 5自带的SimpleTcpCluster(在org.apache.catalina.cluster.tcp包中,文件为server/lib/catalina-cluster.jar);
* session持久化,保存session在一个共享数据库上(org.apache.catalina.session.JDBCStore);
* 在共享的文件系统上保存session的状态 (org.apache.catalina.session.FileStore, part of catalina-optional.jar)。
+ 实现一个J2EE集群需要考虑的因素
设计J2EE集群需要考虑很多因素。下面这些问题都是在一个大型的J2EE系统需要考虑的(这个列表取自EJB基本训练文档-“用J2EE创建高可用性和可扩展的应用)。
++集群
* 那种类型的集群适用:垂直还是水平扩展?
* 在那个层次实现集群:web服务器或者,servlet,JSP容器还是HTTP session对象;或者EJB,应用服务JMS和JNDI对象还是数据库集群?
++ 负载均衡
* 选中一个服务器的时间(也就是affinity,姻亲关系):每次请求,每个事务或者每次会话?
* 如何选择服务器(也就是负载均衡策略):randomly, round-robin, weight-baesd, least loaded server,或者由应用决定?
* 负载均衡在哪个位置上实现,客户端还是服务器?
++ 容错
* 服务器如何进行失败重启检测?
* 什么时候适合失效重启和尝试使用其他的服务器?
* 失败节点上的系统和应用的状态?
++ session 状态持久化
* 状态如何传播?
* 传播的频率?
* 对象状态如何持久化?
* 状态持久化机制的效率如何?
* 复制的状态是否粘性?
* 网络环境对session状态的复制有限制吗?
+ 建议的集群设置
下面列出在推荐的集群环境中,我要达到的目标:
* 可升级能力高
* 容错
* 动态配置,易于管理
* 自动发现新成员
* 失败重启和负载均衡,session数据内存复制
* 可插拔/配置的负载均衡策略
* 当一个成员加入或离开时,能通知组成员
* 通过多播的方式,无掉包的信息传输
* 集群对 web 应用和服务器来说都是无缝的。对客户端和服务端都是透明的。客户透明是指客户端无须知道集群服务或者集群的设置。集群的识别和访问和单机一样,而不是各自独立的服务。服务器透明是指服务器上的应用程序代码不需要知道是在一个集群之内。应用程序代码不能和其他的集群成员通信。
+ 总结
在这篇文章的第二部分,我们看看如何部署一个集群(运行多个Tomcat服务实例)来达到上面提交的目标。我们将讨论在Tomcat 5 中实现 session 复制的集群架构和配置细节。
待续 …
关于:
Srini Penchikala 是Flagstar Bank的信息系统问题的专家。