2015-11-10 1 views
2

Je souhaite configurer une application Spring Boot avec un clustering de session Tomcat intégré.Comment configurer une application Spring Boot avec un clustering de session Tomcat intégré?

Étant donné que Tomcat imbriqué n'a pas de fichier server.xml, j'ai créé un TomcatEmbeddedServletContainerFactory et paramétré par programme la configuration du cluster. Le code est le suivant:

@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"; 
} 

Et je l'ai testé le code avec l'environnement suivant:

  • simple PC Windows
  • 2 instances d'application de démarrage de printemps avec différents localClusterMemberPort et clusterMembers

Puisqu'un cookie ne prend pas en compte le port, le cookie qui contient JSESSIONID est partagé entre les deux instances. Lorsque les instances sont démarrées, le clustering Tomcat semble fonctionner, car la valeur JSESSIONID des demandes pour les 2 instances est la même. Mais quand je fais une demande à la deuxième instance après que je me suis connecté en utilisant la première instance, la deuxième instance ne trouve pas la HttpSession. Il enregistre le message suivant:

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

Apparemment, le HttpSession n'est pas partagé. Mais comme la deuxième instance crée une nouvelle session, les informations de connexion de la première instance sont effacées et la connexion est invalidée.

Que se passe-t-il ici? Session est partagée mais HttpSession n'est pas partagé? BTW, J'ai lu que <distributed /> tag doit être spécifié sur web.xml pour les applications à utiliser le clustering de session Tomcat. Mais je ne sais pas comment le spécifier avec l'environnement no-xml de Spring Boot. Est-ce la cause du problème? Alors comment peut-il être spécifié?

J'ai recherché et trouvé quelques documents qui montrent le clustering en utilisant Redis. Mais actuellement je ne veux pas ajouter une autre partie mobile à ma configuration. Dans ma configuration 3 ~ 4 nœuds sont le maximum.

+1

Vous pourriez utiliser Spring Session, claquer sur une annotation, et être fait. – chrylis

+0

@chrylis Pourriez-vous fournir une référence? J'ai parcouru quelques articles liés à la session de printemps, mais tous semblent concerner Redis, ce que je ne veux pas pour le moment inclure ma configuration. Et il semble que l'équipe de Spring a abandonné Redis embarqué car c'est trop problématique. – zeodtr

+0

Où essayez-vous de stocker l'état de la session? Des dossiers? SQL? – chrylis

Répondre

4

La clé était de rendre le contexte distribuable, et le gestionnaire de paramètres.

Lorsque j'ai modifié le code de la question comme suit, le clustering de session a fonctionné.

@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; 
    } 

    ... 
} 

Pour Spring Boot 1.2.4, context.setManager() n'est pas nécessaire. Mais pour Spring Boot à 1.3.0, si context.setManager() n'est pas appelé, le clusterage échoue et le journal suivant est affiché.

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. 

Je suis un peu inquiet au sujet de cette dépendance de version. Donc, je opened an issue pour cela.

+0

... si vous définissez le gestionnaire dans context.xml, cela n'empêche-t-il pas les sessions d'enregistrement et la réplication de session? – Amalgovinus