集群化与会话复制

只需将下列信息放入 <Engine> 或 <Host> 元素即可实现集群:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

上述配置启用了全局(all-to-all)会话复制功能,全局会话复制是指利用 DeltaManager 来只复制会话中的变更(Session Delta,也译作“会话增量”)。这里说的“全局”是指:会话变更会被复制到集群中的所有其他节点(指 Tomcat 实例)中。全局复制非常适于小集群,但不建议在大集群(包含很多 Tomcat 节点)上采用这种方法。另外,值得注意的是,当使用 delta manager 时,它会将变更复制到所有的节点上,甚至包括那些根本没有部署该应用的节点。

为了解决这个问题,你就得使用 BackupManager。它会把会话数据复制给一个指定的备份节点(这种复制也被称为“配对复制”),而且该备份节点也一定要部署了相关应用。BackupManager 的缺点在于:不像 DeltaManager 那样久经实践考验。

下面是一些重要的默认值。

  1. IP 组播地址为:228.0.0.4
  2. IP 组播端口为:45564(端口和地址一起确定了集群成员)。
  3. 广播的 IP 是 java.net.InetAddress.getLocalHost().getHostAddress()(你一定不能广播 127.0.0.1,这是一个常见错误。)
  4. 侦听复制信息的 TCP 端口是在 4000 - 4100 之间遇到的第一个能用的服务器套接字。
  5. 两个侦听器都配置有 ClusterSessionListener
  6. 两个拦截器都配置有 TcpFailureDetector 和 MessageDispatch15Interceptor

下面是默认的集群配置:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="8">

          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

集群基本知识

要想在 Tomcat 8 上运行会话复制,需要执行以下步骤:

  • 所有的会话属性必须实现 java.io.Serializable
  • 在 server.xml 中取消注释 Cluster 元素。
  • 如果你已经定义了自定义集群值,确保在 server.xml 中的 Cluster 元素下面也定义了 ReplicationValve
  • 如果你的多个 Tomcat 实例都运行在同一台机器上,则要确保每个实例都具有唯一的 tcpListenPort。通常 Tomcat 会自行解决这个问题,会在 4000 - 4100 上自动侦测可用的端口。
  • 确保 web.xml 含有 <distributable/> 属性。
  • 如果使用 mod_jk,则要确保在 <Engine name="Catalina" jvmRoute="node01" > 上设定 jvmRoute 属性。jvmRoute 属性值必须匹配 workers.properties 中的 worker 名称。
  • 所有的节点必须具有相同的时间,并且与 NTP 服务器同步。
  • 确保负载均衡配置了会话模式.

负载均衡可以通过多种技术来实现。

注意:会话状态是通过 cookie 来记录的,所以你的 URL 必须保持一致,否则就会创建一个新会话。

注意:当前如要支持集群,需要 JDK 1.5 或更新版本。

集群模块使用 Tomcat 的 JULI 日志框架,所以可以通过 logging.properties 文件来配置日志。为了跟踪消息,你可以启用 org.apache.catalina.tribes.MESSAGES 键上的日志。

概述

在 Tomcat 中,可以使用以下方法中的一种启用会话复制:

  1. 使用会话持久性,将会话保存到共享文件系统中(PersistenceManager + FileStore)。
  2. 使用会话持久性,将会话保存到共享数据库中(PersistenceManager + JDBCStore)。
  3. 使用内存复制,使用 Tomcat 自带的 SimpleTcpCluster(lib/catalina-tribes.jar + lib/catalina-ha.jar)。

在这一版本的 Tomcat 中,可以使用 DeltaManager 执行全局式会话状态复制,或者使用 BackupManager 执行备份复制,将会话复制到一个节点上。全局式会话复制这种算法只有在集群较小时才比较有效。对于大型集群,更多使用主从会话复制,将会话存储到一台配置了 BackupManager 的备份服务器上。

当前可以使用域名 worker 属性(mod_jk 版本 > 1.2.8)来构建集群分区,从而有可能利用 DeltaManager 实现更具有可扩展性的集群方案(需要为此配置域的拦截器)。为了在全局性环境中降低网络流量,可以将集群分成几个较小的分组。为不同的分组使用不同的组播地址即能实现这种方案。下图展示的是一种简单的配置方案。

DNS 轮询
|
负 载 均 衡 器
/              \
集群 1              集群 2
/     \             /     \
Tomcat 1 Tomcat 2  Tomcat 3 Tomcat 4 

值得注意的是,使用会话复制仅仅是集群化的一个基础方案。关于集群的实现,另一个常用的概念是耕种(farming),比如:只需将应用部署到一个服务器上,集群就会将部署分发到整个集群的各个节点中。这都是 FarmWarDeployer 所具有的功能(参看 server.xml 中的集群范例)。

集群信息

通过组播心跳包(heartbeat)建立起成员(Membership)关系,因此,如果希望细分集群,可以改变 <Membership>元素中的组播 IP 地址或端口。

心跳包中含有 Tomcat 节点的 IP 地址,以及 Tomcat 用来侦听会话复制流量的 TCP 端口。所有的数据通信都使用了 TCP 协议。

ReplicationValve 用于查找请求结束的时间,如果存在会话复制,就对该复制进行初始化。只复制会话变更的数据(通过在会话上调用 setAttribute 或 removeAttribute 来完成)。

复制的异步与同步模式应该是最值得我们注意的一个特点了。在同步复制模式下,复制的会话通过线缆传送,重新在所有集群节点上实例化,这样才会返回请求。同步和异步是通过 channelSendOptions 标志(整型值)来配置的。SimpleTcpCluster/DeltaManager 组合的默认值是 8,从而是异步。在异步复制过程中,请求不必等到数据被复制完毕即可返回。异步复制缩短了请求时间,而同步复制则保证了能在请求返回之前复制完会话。

当发生崩溃时,将会话绑定到故障转移节点

如果你使用了 mod_jk 而没有使用粘性会话(sticky session),或者粘性会话由于某种原因而不起作用,或者仅是故障转移,会话 id 需要修改,因为它之前含有之前 Tomcat 的 worker id(通过 Engine 元素中的 jvmRoute 定义)。为了解决这个问题,就要用到 JvmRouteBinderValve。

JvmRouteBinderValve 将重写会话 id,以便确保下一个请求在故障转移后依然能保持粘性(不会因为 worker 不再可用而回滚到某个随机的节点中)。利用同样的名字,该值重写了 cookie 中的 JSESSIONID 值。假如没有正确地设置 valve,将使 mod_jk 模块在失败后很难保持会话的粘性。

记住,如果在 server.xml 中自定义值,那么默认值将不再有效,所以一定要确保添加了默认所定义的值。

提示:

利用属性 sessionIdAttribute 可以改变包含旧会话 id 的请求属性名。默认的请求属性名是:org.apache.catalina.ha.session.JvmRouteOrignalSessionID

技巧:

可以启用 mod_jk 翻转模式在删除一个节点, 然后启用了 mod_jk Worker 禁用 JvmRouteBinderValves 。这种用例意味着只有请求的会话才能得到迁移。

配置范例

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="6">

          <Manager className="org.apache.catalina.ha.session.BackupManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"
                   mapSendOptions="6"/>
          <!--
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
          -->
          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="5000"
                      selectorTimeout="100"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

下面来仔细剖析一下这段代码。

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6">

Cluster 是主要元素,可在该元素内配置所有的集群相关细节。 对于 SimpleTcpCluster 类或者调用SimpleTcpCluster.send 方法的对象,它们所发出的每一个消息上都附加着一个 channelSendOptions 标志。DeltaManager 使用 SimpleTcpCluster.send 方法发送信息,而备份管理器则直接通过 channel 来发送自身。

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2022

备案号:京ICP备15003423号-3