복붙노트

[SPRING] 내장 된 Tomcat 세션 클러스터링을 사용하여 스프링 부트 응용 프로그램을 설치하는 방법은 무엇입니까?

SPRING

내장 된 Tomcat 세션 클러스터링을 사용하여 스프링 부트 응용 프로그램을 설치하는 방법은 무엇입니까?

임베디드 tomcat 세션 클러스터링을 사용하여 스프링 부트 애플리케이션을 설정하고 싶습니다.

embedded Tomcat에는 server.xml 파일이 없으므로 TomcatEmbeddedServletContainerFactory를 만들고 프로그래밍 방식으로 클러스터 구성을 설정했습니다. 코드는 다음과 같습니다.

@Configuration
public class TomcatConfig
{
    @Bean
    public EmbeddedServletContainerFactory servletContainerFactory()
    {
        return new TomcatEmbeddedServletContainerFactory()
        {
            @Override
            protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat)
            {
                configureCluster(tomcat);
                return super.getTomcatEmbeddedServletContainer(tomcat);
            }

            private void configureCluster(Tomcat tomcat)
            {
                // static membership cluster 

                SimpleTcpCluster cluster = new SimpleTcpCluster();
                cluster.setChannelStartOptions(3);
                {
                    DeltaManager manager = new DeltaManager();
                    manager.setNotifyListenersOnReplication(true);
                    cluster.setManagerTemplate(manager);
                }
                {
                    GroupChannel channel = new GroupChannel();
                    {
                        NioReceiver receiver = new NioReceiver();
                        receiver.setPort(localClusterMemberPort);
                        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 MessageDispatch15Interceptor());
                    {
                        StaticMembershipInterceptor membership =
                            new StaticMembershipInterceptor();
                        String[] memberSpecs = clusterMembers.split(",", -1);
                        for (String spec : memberSpecs)
                        {
                            ClusterMemberDesc memberDesc = new ClusterMemberDesc(spec);
                            StaticMember member = new StaticMember();
                            member.setHost(memberDesc.address);
                            member.setPort(memberDesc.port);
                            member.setDomain("MyWebAppDomain");
                            member.setUniqueId(memberDesc.uniqueId);
                            membership.addStaticMember(member);
                        }
                        channel.addInterceptor(membership);
                    }
                    cluster.setChannel(channel);
                }
                cluster.addValve(new ReplicationValve());
                cluster.addValve(new JvmRouteBinderValve());
                cluster.addClusterListener(new ClusterSessionListener());

                tomcat.getEngine().setCluster(cluster);
            }
        };
    }

    private static class ClusterMemberDesc
    {
        public String address;
        public int port;
        public String uniqueId;

        public ClusterMemberDesc(String spec) throws IllegalArgumentException
        {
            String[] values = spec.split(":", -1);
            if (values.length != 3)
                throw new IllegalArgumentException("clusterMembers element " +
                    "format must be address:port:uniqueIndex");
            address = values[0];
            port = Integer.parseInt(values[1]);
            int index = Integer.parseInt(values[2]);
            if ((index < 0) || (index > 255))
                throw new IllegalArgumentException("invalid unique index: must be >= 0 and < 256");
            uniqueId = "{";
            for (int i = 0; i < 16; i++, index++)
            {
                if (i != 0)
                    uniqueId += ',';
                uniqueId += index % 256;
            }
            uniqueId += '}';
        }
    };

    // This is for example. In fact these are read from application.properties
    private int localClusterMemberPort = 9991;
    private String clusterMembers = "111.222.333.444:9992:1";
}

그리고 다음 환경에서 코드를 테스트했습니다.

쿠키는 포트를 고려하지 않으므로 JSESSIONID가 포함 된 쿠키는 두 인스턴스간에 공유됩니다.

인스턴스가 시작되면 두 인스턴스에 대한 요청의 JSESSIONID 값이 같기 때문에 Tomcat 클러스터링이 작동하는 것 같습니다. 하지만 첫 번째 인스턴스를 사용하여 로그인 한 후 두 번째 인스턴스에 요청하면 두 번째 인스턴스는 HttpSession을 찾지 못합니다. 다음 메시지를 기록합니다.

w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.

분명히 HttpSession은 공유되지 않습니다. 그러나 두 번째 인스턴스가 새 세션을 만들면 첫 번째 인스턴스의 로그인 정보가 지워지고 로그인이 무효화됩니다.

여기서 무슨 일이 일어나고있는거야? 세션이 공유되었지만 HttpSession이 공유되지 않았습니까?

BTW, tomcat 세션 클러스터링을 사용하는 응용 프로그램의 web.xml에 태그를 지정해야한다는 것을 읽었습니다. 하지만 스프링 부트의 no-xml 환경을 지정하는 방법을 모르겠습니다. 이것이 문제의 원인입니까? 그렇다면이를 어떻게 지정할 수 있습니까?

필자는 Redis를 사용하여 클러스터링을 보여주는 몇 가지 문서를 검색하여 발견했습니다. 그러나 현재는 다른 구성 요소를 내 구성에 추가하고 싶지 않습니다. 제 구성에서 3 ~ 4 개의 노드가 최대 값입니다.

해결법

  1. ==============================

    1.핵심은 컨텍스트를 배포 가능하게 만들고 관리자를 설정하는 것이 었습니다.

    핵심은 컨텍스트를 배포 가능하게 만들고 관리자를 설정하는 것이 었습니다.

    다음과 같이 질문의 코드를 수정하면 세션 클러스터링이 작동합니다.

    @Configuration
    public class TomcatConfig
    {
        @Bean
        public EmbeddedServletContainerFactory servletContainerFactory()
        {
            TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory()
            {
                ...
            };
    
            factory.addContextCustomizers(new TomcatContextCustomizer()
            {
                @Override
                public void customize(Context context)
                {
                    context.setManager(new DeltaManager());
                    context.setDistributable(true);
                }
            });
    
            return factory;
        }
    
        ...
    } 
    

    Spring Boot 1.2.4에서는 context.setManager ()가 필요하지 않습니다. 그러나 1.3.0으로의 Spring Boot의 경우 context.setManager ()가 호출되지 않으면 클러스터링이 실패하고 다음 로그가 표시됩니다.

    2015-11-18 19:59:42.882  WARN 9764 --- [ost-startStop-1] o.a.catalina.ha.tcp.SimpleTcpCluster     : Manager [org.apache.catalina.session.StandardManager[]] does not implement ClusterManager, addition to cluster has been aborted.
    

    나는이 버전 의존성에 대해 다소 걱정하고있다. 그래서 나는이 문제를 열었습니다.

  2. ==============================

    2.Spring Boot 2.0.x에서는 WebServerFactoryCustomizer를 사용하여 클러스터링을 구성해야합니다.

    Spring Boot 2.0.x에서는 WebServerFactoryCustomizer를 사용하여 클러스터링을 구성해야합니다.

    @Component
    public class WebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
        @Override
        public void customize( final TomcatServletWebServerFactory factory ) {
            factory.addContextCustomizers( new TomcatClusterContextCustomizer() );
        }
    }
    
    public class TomcatClusterContextCustomizer implements TomcatContextCustomizer {
        @Override
        public void customize( final Context context ) {
            // Call method defined in the question text above, but pass Engine 
            // instead of Tomcat
            configureCluster( (Engine)context.getParent().getParent() );
        }
    }
    
  3. from https://stackoverflow.com/questions/33623311/how-to-setup-a-spring-boot-application-with-embedded-tomcat-session-clustering by cc-by-sa and MIT license