我正在尝试使用 Spring boot 和 Traefik 设置会话复制。我在以下链接中找到了如何使用 Tomcat 及其
server.xml
文件来实现它:Docker swarm 中的 Tomcat 会话复制。
我的整个应用程序看起来就像这个存储库中的应用程序https://github.com/trajano/tomcat-docker-swarm(撰写文件、JSP 页面等)。
不同的是我使用以下 dockerfile:
FROM tomcat:10-jdk17-openjdk-slim
WORKDIR /app
COPY build/libs/demo-0.0.1-SNAPSHOT.war app.war
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.war"]
但是,我无法使用带有嵌入式 Tomcat 的 Spring Boot 使其工作。我还发现这篇文章展示了如何使用嵌入式 tomcat 设置它:如何使用 Spring Boot 嵌入式 tomcat 设置 Tomcat 会话复制,但它使用
StaticMembershipInterceptor
并且我无法使用 CloudMembershipService
让它工作.
我当前的配置类如下所示:
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCust() {
return factory -> {
factory.addContextCustomizers(tomcatContextCutomizer());
};
}
private TomcatContextCustomizer tomcatContextCutomizer() {
return context -> {
DeltaManager deltaManager = new DeltaManager();
deltaManager.setNotifyListenersOnReplication(true);
deltaManager.setExpireSessionsOnShutdown(false);
context.setManager(deltaManager);
SimpleTcpCluster simpleTcpCluster = new SimpleTcpCluster();
simpleTcpCluster.registerManager(deltaManager);
GroupChannel channel = new GroupChannel();
NioReceiver receiver = new NioReceiver();
channel.setChannelReceiver(receiver);
ReplicationTransmitter sender = new ReplicationTransmitter();
sender.setTransport(new PooledParallelSender());
channel.setChannelSender(sender);
channel.addInterceptor(new TcpPingInterceptor());
channel.addInterceptor(new TcpFailureDetector());
channel.addInterceptor(new MessageDispatchInterceptor());
simpleTcpCluster.addValve(new ReplicationValve());
simpleTcpCluster.addValve(new JvmRouteBinderValve());
simpleTcpCluster.addClusterListener(new ClusterSessionListener());
CloudMembershipService clms = new CloudMembershipService();
channel.setUtilityExecutor(new ScheduledThreadPoolExecutor(10));
clms.setChannel(channel);
channel.setMembershipService(clms);
MembershipProvider mp = new DNSMembershipProvider();
clms.setMembershipProvider(mp);
mp.setMembershipService(channel.getMembershipService());
simpleTcpCluster.setChannel(channel);
Host host = (Host)context.getParent();
host.setCluster(simpleTcpCluster);
};
}
此代码的结果是,我能够看到容器由于日志消息而彼此看到:
CloudMembershipProvider : Member added: org.apache.catalina.tribes.membership.MemberImpl[tcp://1.2.3.4:0,1.2.3.4,0, alive=-1, securePort=-1, UDP Port=-1, id={1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 }, payload={}, command={}, domain={}]`.<br><br>
But the session never get's replicated. Some logs implying what may be happening are: <br>
`DeltaManager : Manager []: skipping state transfer. No members active in cluster group. <br>
或
ReplicationValve : Cluster is standalone: reset Session Request Delta at context []
。
使用
context.setDistributable(true)
没有帮助。
我正在使用 Spring boot 2.7.18 和相应的 Tomcat 版本 9.0.83。
我发现了问题。我将为任何想要为会话复制提供开箱即用配置的人保留这个问题。
您不应该创建新的 MembershipProvider,而是需要指定提供者的名称。
调整后的配置为:
DeltaManager deltaManager = new DeltaManager();
deltaManager.setNotifyListenersOnReplication(true);
deltaManager.setExpireSessionsOnShutdown(false);
context.setManager(deltaManager);
SimpleTcpCluster simpleTcpCluster = new SimpleTcpCluster();
simpleTcpCluster.registerManager(deltaManager);
GroupChannel channel = new GroupChannel();
NioReceiver receiver = new NioReceiver();
channel.setChannelReceiver(receiver);
ReplicationTransmitter sender = new ReplicationTransmitter();
sender.setTransport(new PooledParallelSender());
channel.setChannelSender(sender);
channel.addInterceptor(new TcpPingInterceptor());
channel.addInterceptor(new TcpFailureDetector());
channel.addInterceptor(new MessageDispatchInterceptor());
simpleTcpCluster.addValve(new ReplicationValve());
simpleTcpCluster.addValve(new JvmRouteBinderValve());
simpleTcpCluster.addClusterListener(new ClusterSessionListener());
CloudMembershipService clms = new CloudMembershipService();
channel.setUtilityExecutor(new ScheduledThreadPoolExecutor(10));
clms.setChannel(channel);
channel.setMembershipService(clms);
clms.setMembershipProviderClassName(DNSMembershipProvider.class.getName());
simpleTcpCluster.setChannel(channel);
Engine engine = (Engine)context.getParent().getParent();
engine.setCluster(simpleTcpCluster);
};
clms.setMembershipProviderClassName(DNSMembershipProvider.class.getName());
simpleTcpCluster.setChannel(channel);
Engine engine = (Engine)context.getParent().getParent();
engine.setCluster(simpleTcpCluster);