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:
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:
- 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>
- Biblioteki ehcache-core-2.3.0.jar oraz ehcache-jgroupsreplication-1.4.0.jar należy wgrać do katalogu lib serwera,
- 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.
- 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"
- 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. - 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.
- 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ć.