SpringBoot를 내장 톰캣으로 실행하고 만약 세션을 사용한다면 세션 클러스터링 설정이 필요하다. 토큰이나 Redis를 사용하는 경우에는 불필요하겠지만 내장 톰캣의 세션을 그대로 이용한다면 세션 클러스터링을 통해 세션 공유 설정을 해야지만 여러 대의 was로 서비스가 가능할 것이다.
테스트 환경
- SpringBoot 2.6.7, Tomcat 9.0.62, JDK 11
1. 의존성 설정
먼저 build.gradle에 tomcat-catalina-ha를 의존성으로 추가한다.
implementation 'org.apache.tomcat:tomcat-catalina-ha:9.0.62'
2. Java Config 설정
@Configuration을 통해 내장 톰캣에 대한 세션 클러스터링 Java Config 파일을 작성한다. 아래에 MultiCast 포트로 설정한 45564는 서버에서 TCP/UDP 포트 오픈이 필요하고, Receiver 포트로 설정한 5000은 TCP 포트 오픈이 필요하다.
@Configuration
public class TomcatClusterConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(final TomcatServletWebServerFactory factory) {
factory.addContextCustomizers(new TomcatClusterContextCustomizer());
}
}
class TomcatClusterContextCustomizer implements TomcatContextCustomizer {
@Override
public void customize(final Context context) {
context.setDistributable(true);
DeltaManager manager = new DeltaManager();
manager.setExpireSessionsOnShutdown(false);
manager.setNotifyListenersOnReplication(true);
context.setManager(manager);
configureCluster((Engine) context.getParent().getParent());
}
private void configureCluster(Engine engine) {
//cluster
SimpleTcpCluster cluster = new SimpleTcpCluster();
cluster.setChannelSendOptions(6);
//channel
GroupChannel channel = new GroupChannel();
//membership setting
McastService mcastService = new McastService();
mcastService.setAddress("228.0.0.4");
mcastService.setPort(45564); // TCP&UDP port 오픈 필요
mcastService.setFrequency(500);
mcastService.setDropTime(3000);
channel.setMembershipService(mcastService);
//receiver
NioReceiver receiver = new NioReceiver();
receiver.setAddress("auto");
receiver.setMaxThreads(6);
receiver.setPort(5000); // TCP port 오픈 필요
channel.setChannelReceiver(receiver);
//sender
ReplicationTransmitter sender = new ReplicationTransmitter();
sender.setTransport(new PooledParallelSender());
channel.setChannelSender(sender);
//interceptor
channel.addInterceptor(new TcpPingInterceptor());
channel.addInterceptor(new TcpFailureDetector());
channel.addInterceptor(new MessageDispatchInterceptor());
cluster.addValve(new ReplicationValve());
cluster.addValve(new JvmRouteBinderValve());
cluster.setChannel(channel);
cluster.addClusterListener(new ClusterSessionListener());
engine.setCluster(cluster);
}
}
3. application.yml 로그 설정
톰캣 관련 로그를 상세히 보기 위해 application.yml에 톰캣 설정 로그를 DEBUG로 설정한다.
logging:
level:
org.apache.tomcat: DEBUG
org.apache.catalina: DEBUG
4. Local 환경 테스트
위의 설정을 포함한 간단한 SpringBoot 어플리케이션을 Local에 8080, 8081 포트로 2개를 실행한다. 그리고 같은 브라우저에서 각각 localhost:8080, localhost:8081을 접속 했을 때 JSESSIONID가 동일하게 공유되는 것을 확인할 수 있다.
로그 상으로 대략 아래와 같은 로그들이 출력된다.
5. AWS EC2 테스트
AWS EC2에서 테스트를 진행했는데 동작을 하지 않았다. 좀더 찾아봐야겠지만 AWS EC2환경에서는 multicast 지원이 안된다는 얘기도 있고 어떻게 설정을 해야하는지 몰라서 일단 톰캣 세션의 클러스터를 multicast를 사용하지 않는 방법(StaticMembership)으로 다시 설정을 변경하고 테스트를 해봤다.
6. Java Config 설정2 - StaticMembership(not Multicast)
@Configuration
public class TomcatStaticClusterConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(final TomcatServletWebServerFactory factory) {
factory.addContextCustomizers(new TomcatStaticClusterContextCustomizer());
}
}
class TomcatStaticClusterContextCustomizer implements TomcatContextCustomizer {
@Override
public void customize(final Context context) {
context.setDistributable(true);
DeltaManager manager = new DeltaManager();
manager.setExpireSessionsOnShutdown(false);
manager.setNotifyListenersOnReplication(true);
context.setManager(manager);
configureCluster((Engine) context.getParent().getParent());
}
private void configureCluster(Engine engine) {
//cluster
SimpleTcpCluster cluster = new SimpleTcpCluster();
cluster.setChannelStartOptions(3);
cluster.setChannelSendOptions(8);
//channel
GroupChannel channel = new GroupChannel();
StaticMembershipInterceptor staticMembershipInterceptor = new StaticMembershipInterceptor();
/** [WAS1] 설정 기준
// 대상 정보 - was2정보
StaticMember staticMember = new StaticMember();
staticMember.setPort(4056);
staticMember.setSecurePort(-1); // default
staticMember.setHost("172.31.45.3");
staticMember.setUniqueId("{0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2}");
staticMembershipInterceptor.addStaticMember(staticMember);
//receiver(현재 자신의 정보) - was1
NioReceiver receiver = new NioReceiver();
receiver.setAddress("172.31.44.193");
receiver.setMaxThreads(6);
receiver.setPort(4055); // was1: 4055, was2: 4056
channel.setChannelReceiver(receiver);
*/
/** [WAS2] 설정 기준 */
// 대상 정보 - was1정보
StaticMember staticMember = new StaticMember();
staticMember.setPort(4055);
staticMember.setSecurePort(-1); // default
staticMember.setHost("172.31.44.193");
staticMember.setUniqueId("{0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1}");
staticMembershipInterceptor.addStaticMember(staticMember);
//receiver(현재 자신의 정보) - was2
NioReceiver receiver = new NioReceiver();
receiver.setAddress("172.31.45.3");
receiver.setMaxThreads(6);
receiver.setPort(4056); // was1: 4055, was2: 4056
channel.setChannelReceiver(receiver);
channel.addInterceptor(staticMembershipInterceptor);
//sender
ReplicationTransmitter sender = new ReplicationTransmitter();
sender.setTransport(new PooledParallelSender());
channel.setChannelSender(sender);
//interceptor
channel.addInterceptor(new TcpPingInterceptor());
channel.addInterceptor(new TcpFailureDetector());
channel.addInterceptor(new MessageDispatchInterceptor());
cluster.addValve(new ReplicationValve());
cluster.addValve(new JvmRouteBinderValve());
cluster.setChannel(channel);
cluster.addClusterListener(new ClusterSessionListener());
engine.setCluster(cluster);
}
}
일단 was1, was2 기준으로 각각 주석으로 구분해서 설정하고, ec2 2대에 각 업로드해서 was1로 접속해보니 was2번 아래와 같이 세션 정보 받았다는 로그는 찍힌다.
일단 위와 같이 로그까지 확인했는데 각 EC2의 public IP가 달라서 실제 동작 확인은 할 수 없었다. 그래서 좀더 테스트 해보는 차원에서 EC2 2개를 타켓 그룹으로 묶고 앞에 ALB를 붙여서 was1로 접속 확인 후 was1번 내리고, was2로 접속 했을 때 JSESSIONID가 변하지 않는 것을 확인했다. 다시 was1을 올리고 was2를 내렸을 경우에도 JSESSIONID가 변하지 않는것을 확인했다.
참고:
https://oingdaddy.tistory.com/149
'개발 > SpringBoot' 카테고리의 다른 글
SpringBoot Log4jdbc 를 사용한 Mybatis 쿼리 로그 출력 (0) | 2022.06.05 |
---|---|
SpringBoot InMemory DB(H2) 사용 (0) | 2022.05.31 |
SpringBoot @RestControllerAdvice를 통한 예외 처리 (0) | 2022.05.29 |
SpringBoot @RestControllerAdvice not working (0) | 2022.05.06 |
SpringBoot ResourceLoader 사용해서 classpath 파일 읽기 (0) | 2022.03.14 |
댓글