一、实现无 session 集群的负载均衡
1 、安装 apache , resin ,可以把 apache 安装在独立的一台服务器上,如果硬件资源有限,也可以与其中一台后端 resin 装在同一台硬件机上。
2、 下载 mod_caucho.so 、安装 mod_caucho.so 模块(插件)。 mod_caucho.so 是 apaceh 与 resin 实现负载均衡的插件。把 mod_caucho.so 放于 apache 安装目录的 modules 目录中。在 apache 的 httpd.conf 文件中加:
#
# mod_caucho Resin Configuration
#
LoadModule caucho_module /usr/local/apache/modules/mod_caucho.so
# 这里 apache 安装在 /usr/local/apache /里,根据情况自定
ResinConfigServer 192.168.3.158 6802 # 注意这里要用具体的IP,曾经试过, apache 和其中后端 resin 安装在同一台服务器上,用 localhost 会出错,但用具体的IP时就正常了 ; 这里是用于负载均衡的端口,一般为 6802 。
# 这里可以只列出其中一台后端 resin 服务器的 IP 和端口, apache 会根据以下 ressin.conf 的配置来获得其它后端服务器的信息。
CauchoStatus yes
<Location /caucho-request>
SetHandler caucho-request
</Location>
到此,通过地址 /caucho-status 可以检查是否完成安装,出现一个表格显示一些数据就表明安装成功了,一些错误可以暂时不管,因为 resin 还没有配好,所以会出现一些红色的错误。
3 、修改 conf/resin.conf 文件 , 配置后端 resin 服务器。在 <server> à <http > 标签中加以下代码:
<cluster>
<srun server-id="a" host="192.168.3.158" port="6802" index="1"/>
<srun server-id="b" host="192.168.4.242" port="6802" index="2"/>
</cluster>
server-id: 表示服务器的名称。 caucho_module 生成的 sessionId 会以这个名称开头,以区分 session 的属主。
host: 服务器的的 IP 地址
port: 端口
另一台 resin 服务器的 resin.conf 加同样的代码。
启动 a 服务器用 : httpd.sh -server a start
启动 b 服务器用 : httpd.sh -server b start
关闭 a 服务器用 : httpd.sh -server a stop
关闭 b 服务器用 : httpd.sh -server b stop
四、启动后端 resin 服务器
用 httpd.sh -server a start 启动 192.168.3.158 服务器
用 httpd.sh -server b start 启动 192.168.4.242 服务器
注意:在 192.168.3.158 服务器只启动 a; 在 192.168.4.242 服务器只启动 B 。
五、查看配置的状态。
两台服务器都启动后通过 /caucho-status 查看状态,如果正常相应的服务器会用绿色 (ok) 显示,如果仍然有问题会以红色 (down) 显示。
至此,没有实现 session 集群的负载均衡就配置完成了。现在写起来就这么少,真正摸索的时候可花了不少功夫。 J
带 session 集群的负载均衡在以下会继续探讨,这步是最基本的,把这步完成了才能做下面的工作。
以下由官方网站介绍的用来调试负载均衡的方法:
- First, check your configuration with Resin standalone.sh. In other words, add a <http port='8080'/> and check port 8080.
- Check http://localhost/caucho-status. That will tell if mod_caucho has properly connected to the backend Resin server.
- Each srun host should be green and the mappings should match your resin.conf.
- If caucho-status fails entirely, the problem is in the mod_caucho installation and the Apache httpd.conf.
- If caucho-status shows the wrong mappings, there's something wrong with the resin.conf or the pointer to the backend server in httpd.conf.
- If caucho-status shows a red servlet runner, then Resin hasn't properly started.
- If you get a "cannot connect to servlet engine", caucho-status will show red, and Resin hasn't started properly.
- If Resin doesn't start properly, you should look at the logs in resin-3.0.x/log. You should start httpd.sh -verbose or httpd.exe -verbose to get more information.
- If Resin never shows a "srun listening to *:6802" line, it's not listening for connections from mod_caucho. You'll need to add a <srun> line.
- If you get Resin's "file not found", the Apache configuration is good but the resin.conf probably points to the wrong directories.
二、带 session 集群的负载均衡配置
前面已经配置好不带有 session 集群的负载均衡了,这时候我们的基本任务就已完成了。但不带 session 集群的负载均衡还不是最完美的,当其中一台服务( A )停止响应而将请求交给另外一台服务器( B )处理时,由于不能保存前一台服务器( A )的 session ,导致所有保存在在 A 服务器 session 中的信息都会丢失,这虽然比没有实现负载均衡前打不开网站要好很多,但我们的要求不应就此满足,我们要实现多台服务器之间无缝转接,要让客户端不会觉察到服务器端所做的变动。这时候就需要实现 session 的集群了(其实还可以通过数据库的方式实现,由于随着并发量的提高,可能会出现数据库的瓶颈,因此我不建议使用这种方法)。 Resin 已经可以支持这一功能了。下面会介绍这种实现的原理,知道实现原理,我们做起事件来才会事半功倍。如果暂时不想知道这些原理,只想知道如何配置,可以跳过这些原理的说明。
1 、原理说明
首先要说明点实现分布式 session 的很基本的前提 ------ 序列化。 resin 分布式的 session 是基于 java 序列化机制来保存 session 的,因此应用程序的对象必须要实现 java.io.Serializable 这个接口,这样分布式的 session 才能正常的工作。也就是说放在 session 里的对象必须是实现了 java.io.Serializable 这个接口的。
以下说明请求 session 的两种方式:普通的请求方式和高效的请求方式
普通的请求方式
首先,在 session 集群中,每台服务器会以另一台服务器作为它的备份服务器,当 session 的值有改变时,除了在本服务器上保存新的 session 外,还会把这些更新发送给备份服务器,,在备份服务器上也保存一份 session ,当服务器重启后,会查找备份服务器中的 session 来更新自身的 session 。
要看清楚集群式的 session 是如何工作的,可以看作负载均衡器是随机的发送一个请求到后端集群服务器中其中一个服务器的。服务器 C 拥有一个当前的 session, 但负载均衡器把请求转发给了 A ,在以下的示图中,这个请求将会修改这个 session 的值,因此在这里除了要加载这个 session 之外,还要保存这个 session 的值。
session ID 会包含有当前服务器的信息,举个例子,一个 sessionID 值为 ca8MbyA ,分析后知道这个 session 在 C ,同时 resin 也可以通过 cookie 知道备份 session 所在的服务器, A 必须知道每一个 cookie 所反映的 session 所在在的服务器,以便跟这些主机进行通信。以上的示例的配置定义了所有 A 需要知道的服务器群。如果 C 发生了故障, A 可以通过这个配置为 ca8MbyA 找 D 作为 C 的替代。
当请求过来时, A 会向 C 发出请求获取 session 的序列化数据( 2 : load ),由于 A 不会缓存 C 的 session 的数据,针对每一个请来它必须向 C 获取最新过 session 数据。由于请求都只是读取数据, A 唯一要做的事情是基于 TCP 协议的加载,会跳过第 3—5 步。但如果设置了 always-save-session ,将会执行保存的动作,也就是会执行 3—5 步。
在请求的最后, A 会把 session 的所有修改提交给 C ( 3 : store )。如果设置 always-save-session 为 false , session 并未做任何改动时,这一步会跳过。 A 把最新的序列化后的 session 内容提交给 C 。 C 把这个 session 保存到本地的硬盘 (4:save) ,并且保存到备份的 D(5 : backup) 。
高效的 session 请求方式 (sticky-Session)
实现了 sticky-session 的高效的负载均衡器会改进 session 的处理方式。拿以上的请求来说,很大的代价将会花在网络的流量上,也就是花中第 2 ( load )、第 3 步 (store) 中。高效的负载均衡器会避开第 2 、 3 步。看以下图示:举个例子,一个 sessionId 为 caaMbyA 的 session ,这个 session 是属于 C , C 直接把 session 提供给请求的 servlet ,不需要再经过 A ,不需要任何额外的工作,也不需要占用网络带宽。对于一个只读的请求,不需要任何的 session 集群的管理的成本。因此一个高效的负载均衡器会在这方面提高执行效率 (apache 就是采用这种方式 ) 。通常的浏览器会没有任何 session 集群的管理成本,但 AOL 浏览器会采用 non-sticky-session 的处理方式,也就是上面所提到的那种方式。
Session 的恢复
当 C 由于某些原因需要重启,重启后, C 必须要使用最高版本的 session ,但它自身保存在文件中 session 很可能已经不是最新的了。这时候会怎样处理呢?当一个“新的” session 请求到来时, C 会从 D 加载备用 session , D 的会是最新版本的 session 。一旦最新的 session 被加载后, C 就会正常工作,不会让人感觉到它重启过。
2 、配置
(1) 、配置 session 集群服务器。 session 的集群在 <server> 这个 tag 里配置。使用的标记是 <srun> ,这个标签在服务器的
<cluster> 标签里。用如下的设置:
<server>
<cluster>
<srun id="a" host="192.168.0.1" port="6802" index="1"/>
<srun id="b" host="192.168.0.2" port="6802" index="2"/>
<srun id="c" host="192.168.0.3" port="6802" index="3"/>
<srun id="d" host="192.168.0.4" port="6802" index="4"/>
</cluster>
<persistent-store type="cluster">
<init path="cluster"/>
</persistent-store>
其实 <cluster> 就是共用之前我们设置好的配置。
(2) 、配置序列化 session 。在 <web-app> 标签里加上以下的配置:
<web-app id='/foo'>...
<session-config><use-persistent-store/>
</session-config>
...
</web-app>
( 3 )、设置 always-save-session
分布式的 session 必须要知道 session 什么时候做了修改 , 以便保存新的 session 值。尽
管 resin 知道应用程序什么时候调用了 Httpsession.setAttribute 方法,但它不清楚 session
内部的值是否已经被改变。用以下的例子说明这一点:
Counter.java
package test;
public class Counter implements java.io.Serializable {
private int _count;
public int nextCount() { return _count++; }
}
把一个 Counter 对像做为 session 的一个 attribute , resin 不知道应用程序什么时候去调用
nextCounte 方法,它不能知道 _count 的值是否已经有了改动, resin 不会去备份这个新的
session ,除非设置了 always-save-session ,当 always-save-session 为 true 时, resin 会在
每一个请求时备份 session 。因此为了保存最新的 session 值, <session-config> 的配置还需要加上 <always-save-session/> 这一行。
设置如下:
<web-app id='/foo'>
...
<session-config>
<use-persistent-store/>
<always-save-session/>
</session-config>
...
</web-app>
附:
使用 apache 与 resin 实现负载均衡的原理
当使用 apache 作为 web 服务器时, mod_caucho.so 所做的工作是实现负载均衡.它发挥的作用是相当于硬件或 resin 的 LoadBalanceServlet 的负载均衡的作用。
要理解 resin 怎样通过插件实现负载均衡,知道 mod_caucho.so 如何分发请求到后端 JVM 是很重要的。以下的几点顺序说明了一个典型的请求。
1、 请求到达 apache 。
2、 mod_caucho.so 验证这个请求是否属于由 resin 来处理。
3、 mod_caucho.so 选择一个后端 JVM ,来处理请求。正常情况下分两种情况:
A、如果这是个旧的 session ,会把请求发送到拥有这个 session 的 JVM 。(这是通过 cookie 里的 sessionid 来确定去到哪一个 JVM 的)
B、如果这是一个新的请求,根据轮循的原则,把请求发送到其中一个JVM。
注:经测试后发现,如果有一个旧的 session ,它的属主服务器 (A) 当机,不接受请求了,这个时候插件会把这个请求转给另一台服务器 (B) ,在这个时候,如果 session 没有实现集群,实现多台服务器之间 session 同步,那么将会在另一台服务器 (B) 上建立一个新的 session ,原来的登录等信息将会丢失(但这比不能访问网站相比,这已是很大的进步了)。这时候尽管这个 session 属于A,但这时候请求还是会转发给B处理。如果A又重新正常工作了,这时候原来属于 A 的请求又会回归到A处理,不会再交给B处理了,这时候如果在B里又保存有登录信息,那么转移给B后,这些信息又会丢失了 ( 大家想想,假如在A发生故障,这时候请求提由B处理,几分种后又恢复正常了,请求又交回给A处理,然后几分钟后A又当机了,请求又会再交由B处理,整个过程的时间在 session 超时的时间内,那么第二把请求转发给B处理时,是否还需登录呢?答案是不需要的,因为没有 B 里 session 没有超时,仍然保存着上一次登录的 session ,但如果A正常的那几分钟对 session 作了修改,那么将不会反映到B里。这是经过测试得出的结论 ) 。换句话说,正常情况下插件按照 sessionid 的值自动把请求发送给相应的服务器,在其中一台服务器当机时,这个规则就会被打破。 sessionId 为 AsessionIdvalue 的请求不一定只能由A处理, sessionId 为 BsessionIdvalue 的请求不一定只能由B来处理。另外要注意的一点是,由A转到B的时候,在交接过程中,会出现短暂的中断,这点暂时不清楚原因,仍需要进一步研究。
4、 mod_caucho.so 通过 TCP secket 把请求发送到后端 JVM 。
5、 mod_caucho.so 接收后端发通过 TCP socket 送过来的响应。
mod_caucho.so 必须知道哪一个请求应该去到 resin ,比如, servlet-mappings 和 jsp 页面就应该去到 resin 。并且它必须知道后端服务器的TCP连接的主机名和端口,是通过 <srun> 这个标签获取这些信息。/ caucho-status 这个链接会在一个表格里显示所有的信息。 mod_caucho.so 通过正在运行的 resin 服务器获取这些信息。
如何实现通过 sessionId 来判断某一请求是由A处理还是由B处理呢?以下介绍这个过程的实现原理。
为了使同一 session 保留同一台JVM上, resin 会使用服务器的编号来编码 cookie 的 sessionId ,用上面的例子,插件会按以下的规则来生成 sessionId
index
cookie prefix
1
a xxx
2
b xxx
3
c xxx
在服务器端, mod_caucho 会解码 cookie ,并把请求发送到正确的服务器。比如 sesionId 为 bX8ZwooOz 的请求,将会发送到第二台服务器。基本原理就是这样了。