Siedem kroków do ehcache w AmazonWebServices

Ostatnio w pracy mierzę się ze zmianą sposobu komunikacji instancji cache‚y aplikacyjnych w środowisku klastrowym. Do tej pory używaliśmy biblioteki ehache w projekcie, która domyślnie wysyła informacje do członków klastra za pomocą UDP-multicast. Niestety multicast nie jest dostępny w architekturze Amazon Web Services, na którą chcemy się zmigrować. W FAQ dostępnym na stronach AWS, w sekcji Routing & Topology znajduję się pytanie:

Q. Does Amazon VPC support multicast or broadcast? No.

Szukając obszerniejszego wyjaśnienia, przejrzałem oczywiście stackoverflow.com oraz AWS Developers Forum – niestety bez skutku. Dopiero na blogu Sam’a Mitchell’a znalazłem odpowiedź, która zaspokoiła moją ciekawość. Otóż UDP-multicast nie jest wspierany w rozwiązaniach cloud’owych z powodu bezpieczeństwa, uniemożliwiając komunikowanie się pomiedzy serwerami dwórch różnych klientów.

W miedzyczasie natrafiłem na stronę: http://www.cloudar.be/awsblog/multicast-on-aws, gdzie opisany jest sposób jak skorzystać z pseudo-multicast’u na warstwie sieciowej, używając do tego n2n. Ta informacja miała stać się ostatnią deską ratunku, gdybym nie znalazł lepszego rozwiązania. Ale znalazłem i skonfigurowałem!

Użycie ehcache w środowisku bez multicast

Ehcache udostępnia cztery sposoby na osiągnięcie komunikacji w klastrze, w którym niemożliwe jest skorzystanie z multicast:
– RMI,
– JGroups,
– JMS,
– architektura master-slave dla cache’y.

JGroups w porównianiu do pozostałych sposobow komunikacji, jest szybki i dobrze skalowalny, chociaż zużywa nieco więcej zasobów pamięciowych. Dodatkowo jest natywnie dostarczony z JBossem, którego używamy jako kontenera JEE. Przy użyciu tej metody, wykorzystywany jest TCP-unicast oraz manualne odkrywanie członków klastr’a.

Sam process w JBoss w wersji 5.1.0 składa się siedmiu kroków:

    1. Należy odnaleźć, w jakiej wersji znajduje się biblioteka jgroups na serwerze. W przypadku JBoss-5.1.0 używana była wersja 2.6.10, która zdeterminowała przejście na nowszą wersję ehcache-core-2.3.0 w projekcie oraz ehcache-jgroupsreplication-1.4.0.
      Teraz można dokonać zmian zależnościach projektu, pamietając by nie dotarczać wspomnianych bibliotek razem ze zbudowaną paczką projektu, gdyż to może potem doprowadzić do problemów z classload’erami serwera (ta sama klasa załadowana przez classloader’y rożnych aplikacj).W project.xml dodajemy/zmieniamy wpis na:

      <dependency>
          <groupId>net.sf.ehcache</groupId>
          <artifactId>ehcache-core</artifactId>
          <version>2.3.0</version>
          <url>http://ehcache.org/</url>
      </dependency>
      
    2. Biblioteki ehcache-core-2.3.0.jar oraz ehcache-jgroupsreplication-1.4.0.jar należy wgrać do katalogu lib serwera,
    3. Należy zmienić zawartość pliku ehcache.xml na poniższą:
      <?xml version="1.1"?>
      <ehcache>
          <diskStore path="${java.io.tmpdir}"/>
      
          <cacheManagerPeerProviderFactory        class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" 
      properties="file=${ehcache-jgroups-file-location}" />
      
          <defaultCache 
              maxElementsInMemory="1000"
              eternal="false"
              timeToIdleSeconds="120"
              timeToLiveSeconds="120"
              overflowToDisk="false"
              diskPersistent="false"
              diskExpiryThreadIntervalSeconds="120"
              memoryStoreEvictionPolicy="LRU">
      
             <cacheEventListenerFactory
                 class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
                 properties="replicateAsynchronously=true, replicatePuts=true,
                     replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true" />
      
             <bootstrapCacheLoaderFactory
                 class="net.sf.ehcache.distribution.jgroups.JGroupsBootstrapCacheLoaderFactory"
                 properties="bootstrapAsynchronously=false" />
          </defaultCache>
      
          <cache name="sample_cache"
              maxElementsInMemory="1000"
              eternal="false"
              timeToIdleSeconds="120"
              timeToLiveSeconds="120"
              overflowToDisk="false"
              diskPersistent="false"
              diskExpiryThreadIntervalSeconds="120"
              memoryStoreEvictionPolicy="LRU"
              <cacheEventListenerFactory
                        class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
                        properties="replicateAsynchronously=true, replicatePuts=true,
                        replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true" />
                        
              <bootstrapCacheLoaderFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsBootstrapCacheLoaderFactory"
                properties="bootstrapAsynchronously=false" />
              
              />
      </ehcache>
      

      W tym miejscu warto pamiętać, że w przypadku nie odnalezienia pliku ehcache.xml zostanie użyta domyślna wersja ze źrodeł ehcache.

    4. Należy przekazać w argumentach uruchomienia procesu JBoss lokalizacji pliku z konfiguracja JGroups dla ehcache (${ehcache-jgroups-file-location}):
      JAVA_OPTS="${JAVA_OPTS} -Dehcache-jgroups-file-location=/path/to/ehcache-jgroups.xml"
      
    5. Do pliku /path/to/ehcache-jgroups.xml dodać konfigurację jako poniżej:
      <?xml version="1.0" encoding="UTF-8"?>
      <config>
         <TCP bind_addr="hostA" bind_port="26050" start_port="26050" />
         <TCPPING timeout="3000"
             initial_hosts="hostA[26050],hostB[26050]"
             port_range="3"
             num_initial_members="2"/>
         <VERIFY_SUSPECT timeout="1500" />
         <pbcast.NAKACK use_mcast_xmit="false" gc_lag="100"
           retransmit_timeout="300,600,1200,2400,4800"
           discard_delivered_msgs="true"/>
         <pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000" max_bytes="400000"/>
         <pbcast.GMS print_local_addr="true" join_timeout="5000" shun="false" view_bundling="true"/>
      </config>

      Gdzie:
      TCP(bind_addr) – adres aktualnego hosta,
      TCP(bind_port), TCP(start_port) – port początkowy, od ktorego kolejno wolne porty beda przypisywane do instancji cache’a,
      TCPPING(initial_hosts) – lista hostów, które uformuja klaster cache’a (musi zawierac sie w argumencie wywołania procesu JBoss jgroups.tcpping.initial_hosts),
      TCPPING(port_range) – zakres portów do skanowania u członkow klastra (w powyzszym przykładzie będą to: 26050,26051,26052,26053),
      TCPPING(num_initial_members) – minimalna ilość członków do uformowania klastra.

    6. Przekazanie w argumentach uruchomienia procesu JBoss zmiennej jgroups.tcpping.initial_hosts wskazującej hosty do konfiguracji JGroups:
      JAVA_OPTS="${JAVA_OPTS} -Djgroups.tcpping.initial_hosts=hostA[26050],hostB[26050]"
      

      W tym argumencie powinny znaleźć się również namiary na inne usługi używające jgroups na serwerze, na przykład replikacja samych węzłów.

    7. Warto dodać również logowanie:
         <category name="net.sf.ehcache"  additivity="false">
           <priority value="debug" />
           <appender-ref ref="FILE" />
         </category>
         <category name="net.sf.ehcache.config"  additivity="false">
           <priority value="debug" />
           <appender-ref ref="FILE" />
         </category>
         <category name="net.sf.ehcache.distribution"  additivity="false">
           <priority value="debug" />
           <appender-ref ref="FILE" />
         </category>

    Teraz wystarczy uruchomić serwer i sprawdzić.