Bootstrap

Nginx+Tomcat负载均衡——Tomcat集群 Session复制

Tomcat集群搭建


一、成果

  1. windows很顺利的实现成功
  2. 阿里云linux服务器有组播问题
  3. 配置好的Server.xml如下:
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->


    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
    -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443
         This connector uses the NIO implementation that requires the JSSE
         style configuration. When using the APR/native implementation, the
         OpenSSL style configuration is required as described in the APR/native
         documentation -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine name="Catalina" defaultHost="localhost">

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- 
            Cluster(集群,族) 节点,如果你要配置tomcat集群,则需要使用此节点.
            className 表示tomcat集群时,之间相互传递信息使用那个类来实现信息之间的传递.
            channelSendOptions可以设置为2、4、8、10,每个数字代表一种方式
            2 = Channel.SEND_OPTIONS_USE_ACK(确认发送)
            4 = Channel.SEND_OPTIONS_SYNCHRONIZED_ACK(同步发送) 
            8 = Channel.SEND_OPTIONS_ASYNCHRONOUS(异步发送)
            在异步模式下,可以通过加上确认发送(Acknowledge)来提高可靠性,此时channelSendOptions设为10
        -->
    <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
        <!--
            Manager决定如何管理集群的Session信息。Tomcat提供了两种Manager:BackupManager和DeltaManager
            BackupManager-集群下的所有Session,将放到一个备份节点。集群下的所有节点都可以访问此备份节点
            DeltaManager-集群下某一节点生成、改动的Session,将复制到其他节点。
            DeltaManager是Tomcat默认的集群Manager,能满足一般的开发需求
            使用DeltaManager,每个节点部署的应用要一样;使用BackupManager,每个节点部署的应用可以不一样.

            className-指定实现org.apache.catalina.ha.ClusterManager接口的类,信息之间的管理.
            expireSessionsOnShutdown-设置为true时,一个节点关闭,将导致集群下的所有Session失效
            notifyListenersOnReplication-集群下节点间的Session复制、删除操作,是否通知session listeners
            maxInactiveInterval-集群下Session的有效时间(单位:s)。
            maxInactiveInterval内未活动的Session,将被Tomcat回收。默认值为1800(30min)
        -->
        <Manager className="org.apache.catalina.ha.session.DeltaManager"
                 expireSessionsOnShutdown="false"
                 notifyListenersOnReplication="true"/>

        <!--
            Channel是Tomcat节点之间进行通讯的工具。
            Channel包括5个组件:Membership、Receiver、Sender、Transport、Interceptor
        -->
        <Channel className="org.apache.catalina.tribes.group.GroupChannel">
         <!--
            Membership维护集群的可用节点列表。它可以检查到新增的节点,也可以检查到没有心跳的节点
            className-指定Membership使用的类
            address-组播地址
            port-组播端口
            frequency-发送心跳(向组播地址发送UDP数据包)的时间间隔(单位:ms)。默认值为500
            dropTime-Membership在dropTime(单位:ms)内未收到某一节点的心跳,则将该节点从可用节点列表删除。默认值为3000

            注: 组播(Multicast):一个发送者和多个接收者之间实现一对多的网络连接。
                一个发送者同时给多个接收者传输相同的数据,只需复制一份相同的数据包。
                它提高了数据传送效率,减少了骨干网络出现拥塞的可能性
                相同组播地址、端口的Tomcat节点,可以组成集群下的子集群
         -->
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>

            <!--
                Receiver : 接收器,负责接收消息
                接收器分为两种:BioReceiver(阻塞式)、NioReceiver(非阻塞式)

                className-指定Receiver使用的类
                address-接收消息的地址
                port-接收消息的端口
                autoBind-端口的变化区间
                如果port为4000,autoBind为100,接收器将在4000-4099间取一个端口,进行监听
                selectorTimeout-NioReceiver内轮询的超时时间
                maxThreads-线程池的最大线程数
            -->
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <!--
                Sender : 发送器,负责发送消息
                Sender内嵌了Transport组件,Transport真正负责发送消息
            -->
            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
                <!--
                    Transport分为两种:bio.PooledMultiSender(阻塞式)、nio.PooledParallelSender(非阻塞式) 
                -->
                <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>

            <!--
                Interceptor : Cluster的拦截器
                TcpFailureDetector-网络、系统比较繁忙时,Membership可能无法及时更新可用节点列表,
                此时TcpFailureDetector可以拦截到某个节点关闭的信息,
                并尝试通过TCP连接到此节点,以确保此节点真正关闭,从而更新集群可以用节点列表                 
            -->
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>

            <!--
                MessageDispatch15Interceptor-查看Cluster组件发送消息的方式是否设置为
                Channel.SEND_OPTIONS_ASYNCHRONOUS(Cluster标签下的channelSendOptions为8时)。
                设置为Channel.SEND_OPTIONS_ASYNCHRONOUS时,
                MessageDispatch15Interceptor先将等待发送的消息进行排队,然后将排好队的消息转给Sender
            -->
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
        </Channel>

        <!--
            Valve : 可以理解为Tomcat的拦截器
            ReplicationValve-在处理请求前后打日志;过滤不涉及Session变化的请求                   
            vmRouteBinderValve-Apache的mod_jk发生错误时,保证同一客户端的请求发送到集群的同一个节点
        -->
        <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
        <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

        <!--
            Deployer : 同步集群下所有节点的一致性。Deployer没试验成功过。。。
         -->
         <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                       tempDir="/tmp/war-temp/"
                       deployDir="/tmp/war-deploy/"
                       watchDir="/tmp/war-listen/"
                       watchEnabled="false"/>
        <!--
            ClusterListener : 监听器,监听Cluster组件接收的消息
            使用DeltaManager时,Cluster接收的信息通过ClusterSessionListener传递给DeltaManager
        -->
        <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
    </Cluster>        

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

具体需要了解每一个节点的含义,可以去官网 看看。

二、参考地址

Tomcat集群—Cluster节点配置 :配置的含义可以参考他的,挺详细的。
Tomcat集群配置学习篇—–分布式应用:过程可以参考他的。
多个tomcat之间的session复制

三、搭建场景

项目原本是单点部署的系统,由于考虑到后期压力的问题,需要把架构改成Nginx+tomcat负载均衡,但是项目存在Session无法保持的问题。

Nginx配置的是权重,不是IP_HASH,因为我们的数据来源很多都是同一个IP,用IPHASH就无法负载了。

四、遇到的坑

坑1:tomcat版本导致的配置不同

tomcat7->8在配置上有一些变化,“JvmRouteSessionIDBinderListener”监听器不需要添加,大部分网上的教程都没有提到这一点,所以在找教程的时候需要参考官网。引用官方原话如下:

Clustering

The addition of the HttpServletRequest.changeSessionId() method in Servlet 3.1 made the org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener unnecessary so it has been removed. It must be removed from cluster configurations when upgrading to Tomcat 8.

坑2:阿里云VPC下的linux ECS默认不支持组播

这个坑踩的比较痛,windows上测试成功后,放到阿里云就是不行。

VPC目前不支持二层组播功能,您可以通过阿里云提供的组播代理工具对组播应用进行代理。组播工具按操作系统分为Linux和Windows组播工具,配置详情参考Linux内核态组播工具使用说明和Windows组播代理工具使用说明。
单击这里获取工具源码,您可以直接使用已经打包好的代理工具,也可以根据需要重新打包代理工具。
组播代理分为服务端和客户端。服务端安装在需要发出组播数据包的一端ECS上(该ECS一般是组播源),对组播报文进行代理,如果只是多台服务器之间的报文组播通信,安装服务端就能满足需求,不需要客户端。
如果是在专有网络内的服务器之间的报文组播通信,如下图所示ECS1向ECS2、ECS3、ECS4进行数据组播,只需在ECS1上安装组播代理服务即可。

阿里云的组播教程有需要的可以去看看,目前由于服务器的内核版本的问题,阿里云上目前问题阻塞,准备通过调整部分架构来实现功能。
官方回复是:

目前组播工具开源了,需要您手动编译了
查看您服务器是Centos 7.3的系统,内核版本较高。
您尝试给服务器,更换为Centos6.x的系统,使用2.6.x 版本内核的系统,进行安装试下。
高版本的内核不保证都支持使用,建议是使用2.6.x 版本的内核。

·

参考资料:
1. 多个tomcat之间的session复制
2. Tomcat集群—Cluster节点配置

;