Windows下搭建Tomcat集群的配置详解



《 Windows下搭建Tomcat集群基础入门详解 》




前言
在搭建 《 Apache + Tomcat 实现Web服务器集群 》 前我们还需要实现 Tomcat集群实现Session复制 ,这样用户只需要登录一次即可,就不用每次请求都需要登录了,这儿使用的是两个 Tomcat 节点实现集群及节点间 Session 的复制。


首先我们需要找到 Tomcat 的配置文件 “ server.xml  ” ,在其中找到 <Engine> 节点,在该节点下添加一个 <Cluster className =“org.apache.catalina.ha.tcp.SimpleTcpCluster”/> 节点,


关于 Tomcat 集群的默认配置如下:


<!--  Cluster-(集群) 节点,在配置 Tomcat 集群时,必须添加此节点用以启动 集群 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">


	<!--  Manager-节点决定如何管理集群的 Session 信息 -->
	<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-用于维护集群中可用的节点列表 -->
		<Membership className="org.apache.catalina.tribes.membership.McastService"
			address="228.0.0.4"
			port="45564"
			frequency="500"
			dropTime="3000"/>
		
		<!--  Receiver-接收器,负责接收消息 -->
		<!--  两种接收器: BioReceiver(阻塞式)、NioReceiver(非阻塞式) -->
		<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
				address="auto"
				port="4000"
				autoBind="100"
				selectorTimeout="5000"
				maxThreads="6"/>


		<!--  Sender-发送器,使用 Sender 中内嵌的 Transport 组件实现消息的返送 -->
		<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
			<!--  消息发送也分为两种:bio.PooledMultiSender(阻塞式)、nio.PooledParallelSender(非阻塞式)  -->
			<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
		</Sender>
		
		<!--  Interceptor-Cluster的拦截器,当拦截到某个节点的关闭信息时,自身节点会通过 TCP 方式连接到此(异常节点)节点,确保此节点真正关闭,从而更新集群的中可用节点列表-->
		<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
		
		<!--  Interceptor-Cluster的拦截器,MessageDispatch15Interceptor 会先将等待发送的消息进行排队,然后将排好队的消息转给 Sender ,再由 Sender 进行消息的发送 -->
		<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
	</Channel>


	<!--  ReplicationValve-在处理请求前后打日志 -->
	<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
	
	<!--  JvmRouteBinderValve-当 Apache 的 mod_jk 发生错误时,确保同一客户端的请求发送到集群的同一个节点 -->
	<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>


	<!--  Deployer-确保所有 Tomcat 集群中所有节点的一致性 -->
	<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 ,则 ClusterSessionListener 会将拦截到的消息床底给 DeltaManager -->
	<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>




配置简述:
1、组播地址为 228.0.0.4 。
2、组播端口为 45564(端口和地址共同决定了集群成员 status)。
3、广播的 IP 是 java.net.InetAddress.getLocalHost().getHostAddress()(请确保没有广播127.0.0.1,这是一个常见错误,如果广播为 127.0.0.1,集群会创建失败哟!)。
4、该 TCP 端口监听复制消息是在第一个可用的 socket 服务器 4000-4100 范围中。
5、两个侦听配置 ClusterSessionListener 和 JvmRouteSessionIDBinderListener ,在这儿我只使用了 ClusterSessionListener 即可。
6、两个拦截器配置为 TcpFailureDetector 和 MessageDispatch15Interceptor。


注意事项:
使用上述配置将使用 DeltaManager 复制 DeltaSession 来启用 Tomcat 节点间全部会话的复制。也就是将会话复制到集群中的所有其他 Tomcat 节点。
关于这个 Tomcat 集群配置只使用于较小的群集非常适用,但我们不建议将它用于较大的群集(多个 Tomcat 节点)。
另外,在使用 DeltaManager 时,它将复制到所有节点,只要在集群的 Tomcat 节点中配置了集群即使没有部署应用程序的节点也是复制/广播到该节点。
为了解决这个问题,需要使用备份管理器。此管理器仅将会话数据复制到一个备份节点,并且仅复制到已部署应用程序的节点。使用备份管理器的缺点:和 DeltaManager 的测试不一样。 



实现 Tomcat 集群需要满足基本要求
1、所有的会话属性都必须实现 java.io.Serializable 接口。
2、取消 server.xml中 Cluster 元素注释。
3、如果在集群中自定了 Valve,则请确保该在 server.xml 中定义了 ReplicationValve 元素。
4、如果一个集群中所有的 Tomcat 实例在同一台服务器上运行,​​请确保 server.xml 中 TcpListenPort 属性对于每个实例都是唯一的,但是在大多数情况下,Tomcat 可通过自动检测 4000-4100 范围内的可用端口来解决此问题。
5、必须确保在应用程序 web.xml 配置文件中存在 <distributable/> 元素。
6、如果您使用的是 mod_jk 连接器,请确保在引擎中设置了 jvmRoute 属性 ( <Engine name="Catalina" jvmRoute="node01" > ),并且 jvmRoute 属性值与 workers.properties 中所配置 workers 的名称相同。
7、确保所有节点具有相同的时间(如果时间不一致,这儿有个代名词叫时间戳问题)并与NTP服务同步。
8、确保您的负载均衡器配置为具有粘性的会话模式。




在 Tomcat 中启用会话复制,三种方法实现会话相同
1、使用会话持久性,并将会话保存到共享文件系统(PersistenceManager + FileStore)
2、使用会话持久性,并将会话保存到共享数据库(PersistenceManager + JDBCStore)
3、使用内存复制,使用 Tomcat 附带的 SimpleTcpCluster(lib / catalina-tribes.jar + lib / catalina-ha.jar)




集群描述
Membership 是使用组播心跳建立的。因此,如果希望细分 Tomcat 群集,可以通过修改 <Membership> 元素中的多播IP地址或端口来实现。
心跳包含 Tomcat节点的IP地址和 Tomcat 监听复制流量的 TCP 端口。所有数据通信都通过 TCP 进行的。
ReplicationValve 主要被用来找出当请求已经完成并开始复制的会话,如果有,则只有在会话发生改变时才会复制 session 数据(主要通过在会话上调用 setAttribute 或 removeAttribute 实现)。
关于 Tomcat 中 session 数据复制分为同步复制和异步复制,这个会直接影响集群性能。
在同步复制模式下,请求不会返回,直到复制的会话通过线路发送并重新实例化所有其他群集节点。
同步与异步配置使用该 channelSendOptions 标志并且是一个整数值。SimpleTcpCluster/DeltaManager 组合的默认值是 “ 8 ”,这个是异步的。
为了更加方便,channelSendOptions 还可以通过别名来设置,然后在启动时将其转换为整数值。
别名包括:“异步”(别名为“async”),“byte_message”(别名为“字节”),“多播”,“安全”,“synchronized_ack”(别名为“同步”),“udp” ”。
使用逗号分隔多个名称,例如通过选项的“异步,多播” SEND_OPTIONS_ASYNCHRONOUS | SEND_OPTIONS_MULTICAST。




将崩溃后的会话绑定到故障转移节点
如果在集群中使用的是 mod_jk 而不是使用 “ 粘性会话 ”,或者由于某些原因,粘性会话不起作用,或者您只是简单地进行故障转移,则需要修改会话标识即可,因为它之前包含上一个 Tomcat 的  worker  标识(由 jvmRoute 在 <Engine/> 元素中定义)。
为了解决故障转移的问题,需要使用 JvmRouteBinderValve。


使用 JvmRouteBinderValve 重会话 ID 以确保在故障切换后下一个请求将保持粘滞状态(并且不会退回到随机节点,因为该 worker 不再可用)。Valve 重写同名的 cookie 中的 JSESSIONID 值。如果没有这个 Valve,在 mod_jk 模块发生故障时将难以确保粘性。


注意:
需要注意的是如果要在 server.xml 中添加自己的 Valve,那么缺省的值不再有效,请确保添加默认定义的所有适当的 Valve。


提示:
通过属性 sessionIdAttribute,可以更改包含旧会话标识的请求属性名称。
默认属性名称是 org.apache.catalina.ha.session.JvmRouteOrignalSessionID。


技巧:
在将节点终止时,您可以通过 JMX 启用 mod_jk 转换模式!在所有 JvmRouteBinderValve 备份上设置为 true,在 mod_jk 上禁用 worker,然后终止该节点并重新启动它!然后启用 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.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>




将其进行分解:


主元素:

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


在这个元素中所有的集群细节都可以配置。其中 channelSendOptions 是连接到 SimpleTcpCluster 类或调用 SimpleTcpCluster 的任何对象所发送的每个消息的标志。
在 java 文档中可以查看到发送标志的具体描述,DeltaManager 使用 SimpleTcpCluster 发送信息。而备份管理器则直接通过通道发送消息。


管理器配置模板:
<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"/>
-->



如果 <Context/> 元素中没有定义管理器,将使用该管理器模板。在 Tomcat 5.x 中,每个 webapp 的标记分布必须使用相同的管理器,因为在 Tomcat 中可以为每个 webapp 定义一个管理器类,
所以可以在一个集群中使用混合管理器。很显然,一个节点上的应用程序的管理器与另一个节点上的同一个应用程序的的管理器相对应。
如果没有为 webapp 指定管理器,并且 webapp 被标记为 <distributable /> Tomcat将采用此管理器配置并创建克隆此配置的管理器实例。 


通信:

<Channel className="org.apache.catalina.tribes.group.GroupChannel">




在 Tomcat 中使用的组通信框架。该元素封装了与通信和成员逻辑相关的所有内容。


节点间的组播:

<Membership className="org.apache.catalina.tribes.membership.McastService"
		address="228.0.0.4"
		port="45564"
		frequency="500"
		dropTime="3000"/>



集群中节点通过多播的方式完成的。在节点中还支持静态成员使用 StaticMembershipInterceptor,如果想延长加入的节点超出多播,
那么必须地址属性是使用的组播地址,端口是组播端口。这两者一起创建集群分离。如果想要保证群集和生产群集的高性能,最简单的配置是使群集位于与生产群集不同的多播地址/端口组合之上。
成员组通过广播其自身的 TCP地址/端口 到其他节点,以便节点之间的通信可以通过TCP完成。




消息接收:
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
		address="auto"
		port="5000"
		selectorTimeout="100"
		maxThreads="6"/>


在一个节点中,发送和接收数据的逻辑已被分解为两个功能组件。

该 Receiver 顾名思义就是负责接消息的接收。由于节点的堆栈线程少(其他框架也采用了一种流行的改进方法),此组件中只有一个线程池,它具有 maxThreads 和 minThreads 设置。
其中 address 属性是由成员组件向其他节点广播的主机地址。


消息发送:

<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
	<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>


该 Sender 顾名思义就是负责接消息的发送,Sender 有一个shell组件ReplicationTransmitter,但是在子组件 Transport 中完成工作的。一个节点中支持 Sender pool,以便消息的并行发送,使用 NioSender ,也可以实现消息的滨兴发送。
同时:同时向多个 Sender 发送一条消息。
并行:并行向多个 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"/>



在节点中使用堆栈进行消息的发送。在堆栈中的每个元素都称为拦截器,其工作方式与 Tomcat servlet 容器中的 Valve 非常相似。
使用拦截器后,其业务逻辑可以分解为更易于管理的代码片断。常用的三种拦截器:
1、TcpFailureDetector - 通过 TCP 验证集群中的节点是否挂掉,如果组播中的数据包被丢弃,这个拦截器作用是防止被误报(即使该节点仍然活着并且正在运行,但是被误标记为崩溃的节点)。
2、MessageDispatchInterceptor - 将消息分派给线程(thread pool)以便异步消息发送。
3、ThroughputInterceptor - 输出简单消息流量统计信息。
注意:拦截器的顺序很重要。在 server.xml 中是以堆栈表示的方式。把它们想象成一个链表的形式,头部是第一个拦截器,尾部是最后一个。


集群请求跟踪:
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
	filter=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>



在集群中配置 Valve 来跟踪对 Web 应用程序的请求,在上面已经提到了 ReplicationValve 和 JvmRouteBinderValve。
<Cluster> 元素本身不是 Tomcat 中管道的一部分,而是集群将 Valve 添加到其父容器。如果在 <Engine> 元素中配置了 <Cluster> 元素,则需要将 Valve 添加到 Engine 下。 


支持远程部署:
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
		tempDir="/tmp/war-temp/"
		deployDir="/tmp/war-deploy/"
		watchDir="/tmp/war-listen/"
		watchEnabled="false"/>



Tomcat集群默认支持远程部署,即可以在其他集群节点上部署和取消部署应用程序。
这个组件的状态处于不断变化之中,但很快就解决了。Tomcat 5.0 和 5.5 之间的部署方法发生了变化,此时该组件的逻辑已更改为部署目录必须与 webapps 目录匹配的位置。 


监听器:

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


由于 SimpleTcpCluster 本身是 Channel 对象的“ 发送者 ” 和 “ 接收者 ”,因此组件可以将自己注册为 SimpleTcpCluster 的监听器。

上面的 ClusterSessionListener 监听器监听 DeltaManager 复制消息,并将 Delta 应用于 Manager,然后将其应用于 Session。



理解 Tomcat 集群如何运行的
为了便于理解群集如何工作的,这儿使用两个 Tomcat 实例 ,即 TomcatA 和 TomcatB 。操作内容如下:
1、TomcatA 启动
2、TomcatB 启动(等待 TomcatA 启动完成)
3、TomcatA 收到一个请求,会话 S1 被创建
4、TomcatA 崩溃
5、TomcatB 收到会话请求 S1
6、TomcatA 启动
7、TomcatA 接收到一个请求,在会话(S1)上调用 invalidate
8、TomcatB 接收请求,进行新会话(S2)
9、TomcatA 会话 S2 由于不活动而过期(这儿就需要重新登录了)


有了一个大致的提纲后,下面我们进行详细的介绍:


1、TomcatA 启动
Tomcat 使用标准启动序列启动。当创建 Host 对象时,将与它关联一个集群对象。在解析上下文时,如果 web.xml 中定义了 distributable 元素,Tomcat 会询问 Cluster 类(在本例中 SimpleTcpCluster )为复制的 <Context/> 中创建 Manager。
因此,启用集群后,可以在 web.xml 中设置可分发的数据集, Tomcat 将为该 <Context/> 创建一个DeltaManager,而不是一个 StandardManager。群集还将启动 Membership  服务(多播)和 Replication 服务(tcp单播)。




2、TomcatB 启动
当 TomcatB 启动时,它遵循与TomcatA相同的序列。该集群已启动并将建立 membership(TomcatA,TomcatB)。
TomcatB 现在将从群集中已存在的服务器(在本例中 为TomcatA )请求会话状态。TomcatA 响应请求,并且在 TomcatB 开始监听 HTTP 请求之前,status 已从 TomcatA 转移到 TomcatB。
如果 TomcatA 没有响应,TomcatB 将在60秒后超时,并发出日志通知。每个 web 应用程序的会话状态都被转移到它的 web.xml 中。
注意:为了使会话有效的复制,所有的 Tomcat实例都应该配置相同。


3、TomcatA 收到一个请求,会话 S1 被创建
对 TomcatA 的请求与没有会话复制的处理方式完全相同。当请求完成时,ReplicationValve 会在将响应返回给用户之前拦截请求。
此时 TomcatA 发现会话已被修改,并且它使用 TCP 将会话复制到 TomcatB。一旦序列化的数据被传递到 TCP 逻辑,请求就会通过 Valve 管道返回给用户。
对于每个请求,整个会话都被复制,这允许在会话中修改属性的代码,而不需要复制 setAttribute 或 removeAttribute。可以使用 useDirtyFlag 配置参数来优化会话复制的次数。


4、TomcatA 崩溃
当TomcatA崩溃时,TomcatB 收到一个通知,TomcatA 已经退出集群。TomcatB 从其成员列表中删除 TomcatA,TomcatA 将不再收到 TomcatB 发生的任何更改通知。
负载均衡器会将来自 TomcatA 的请求重定向到 TomcatB,并且所有会话都是最新的。


5、TomcatB 收到会话请求 S1
TomcatB 将处理请求作为任何其他请求。


6、TomcatA 启动
在 TomcatA 启动之后,在 TomcatA 开始接受新请求并使其可用之前,将遵循上述1)2)所述的启动顺序。
TomcatA 将加入集群,并与 TomcatB 联系以获取当前所有会话的状态。一旦它收到会话状态,它就完成加载并打开它的HTTP / mod_jk端口。
因此,在 TomcatB 接收到会话状态之前,没有任何请求会到达 TomcatA。


7、TomcatA 接收到一个请求,在会话(S1)上调用 invalidate
无效的调用被拦截,并且会话在无效会话中排队。当请求完成时,不再发送已更改的会话,而是向 TomcatB 发送一条 “ 过期 ” 的消息,并且 TomcatB 也会使该会话失效。


8、TomcatB 接收请求,进行新会话(S2)
与步骤3中相同的场景)


9、TomcatA会话S2由于不活动而过期
无效的调用被拦截,就像用户会话无效时一样,会话被无效会话排队。此时,无效会话将不会被复制,直到另一个请求通过系统并检查无效队列为止。


Membership : 群集 Membership 使用非常简单的组播 ping 建立。每个 Tomcat 实例将定期发送一个多播 ping,在 ping 消息中,实例将广泛地将其 IP 和 TCP 监听端口进行复制。
如果在集群中一个实例在给定的时间范围内没有收到这样的 ping,则该成员被认为是死的。非常简单,而且非常有效!当然,只需要在系统上启用多播。


TCP Replication : 一旦接收到组播 ping,member 就会被添加到集群。
在下一个请求时复制,发送实例将使用 Host 和 Port 信息并建立一个TCP Socket。使用这个 Socket,它将发送序列化后的数据。


分布式锁定和使用框架页面 : Tomcat 不会保持会话实例在群集中同步。这种逻辑的实现会花费很多开销并导致各种问题。
如果在客户端使用多个请求同时访问同一会话,则最后一个请求将覆盖集群中的其他会话。






















Apache Tomcat 官网: https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值