您还可以查看配置参考文档。
集群/会话复制操作指南
重要提示
目录
快速入门
只需添加
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>到您的 <Engine> 或 <Host> 元素中即可启用集群。
使用上述配置将启用使用 DeltaManager 进行全对全会话复制,以复制会话增量。全对全是指集群中每个会话都会复制到所有其他节点。这对于小型集群非常有效,但我们不建议将其用于大型集群——例如超过4个节点。此外,当使用 DeltaManager 时,Tomcat 会将会话复制到所有节点,即使是未部署应用程序的节点。
为了解决这些问题,您会希望使用 BackupManager。BackupManager 仅将会话数据复制到一个备份节点,并且仅复制到已部署应用程序的节点。一旦您使用 DeltaManager 运行了一个简单的集群,随着集群中节点数量的增加,您可能需要迁移到 BackupManager。
以下是一些重要的默认值
- 多播地址为 228.0.0.4
- 多播端口为 45564(端口和地址共同决定集群成员身份)。
- 广播的 IP 是
java.net.InetAddress.getLocalHost().getHostAddress()(请确保您没有广播 127.0.0.1,这是一个常见错误) - 监听复制消息的 TCP 端口是范围
4000-4100内的第一个可用服务器套接字 - 监听器配置为
ClusterSessionListener - 配置了两个拦截器:
TcpFailureDetector和MessageDispatchInterceptor
以下是默认集群配置
<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>我们将在本文档后面更详细地介绍这一部分。
安全
集群实现基于安全、受信任的网络用于所有集群相关的网络流量。在不安全、不受信任的网络上运行集群是不安全的。
有许多选项可以为 Tomcat 集群提供安全、受信任的网络。这些包括
- 私有局域网
- 虚拟专用网络 (VPN)
- IPSEC
EncryptInterceptor 提供机密性和完整性保护,但它不能防御在不受信任的网络上运行 Tomcat 集群相关的所有风险,特别是 DoS 攻击。
集群基础
要在您的 Tomcat 11 容器中运行会话复制,应完成以下步骤
- 您所有的会话属性都必须实现
java.io.Serializable - 取消注释 server.xml 中的
Cluster元素 - 如果您定义了自定义集群阀,请确保在 server.xml 的 Cluster 元素下也定义了
ReplicationValve - 如果您的 Tomcat 实例在同一台机器上运行,请确保每个实例的
Receiver.port属性是唯一的,在大多数情况下,Tomcat 足够智能,可以通过自动检测 4000-4100 范围内的可用端口来自行解决此问题。 - 确保您的
web.xml包含<distributable/>元素 - 如果您正在使用 mod_jk,请确保在 Engine
<Engine name="Catalina" jvmRoute="node01" >处设置了 jvmRoute 属性,并且 jvmRoute 属性值与 workers.properties 中的 worker 名称匹配。 - 确保所有节点时间同步并与 NTP 服务同步!
- 确保您的负载均衡器配置为粘性会话模式。
负载均衡可以通过多种技术实现,详见负载均衡章节。
注意:请记住您的会话状态是通过 cookie 跟踪的,因此您的 URL 从外部看必须相同,否则将创建一个新会话。
集群模块使用 Tomcat JULI 日志框架,因此您可以通过常规的 logging.properties 文件配置日志。要跟踪消息,您可以在键:org.apache.catalina.tribes.MESSAGES 上启用日志记录。
概述
要在 Tomcat 中启用会话复制,可以遵循三种不同的路径来实现相同的功能
- 使用会话持久化,将会话保存到共享文件系统(PersistenceManager + FileStore)
- 使用会话持久化,将会话保存到共享数据库(PersistenceManager + JDBCStore)
- 使用内存复制,使用 Tomcat 自带的 SimpleTcpCluster(lib/catalina-tribes.jar + lib/catalina-ha.jar)
Tomcat 可以使用 DeltaManager 执行会话状态的全对全复制,或者使用 BackupManager 仅向一个节点执行备份复制。全对全复制算法仅在集群较小时效率高。对于大型集群,您应该使用 BackupManager 来采用主-备会话复制策略,其中会话将仅存储在一个备份节点上。
目前您可以使用域工作器属性 (mod_jk > 1.2.8) 来构建集群分区,从而有可能通过 DeltaManager 获得更具可伸缩性的集群解决方案(您需要为此配置域拦截器)。为了在全对全环境中减少网络流量,您可以将集群分成更小的组。这可以通过为不同的组使用不同的多播地址轻松实现。一个非常简单的设置将如下所示:
DNS Round Robin
|
Load Balancer
/ \
Cluster1 Cluster2
/ \ / \
Tomcat1 Tomcat2 Tomcat3 Tomcat4这里需要提及的是,会话复制仅仅是集群的开始。另一个用于实现集群的流行概念是“部署场”(farming),即您只将应用程序部署到一个服务器,然后集群会将部署分发到整个集群。FarmWarDeployer(参见 server.xml 中的集群示例)可以实现所有这些功能。
在下一节中,我们将深入探讨会话复制的工作原理以及如何配置它。
集群信息
成员身份是通过多播心跳建立的。因此,如果您希望细分您的集群,可以通过更改 <Membership> 元素中的多播 IP 地址或端口来实现。
心跳包含 Tomcat 节点的 IP 地址和 Tomcat 监听复制流量的 TCP 端口。所有数据通信都通过 TCP 进行。
ReplicationValve 用于判断请求何时完成并(如果有的话)启动复制。只有当会话发生更改(通过调用会话上的 setAttribute 或 removeAttribute)时,数据才会被复制。
最重要的性能考虑因素之一是同步复制与异步复制。在同步复制模式下,请求不会返回,直到复制的会话已通过网络发送并在所有其他集群节点上重新实例化。同步与异步通过 channelSendOptions 标志配置,它是一个整数值。SimpleTcpCluster/DeltaManager 组合的默认值为 8,即异步。有关各种 channelSendOptions 值的更多讨论,请参阅配置参考。
为了方便,channelSendOptions 可以通过名称而不是整数来设置,这些名称在启动时会转换为其整数值。有效的选项名称是:“asynchronous”(别名“async”)、“byte_message”(别名“byte”)、“multicast”、“secure”、“synchronized_ack”(别名“sync”)、“udp”、“use_ack”。使用逗号分隔多个名称,例如为选项 SEND_OPTIONS_ASYNCHRONOUS | SEND_OPTIONS_MULTICAST 传入“async, multicast”。
您可以阅读更多关于发送标志(概述)或发送标志(javadoc)的信息。在异步复制期间,数据复制完成前请求就会返回。异步复制会缩短请求时间,而同步复制则保证在请求返回之前会话已完成复制。
崩溃后将会话绑定到故障转移节点
如果您正在使用 mod_jk 且未使用粘性会话,或者由于某种原因粘性会话不起作用,或者您只是在进行故障转移,则会话 ID 需要修改,因为它之前包含旧 Tomcat 的工作器 ID(在 Engine 元素中由 jvmRoute 定义)。为了解决这个问题,我们将使用 JvmRouteBinderValve。
JvmRouteBinderValve 会重写会话 ID,以确保在故障转移后,下一个请求仍保持粘性(并且不会因为工作器不再可用而回退到随机节点)。该阀门会重写 cookie 中同名的 JSESSIONID 值。如果没有这个阀门,mod_jk 模块在发生故障时将更难保证粘性。
请记住,如果您在 server.xml 中添加了自己的阀门,则默认值将不再有效,请确保您添加了所有默认情况下定义的适当阀门。
提示
使用属性 sessionIdAttribute 可以更改包含旧会话 ID 的请求属性名称。默认属性名称是 org.apache.catalina.ha.session.JvmRouteOriginalSessionID。
技巧
您可以在将节点移除到所有备份节点之前,通过 JMX 启用此 mod_jk 转换模式!在所有 JvmRouteBinderValve 备份上将 enable 设置为 true,禁用 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.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.send 方法的任何对象的标志。发送标志的描述可在我们的 javadoc 网站上找到。DeltaManager 使用 SimpleTcpCluster.send 方法发送信息,而备份管理器则直接通过通道发送。
更多信息,请访问参考文档
<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">channel 元素是 Tribes,Tomcat 内部使用的组通信框架。此元素封装了所有与通信和成员身份逻辑相关的内容。
更多信息,请访问参考文档
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>成员身份通过多播实现。请注意,如果您想将成员身份扩展到多播之外,Tribes 还支持使用 StaticMembershipInterceptor 的静态成员身份。address 属性是使用的多播地址,port 是多播端口。这两个共同创建了集群分离。如果您想要一个 QA 集群和一个生产集群,最简单的配置是让 QA 集群使用与生产集群不同的多播地址/端口组合。
成员身份组件会向其他节点广播自身的 TCP 地址/端口,以便节点之间可以通过 TCP 进行通信。请注意,广播的地址是 Receiver.address 属性的值。
更多信息,请访问参考文档
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="5000"
selectorTimeout="100"
maxThreads="6"/>在 Tribes 中,发送和接收数据的逻辑被分解为两个功能组件。Receiver,顾名思义,负责接收消息。由于 Tribes 堆栈是无线程的(现在也被其他框架采纳的流行改进),这个组件中有一个线程池,它有 maxThreads 和 minThreads 设置。
address 属性是成员身份组件将广播到其他节点的主机地址。
更多信息,请访问参考文档
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>Sender 组件,顾名思义,负责向其他节点发送消息。Sender 有一个外壳组件 ReplicationTransmitter,但真正的工作是在子组件 Transport 中完成的。Tribes 支持拥有一个发送者池,这样消息可以并行发送,如果使用 NIO 发送者,您也可以并发发送消息。
并发意味着同时向多个发送者发送一条消息,而并行意味着同时向多个发送者发送多条消息。
更多信息,请访问参考文档
<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>Tribes 使用堆栈发送消息。堆栈中的每个元素称为拦截器,其工作方式与 Tomcat servlet 容器中的阀门非常相似。使用拦截器,可以将逻辑分解成更易于管理的模块。上面配置的拦截器是:
TcpFailureDetector - 通过 TCP 验证崩溃的成员,如果多播包丢失,此拦截器可防止误报,即即使节点仍然存活并运行,也被标记为崩溃。
MessageDispatchInterceptor - 将消息分派给一个线程(线程池)以异步发送消息。
ThroughputInterceptor - 打印消息流量的简单统计信息。
请注意,拦截器的顺序很重要。它们在 server.xml 中定义的顺序就是它们在通道堆栈中表示的顺序。可以将其视为一个链表,头部是第一个拦截器,尾部是最后一个。
更多信息,请访问参考文档
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>集群使用阀门来跟踪对 Web 应用程序的请求,我们上面提到了 ReplicationValve 和 JvmRouteBinderValve。<Cluster> 元素本身不是 Tomcat 管道的一部分,而是集群将其阀门添加到其父容器中。如果 <Cluster> 元素配置在 <Engine> 元素中,则阀门会被添加到引擎中,依此类推。
更多信息,请访问参考文档
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>默认的 Tomcat 集群支持部署场(farmed deployment),即集群可以在其他节点上部署和卸载应用程序。此组件的状态目前正在调整中,但很快就会解决。Tomcat 5.0 和 5.5 之间的部署算法发生了变化,从那时起,此组件的逻辑也随之改变,要求部署目录必须与 webapps 目录匹配。
更多信息,请访问参考文档
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>由于 SimpleTcpCluster 本身就是 Channel 对象的发送者和接收者,因此组件可以将自己注册为 SimpleTcpCluster 的监听器。上面的监听器 ClusterSessionListener 监听 DeltaManager 复制消息,并将增量应用于管理器,管理器再将其应用于会话。
更多信息,请访问参考文档
集群架构
组件级别
Server
|
Service
|
Engine
| \
| --- Cluster --*
|
Host
|
------
/ \
Cluster Context(1-N)
| \
| -- Manager
| \
| -- DeltaManager
| -- BackupManager
|
---------------------------
| \
Channel \
----------------------------- \
| \
Interceptor_1 .. \
| \
Interceptor_N \
----------------------------- \
| | | \
Receiver Sender Membership \
-- Valve
| \
| -- ReplicationValve
| -- JvmRouteBinderValve
|
-- LifecycleListener
|
-- ClusterListener
| \
| -- ClusterSessionListener
|
-- Deployer
\
-- FarmWarDeployer
工作原理
为了更容易理解集群的工作原理,我们将带您经历一系列场景。在此场景中,我们只计划使用两个 Tomcat 实例:TomcatA 和 TomcatB。我们将涵盖以下事件序列:
TomcatA启动TomcatB启动(等待 TomcatA 启动完成)TomcatA收到请求,创建会话S1。TomcatA崩溃TomcatB收到会话S1的请求TomcatA启动TomcatA收到请求,会话S1被调用 invalidateTomcatB收到一个新会话S2的请求TomcatA会话S2因不活动而过期。
好的,现在我们有了一个好的序列,我们将带您详细了解会话复制代码中发生了什么
TomcatA启动Tomcat 使用标准启动序列启动。当创建 Host 对象时,会有一个集群对象与之关联。解析上下文时,如果 web.xml 文件中存在 distributable 元素,Tomcat 会要求 Cluster 类(在此情况下为
SimpleTcpCluster)为复制上下文创建一个管理器。因此,当启用集群并在 web.xml 中设置 distributable 时,Tomcat 会为该上下文创建一个DeltaManager而不是StandardManager。集群类将启动成员身份服务(多播)和复制服务(TCP 单播)。有关架构的更多信息将在本文档后面介绍。TomcatB启动当 TomcatB 启动时,它会遵循与 TomcatA 相同的序列,但有一个例外。集群启动并建立成员身份(TomcatA、TomcatB)。TomcatB 现在将从集群中已存在的服务器(本例中为 TomcatA)请求会话状态。TomcatA 响应请求,在 TomcatB 开始监听 HTTP 请求之前,状态已从 TomcatA 传输到 TomcatB。如果 TomcatA 没有响应,TomcatB 将在 60 秒后超时,发出日志条目,并继续启动。对于 web.xml 中包含 distributable 的每个 Web 应用程序,会话状态都会被传输。(注意:为了有效地使用会话复制,您所有的 Tomcat 实例都应配置相同。)
TomcatA收到请求,创建会话S1。发送到 TomcatA 的请求处理方式与没有会话复制时完全相同,直到请求完成,此时
ReplicationValve将在响应返回给用户之前拦截请求。此时它发现会话已被修改,并使用 TCP 将会话复制到 TomcatB。一旦序列化数据被传递给操作系统的 TCP 逻辑,请求就会通过阀门管道返回给用户。对于每个请求,整个会话都会被复制,这允许修改会话属性但未调用 setAttribute 或 removeAttribute 的代码也能被复制。可以使用 useDirtyFlag 配置参数来优化会话复制的次数。TomcatA崩溃当 TomcatA 崩溃时,TomcatB 会收到 TomcatA 已退出集群的通知。TomcatB 会从其成员列表中移除 TomcatA,TomcatA 将不再收到 TomcatB 中发生的任何更改的通知。负载均衡器会将来自 TomcatA 的请求重定向到 TomcatB,所有会话都将是当前的。
TomcatB收到会话S1的请求没什么特别的,TomcatB 将像处理其他任何请求一样处理该请求。
TomcatA启动启动时,在 TomcatA 开始接收新请求并使其可用之前,它将遵循上述 1) 2) 中描述的启动序列。它将加入集群,联系 TomcatB 获取所有会话的当前状态。一旦它收到会话状态,它就完成加载并打开其 HTTP/mod_jk 端口。因此,在从 TomcatB 收到会话状态之前,任何请求都不会到达 TomcatA。
TomcatA收到请求,会话S1被调用 invalidateinvalidate 调用被拦截,会话与已失效的会话一起排队。当请求完成后,它不会发送已更改的会话,而是向 TomcatB 发送一个“过期”消息,TomcatB 也会将会话失效。
TomcatB收到一个新会话S2的请求与步骤 3) 中的场景相同
TomcatA会话S2因不活动而过期。invalidate 调用以与用户使会话失效时相同的方式被拦截,并且会话与已失效的会话一起排队。此时,已失效的会话不会被复制,直到另一个请求通过系统并检查失效队列。
呼!:)
成员身份 集群成员身份是通过非常简单的多播 ping 建立的。每个 Tomcat 实例都会定期发送一个多播 ping,在 ping 消息中,实例会广播其 IP 和用于复制的 TCP 监听端口。如果一个实例在给定时间内没有收到这样的 ping,则该成员被认为是死的。非常简单,非常有效!当然,您需要在系统上启用多播。
TCP 复制 一旦收到多播 ping,该成员就会添加到集群中。在下一个复制请求时,发送实例将使用主机和端口信息并建立一个 TCP 套接字。使用此套接字,它会发送序列化数据。我选择 TCP 套接字的原因是它具有内置的流控制和保证交付。所以我知道,当我发送一些数据时,它会到达那里 :)
分布式锁定和使用框架的页面 Tomcat 不会使集群中的会话实例保持同步。实现这种逻辑会带来太多开销并导致各种问题。如果您的客户端使用多个请求同时访问同一个会话,那么最后一个请求将覆盖集群中的其他会话。
使用 JMX 监控您的集群
在使用集群时,监控是一个非常重要的问题。一些集群对象是 JMX MBean
将以下参数添加到您的启动脚本中
set CATALINA_OPTS=\
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=%my.jmx.port% \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false集群 MBean 列表
| 名称 | 描述 | MBean ObjectName - Engine | MBean ObjectName - Host |
|---|---|---|---|
| 集群 | 完整的集群元素 | type=Cluster |
type=Cluster,host=${HOST} |
| DeltaManager | 此管理器控制会话并处理会话复制 | type=Manager,context=${APP.CONTEXT.PATH}, host=${HOST} |
type=Manager,context=${APP.CONTEXT.PATH}, host=${HOST} |
| FarmWarDeployer | 管理将应用程序部署到集群中所有节点的过程 | 不支持 | type=Cluster, host=${HOST}, component=deployer |
| 成员 | 表示集群中的一个节点 | type=Cluster, component=member, name=${NODE_NAME} | type=Cluster, host=${HOST}, component=member, name=${NODE_NAME} |
| ReplicationValve | 此阀门控制向备份节点的复制 | type=Valve,name=ReplicationValve |
type=Valve,name=ReplicationValve,host=${HOST} |
| JvmRouteBinderValve | 这是一个集群回退阀,用于将会话 ID 更改为当前 Tomcat 的 jvmroute。 | type=Valve,name=JvmRouteBinderValve, context=${APP.CONTEXT.PATH} |
type=Valve,name=JvmRouteBinderValve,host=${HOST}, context=${APP.CONTEXT.PATH} |
常见问题
请参阅FAQ 的集群部分。
