• Day700.Cluster组件:Tomcat的集群通信原理 -深入拆解 Tomcat & Jetty


    Cluster组件:Tomcat的集群通信原理

    Hi,我是阿昌,今天学习记录的是关于Cluster组件:Tomcat的集群通信原理的内容。
    为了支持水平扩展和高可用,Tomcat 提供了集群部署的能力,但与此同时也带来了分布式系统的一个通用问题,那就是如何在集群中的多个节点之间保持数据的一致性,比如会话(Session)信息。

    要实现这一点,基本上有两种方式,一种是把所有 Session 数据放到一台服务器或者一个数据库中,集群中的所有节点通过访问这台 Session 服务器来获取数据。

    另一种方式就是在集群中的节点间进行 Session 数据的同步拷贝,这里又分为两种策略:第一种是将一个节点的 Session 拷贝到集群中其他所有节点;

    第二种是只将一个节点上的 Session 数据拷贝到另一个备份节点。

    对于 Tomcat 的 Session 管理来说,这两种方式都支持。

    一、集群通信原理

    要实现集群通信,首先要知道集群中都有哪些成员。

    Tomcat 是通过组播(Multicast)来实现的。那什么是组播呢?

    为了理解组播,先来说说什么是“单播”。网络节点之间的通信就好像是人们之间的对话一样,一个人对另外一个人说话,此时信息的接收和传递只在两个节点之间进行,比如你在收发电子邮件、浏览网页时,使用的就是单播,也就是我们熟悉的“点对点通信”。

    如果一台主机需要将同一个消息发送多个主机逐个传输,效率就会比较低,于是就出现组播技术

    组播是一台主机向指定的一组主机发送数据报包,组播通信的过程是这样的:

    每一个 Tomcat 节点在启动时和运行时都会周期性(默认 500 毫秒)发送组播心跳包,同一个集群内的节点都在相同的组播地址和端口监听这些信息;在一定的时间内(默认 3 秒)不发送组播报文的节点就会被认为已经崩溃了,会从集群中删去。

    因此通过组播,集群中每个成员都能维护一个集群成员列表。

    二、集群通信配置

    有了集群成员的列表,集群中的节点就能通过 TCP 连接向其他节点传输 Session 数据。

    Tomcat 通过 SimpleTcpCluster 类来进行会话复制(In-Memory Replication)。

    要开启集群功能,只需要将server.xml里的这一行的注释去掉就行:

    在这里插入图片描述

    变成这样:

    在这里插入图片描述
    虽然只是简单的一行配置,但这一行配置等同于下面这样的配置,也就是说 Tomcat 给我们设置了很多默认参数,这些参数都跟集群通信有关。

    
    
    <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.MessageDispatchInterceptor"/>
      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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140

    从上面的的参数列表可以看到,默认情况下 Session 管理组件 DeltaManager 会在节点之间拷贝 Session,DeltaManager 采用的一种 all-to-all 的工作方式,即集群中的节点会把 Session 数据向所有其他节点拷贝,而不管其他节点是否部署了当前应用。

    • 当集群节点数比较少时,比如少于 4 个,这种 all-to-all 的方式是不错的选择;

    • 但是当集群中的节点数量比较多时,数据拷贝的开销成指数级增长,这种情况下可以考虑 BackupManagerBackupManager 只向一个备份节点拷贝数据。

    在大体了解了 Tomcat 集群实现模型后,就可以对集群作出更优化的配置了。

    Tomcat 推荐了一套配置,使用了比 DeltaManager 更高效的 BackupManager,并且通过 ReplicationValve 设置了请求过滤。

    这里还请注意在一台服务器部署多个节点时需要修改 Receiver 的侦听端口,另外为了在节点间高效地拷贝数据,所有 Tomcat 节点最好采用相同的配置,具体配置如下:

    <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                     channelSendOptions="6">
    
        <Manager className="org.apache.catalina.ha.session.BackupManager"
                       expireSessionsOnShutdown="false"
                       notifyListenersOnReplication="true"
                       mapSendOptions="6"/>
             
         <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.MessageDispatchInterceptor"/>
         
         <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    三、集群工作过程

    Tomcat 的官网给出了一个例子,来说明 Tomcat 集群模式下是如何工作的,以及 Tomcat 集群是如何实现高可用的。比如集群由 Tomcat A 和 Tomcat B 两个 Tomcat 实例组成,按照时间先后顺序发生了如下事件:

    1、Tomcat A 启动

    Tomcat A 启动过程中,当 Host 对象被创建时,一个 Cluster 组件(默认是 SimpleTcpCluster)被关联到这个 Host 对象。当某个应用在web.xml中设置了 Distributable 时,Tomcat 将为此应用的上下文环境创建一个 DeltaManager。SimpleTcpCluster 启动 Membership 服务和 Replication 服务。

    2、Tomcat B 启动(在 Tomcat A 之后启动)

    首先 Tomcat B 会执行和 Tomcat A 一样的操作,然后 SimpleTcpCluster 会建立一个由 Tomcat A 和 Tomcat B 组成的 Membership。接着 Tomcat B 向集群中的 Tomcat A 请求 Session 数据,如果 Tomcat A 没有响应 Tomcat B 的拷贝请求,Tomcat B 会在 60 秒后 time out。在 Session 数据拷贝完成之前 Tomcat B 不会接收浏览器的请求。

    3、Tomcat A 接收 HTTP 请求,创建 Session 1

    Tomcat A 响应客户请求,在把结果发送回客户端之前,ReplicationValve 会拦截当前请求(如果 Filter 中配置了不需拦截的请求类型,这一步就不会进行,默认配置下拦截所有请求),如果发现当前请求更新了 Session,就调用 Replication 服务建立 TCP 连接将 Session 拷贝到 Membership 列表中的其他节点即 Tomcat B。在拷贝时,所有保存在当前 Session 中的可序列化的对象都会被拷贝,而不仅仅是发生更新的部分。

    4、Tomcat A 崩溃

    当 Tomcat A 崩溃时,Tomcat B 会被告知 Tomcat A 已从集群中退出,然后 Tomcat B 就会把 Tomcat A 从自己的 Membership 列表中删除。并且 Tomcat B 的 Session 更新时不再往 Tomcat A 拷贝,同时负载均衡器会把后续的 HTTP 请求全部转发给 Tomcat B。在此过程中所有的 Session 数据不会丢失。

    5、Tomcat B 接收 Tomcat A 的请求

    Tomcat B 正常响应本应该发往 Tomcat A 的请求,因为 Tomcat B 保存了 Tomcat A 的所有 Session 数据。

    6、Tomcat A 重新启动

    Tomcat A 按步骤 1、2 操作启动,加入集群,并从 Tomcat B 拷贝所有 Session 数据,拷贝完成后开始接收请求。

    7、Tomcat A 接收请求,Session 1 被用户注销

    Tomcat 继续接收发往 Tomcat A 的请求,Session 1 设置为失效。请注意这里的失效并非因为 Tomcat A 处于非活动状态超过设置的时间,而是应用程序执行了注销的操作(比如用户登出)而引起的 Session 失效。这时 Tomcat A 向 Tomcat B 发送一个 Session 1 Expired 的消息,Tomcat B 收到消息后也会把 Session 1 设置为失效。

    8、Tomcat B 接收到一个新请求,创建 Session 2

    同理这个新的 Session 也会被拷贝到 Tomcat A。

    9、Tomcat A 上的 Session 2 过期

    因超时原因引起的 Session 失效 Tomcat A 无需通知 Tomcat B,Tomcat B 同样知道 Session 2 已经超时。因此对于 Tomcat 集群有一点非常重要,所有节点的操作系统时间必须一致。不然会出现某个节点 Session 已过期而在另一节点此 Session 仍处于活动状态的现象。

    四、总结

    Tomcat 集群对 Session 的拷贝支持两种方式:DeltaManagerBackupManager

    当集群中节点比较少时,可以采用 DeltaManager,因为 Session 数据在集群中各个节点都有备份,任何一个节点崩溃都不会对整体造成影响,可靠性比较高。

    当集群中节点数比较多时,可以采用 BackupManager,这是因为一个节点的 Session 只会拷贝到另一个节点,数据拷贝的开销比较少,同时只要这两个节点不同时崩溃,Session 数据就不会丢失。

    在 Tomcat 官方推荐的配置里,ReplicationValve 被配置成下面这样:

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

    你是否注意到,filter 的值是一些 JS 文件或者图片等,这是为什么呢?

    这些静态资源不涉及session,直接过滤就好

    小的集群可以用Tomcat原生方案,大集群还是用Redis


  • 相关阅读:
    Apache Airflow (一) : Airflow架构及
    MFC Windows 程序设计[115]之多样式方形下拉列表框
    YOLOv5代码解读[03] utils/loss.py文件解析
    企业电子招标采购系统源码之从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理
    农业信息化技术导论886笔记复习
    设计模式-组合模式
    Polygon zkEVM FFT和多项式evaluate计算的circom约束
    【python】网络爬虫与信息提取--scrapy爬虫框架介绍
    关于线程池概念使用
    汽车电子 TLV1702AQDGKRQ1 比较器 通用 开路集电极
  • 原文地址:https://blog.csdn.net/qq_43284469/article/details/126194376