Een gids voor Infinispan in Java

1. Overzicht

In deze handleiding leren we over Infinispan, een sleutel / waarde-gegevensopslag in het geheugen die wordt geleverd met een robuustere set functies dan andere tools van dezelfde niche.

Om te begrijpen hoe het werkt, bouwen we een eenvoudig project met de meest voorkomende functies en kijken we hoe deze kunnen worden gebruikt.

2. Projectconfiguratie

Om het op deze manier te kunnen gebruiken, moeten we de afhankelijkheid ervan toevoegen aan ons pom.xml.

De nieuwste versie is te vinden in de Maven Central-repository:

 org.infinispan infinispan-core 9.1.5.Finale 

Alle benodigde onderliggende infrastructuur wordt voortaan programmatisch afgehandeld.

3. CacheManager Opstelling

De CacheManager is de basis van de meeste functies die we zullen gebruiken. Het fungeert als een container voor alle gedeclareerde caches, controleert hun levenscyclus en is verantwoordelijk voor de globale configuratie.

Infinispan wordt geleverd met een heel gemakkelijke manier om het CacheManager:

openbare DefaultCacheManager cacheManager () {retourneer nieuwe DefaultCacheManager (); }

Nu kunnen we er onze caches mee bouwen.

4. Caches instellen

Een cache wordt gedefinieerd door een naam en een configuratie. De benodigde configuratie kan worden gebouwd met behulp van de class ConfigurationBuilder, al beschikbaar in ons klassenpad.

Om onze caches te testen, zullen we een eenvoudige methode bouwen die een zware zoekopdracht simuleert:

openbare klasse HelloWorldRepository {openbare String getHelloWorld () {probeer {System.out.println ("uitvoeren van een zware query"); Thread.sleep (1000); } catch (InterruptedException e) {// ... e.printStackTrace (); } retourneer "Hallo wereld!"; }}

Om te kunnen controleren op wijzigingen in onze caches, biedt Infinispan ook een eenvoudige annotatie @Luisteraar.

Bij het definiëren van onze cache kunnen we een object doorgeven dat geïnteresseerd is in een gebeurtenis die erin plaatsvindt, en Infinispan zal dit melden bij het afhandelen van de cache:

@Listener openbare klasse CacheListener {@CacheEntryCreated openbare ongeldige entryCreated (CacheEntryCreatedEvent-gebeurtenis) {this.printLog ("Sleutel toevoegen" + event.getKey () + "'naar cache", gebeurtenis); } @CacheEntryExpired openbare ongeldige entryExpired (CacheEntryExpiredEvent-gebeurtenis) {this.printLog ("Verlopen sleutel '" + event.getKey () + "' uit cache", gebeurtenis); } @CacheEntryVisited openbare ongeldige entryVisited (CacheEntryVisitedEvent-evenement) {this.printLog ("Sleutel '" + event.getKey () + "' is bezocht", evenement); } @CacheEntryActivated openbare ongeldige entryActivated (CacheEntryActivatedEvent-gebeurtenis) {this.printLog ("Activerende sleutel '" + event.getKey () + "' in cache", gebeurtenis); } @CacheEntryPassivated openbare ongeldige entryPassivated (CacheEntryPassivatedEvent-gebeurtenis) {this.printLog ("Passiverende sleutel '" + event.getKey () + "' uit cache", gebeurtenis); } @CacheEntryLoaded openbare ongeldige entryLoaded (CacheEntryLoadedEvent-gebeurtenis) {this.printLog ("Laadsleutel '" + event.getKey () + "' naar cache", gebeurtenis); } @CacheEntriesEvicted openbare ongeldige inzendingenEvicted (CacheEntriesEvictedEvent-gebeurtenis) {StringBuilder builder = nieuwe StringBuilder (); event.getEntries (). forEach ((sleutel, waarde) -> builder.append (sleutel) .append (",")); System.out.println ("De volgende items uit de cache verwijderen:" + builder.toString ()); } private void printLog (String log, CacheEntryEvent event) {if (! event.isPre ()) {System.out.println (log); }}}

Voordat we ons bericht afdrukken, controleren we of de gebeurtenis die op de hoogte wordt gebracht al heeft plaatsgevonden, omdat Infinispan voor sommige soorten gebeurtenissen twee meldingen verstuurt: één ervoor en één direct nadat deze is verwerkt.

Laten we nu een methode bouwen om het maken van de cache voor ons af te handelen:

private Cache buildCache (String cacheName, DefaultCacheManager cacheManager, CacheListener listener, configuratieconfiguratie) {cacheManager.defineConfiguration (cachenaam, configuratie); Cachecache = cacheManager.getCache (cachenaam); cache.addListener (luisteraar); terugkeer cache; }

Merk op hoe we een configuratie doorgeven aan CacheManager, en gebruik dan hetzelfde cacheName om het object te krijgen dat overeenkomt met de gewenste cache. Merk ook op hoe we de luisteraar informeren over het cache-object zelf.

We zullen nu vijf verschillende cacheconfiguraties bekijken, en we zullen zien hoe we ze kunnen instellen en er optimaal gebruik van kunnen maken.

4.1. Eenvoudige cache

Met behulp van onze methode kan het eenvoudigste type cache op één regel worden gedefinieerd buildCache:

openbare cache simpleHelloWorldCache (DefaultCacheManager cacheManager, CacheListener listener) {return this.buildCache (SIMPLE_HELLO_WORLD_CACHE, cacheManager, luisteraar, nieuwe ConfigurationBuilder (). build ()); }

We kunnen nu een Onderhoud:

openbare String findSimpleHelloWorld () {String cacheKey = "simple-hallo"; retourneer simpleHelloWorldCache .computeIfAbsent (cacheKey, k -> repository.getHelloWorld ()); }

Merk op hoe we de cache gebruiken, en controleer eerst of het gewenste item al in de cache is opgeslagen. Als dat niet het geval is, moeten we onze bellen Opslagplaats en vervolgens cachen.

Laten we een eenvoudige methode aan onze tests toevoegen om onze methoden te timen:

beschermde lange tijdDit (leverancier leverancier) {long millis = System.currentTimeMillis (); leverancier.get (); retourneer System.currentTimeMillis () - millis; }

Door het te testen, kunnen we de tijd controleren tussen het uitvoeren van twee methodeaanroepen:

@Test openbare leegte whenGetIsCalledTwoTimes_thenTheSecondShouldHitTheCache () {assertThat (timeThis (() -> helloWorldService.findSimpleHelloWorld ())) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findSimpleHelloWorld ())) .isLessThan (100); }

4.2. Vervalcache

We kunnen een cache definiëren waarin alle items een levensduur hebben, met andere woorden, elementen worden na een bepaalde periode uit de cache verwijderd. De configuratie is vrij eenvoudig:

private Configuration expiringConfiguration () {retourneer nieuwe ConfigurationBuilder (). expiration () .lifespan (1, TimeUnit.SECONDS) .build (); }

Nu bouwen we onze cache met behulp van de bovenstaande configuratie:

public Cache expiringHelloWorldCache (DefaultCacheManager cacheManager, CacheListener listener) {return this.buildCache (EXPIRING_HELLO_WORLD_CACHE, cacheManager, listener, expiringConfiguration ()); }

En tot slot, gebruik het op een vergelijkbare manier uit onze eenvoudige cache hierboven:

openbare String findSimpleHelloWorldInExpiringCache () {String cacheKey = "simple-hallo"; String helloWorld = expiringHelloWorldCache.get (cacheKey); if (helloWorld == null) {helloWorld = repository.getHelloWorld (); expiringHelloWorldCache.put (cacheKey, helloWorld); } terugkeer helloWorld; }

Laten we onze tijden opnieuw testen:

@Test openbare leegte whenGetIsCalledTwoTimesQuickly_thenTheSecondShouldHitTheCache () {assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isLessThan (100); }

Als we het uitvoeren, zien we dat de cache snel achter elkaar raakt. Om aan te tonen dat de vervaldatum relatief is ten opzichte van de invoer leggen tijd, laten we het forceren in onze inzending:

@Test openbare leegte whenGetIsCalledTwiceSparsely_thenNeitherHitsTheCache () gooit InterruptedException {assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isGreaterThanOrEqualTo (1000); Thread.sleep (1100); assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isGreaterThanOrEqualTo (1000); }

Merk na het uitvoeren van de test op hoe na de opgegeven tijd onze invoer uit de cache was verlopen. We kunnen dit bevestigen door naar de afgedrukte logregels van onze luisteraar te kijken:

Een zware zoekopdracht uitvoeren Sleutel 'simple-hallo' aan de cache toevoegen Sleutel 'simple-hallo' uit de cache laten vervallen Een zware zoekopdracht uitvoeren Sleutel 'simple-hallo' aan de cache toevoegen

Houd er rekening mee dat het item is verlopen wanneer we er toegang toe proberen te krijgen. Infinispan controleert binnen twee ogenblikken op een verlopen item: wanneer we er toegang toe proberen te krijgen of wanneer de reaper-thread de cache scant.

We kunnen expiration zelfs in caches gebruiken zonder dit in hun hoofdconfiguratie. De methode leggen accepteert meer argumenten:

simpleHelloWorldCache.put (cacheKey, helloWorld, 10, TimeUnit.SECONDS);

Of, in plaats van een vaste levensduur, kunnen we onze inzending een maximum geven idleTime:

simpleHelloWorldCache.put (cacheKey, helloWorld, -1, TimeUnit.SECONDS, 10, TimeUnit.SECONDS);

Als je -1 gebruikt voor het attribuut levensduur, zal de cache er niet door verlopen, maar als we het combineren met 10 seconden idleTime, vertellen we Infinispan om dit item te laten vervallen, tenzij het in dit tijdsbestek wordt bezocht.

4.3. Cache-uitzetting

In Infinispan kunnen we het aantal vermeldingen in een bepaalde cache beperken met de uitzetting configuratie:

private Configuration evictingConfiguration () {retourneer nieuwe ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .size (1) .build (); }

In dit voorbeeld beperken we het maximale aantal vermeldingen in deze cache tot één, wat betekent dat als we proberen een andere in te voeren, deze uit onze cache wordt verwijderd.

Nogmaals, de methode is vergelijkbaar met de hier al gepresenteerde:

openbare String findEvictingHelloWorld (String-sleutel) {String-waarde = evictingHelloWorldCache.get (sleutel); if (waarde == null) {waarde = repository.getHelloWorld (); evictingHelloWorldCache.put (sleutel, waarde); } winstwaarde; }

Laten we onze test bouwen:

@Test openbare leegte whenTwoAreAdded_thenFirstShouldntBeAvailable () {assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("key 2"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); }

Als we de test uitvoeren, kunnen we ons listenerlogboek met activiteiten bekijken:

Een zware zoekopdracht uitvoeren Sleutel 'sleutel 1' aan cache toevoegen Een zware zoekopdracht uitvoeren Volgende items uit cache verwijderen: sleutel 1, sleutel 'sleutel 2' aan cache toevoegen Een zware zoekopdracht uitvoeren Volgende items uit cache verwijderen: sleutel 2, sleutel 'sleutel toevoegen 1 'om te cachen

Controleer hoe de eerste sleutel automatisch uit de cache werd verwijderd toen we de tweede installeerden, en vervolgens de tweede ook werd verwijderd om weer ruimte te maken voor onze eerste sleutel.

4.4. Passivering Cache

De cache passivering is een van de krachtige eigenschappen van Infinispan. Door passivering en ontruiming te combineren, kunnen we een cache creëren die niet veel geheugen in beslag neemt, zonder informatie te verliezen.

Laten we eens kijken naar een passiveringsconfiguratie:

private Configuration passivatingConfiguration () {retourneer nieuwe ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .size (1) .persistence () .passivation (true) // activering van passivering .addSingleFileStore () // in een enkel bestand .purgeOnStartup (true) // maak het bestand schoon bij het opstarten .location (System.getProperty ("java.io.tmpdir")) .build (); }

We dwingen opnieuw slechts één item in ons cachegeheugen, maar vertellen Infinispan om de resterende items te passiveren in plaats van ze gewoon te verwijderen.

Laten we eens kijken wat er gebeurt als we proberen meer dan één item in te vullen:

openbare String findPassivatingHelloWorld (String key) {return passivatingHelloWorldCache.computeIfAbsent (key, k -> repository.getHelloWorld ()); }

Laten we onze test bouwen en uitvoeren:

@Test openbare leegte whenTwoAreAdded_thenTheFirstShouldBeAvailable () {assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("key 2"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("key 1"))) .isLessThan (100); }

Laten we nu eens kijken naar onze luisteraaractiviteiten:

Uitvoeren van een zware zoekopdracht Sleutel 'sleutel 1' toevoegen aan cache Uitvoeren van zware zoekopdracht Sleutel 'sleutel 1' passiveren uit cache Volgende items uit cache verwijderen: sleutel 1, Sleutel 'sleutel 2' toevoegen aan cache Passiveren sleutel 'sleutel 2' uit cache Ontruimen volgende items uit cache: sleutel 2, Laad sleutel 'sleutel 1' naar cache Activering sleutel 'sleutel 1' op cache Sleutel 'sleutel 1' is bezocht

Merk op hoeveel stappen er nodig waren om onze cache met slechts één invoer te behouden. Let ook op de volgorde van de stappen: passiveren, uitzetten en vervolgens laden, gevolgd door activering. Laten we eens kijken wat die stappen betekenen:

  • Passivering - onze invoer wordt op een andere plaats opgeslagen, weg van de hoofdopslag van Infinispan (in dit geval het geheugen)
  • Uitzetting - de vermelding wordt verwijderd om geheugen vrij te maken en om het geconfigureerde maximale aantal vermeldingen in de cache te behouden
  • Bezig met laden - wanneer we proberen onze gepassiveerde invoer te bereiken, controleert Infinispan de opgeslagen inhoud en laadt de invoer opnieuw in het geheugen
  • Activering - de vermelding is nu weer toegankelijk in Infinispan

4.5. Transactionele cache

Infinispan wordt geleverd met een krachtige transactiecontrole. Net als de database-tegenhanger, is het handig om de integriteit te behouden terwijl meer dan één thread probeert hetzelfde item te schrijven.

Laten we eens kijken hoe we een cache met transactiemogelijkheden kunnen definiëren:

private Configuration transactionalConfiguration () {retourneer nieuwe ConfigurationBuilder () .transaction (). transactionMode (TransactionMode.TRANSACTIONAL) .lockingMode (LockingMode.PESSIMISTIC) .build (); }

Om het mogelijk te maken om het te testen, laten we twee methoden bouwen: een die de transactie snel voltooit en een die even duurt:

openbaar geheel getal getQuickHowManyVisits () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); Geheel getal howManyVisits = transactionalCache.get (KEY); howManyVisits ++; System.out.println ("Ik zal proberen HowManyVisits in te stellen op" + howManyVisits); StopWatch watch = nieuwe StopWatch (); watch.start (); transactionalCache.put (KEY, howManyVisits); watch.stop (); System.out.println ("Ik kon HowManyVisits instellen op" + howManyVisits + "na wachten" + watch.getTotalTimeSeconds () + "seconden"); tm.commit (); terug howManyVisits; }
openbare ongeldige startBackgroundBatch () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); transactionalCache.put (KEY, 1000); System.out.println ("HowManyVisits zou nu 1000 moeten zijn," + "maar we houden de transactie vast"); Thread.sleep (1000L); tm.rollback (); System.out.println ("De langzame batch werd teruggedraaid"); }

Laten we nu een test maken die beide methoden uitvoert en kijken hoe Infinispan zich zal gedragen:

@Test openbare leegte whenLockingAnEntry_thenItShouldBeInaccessible () gooit InterruptedException {Runnable backGroundJob = () -> transactionalService.startBackgroundBatch (); Thread backgroundThread = nieuwe Thread (backGroundJob); transactionalService.getQuickHowManyVisits (); backgroundThread.start (); Thread.sleep (100); // laten we wachten onze thread opwarmen assertThat (timeThis (() -> transactionalService.getQuickHowManyVisits ())) .isGreaterThan (500) .isLessThan (1000); }

Als we het uitvoeren, zien we de volgende activiteiten opnieuw in onze console:

Sleutel 'sleutel' toevoegen aan cache Sleutel 'sleutel' is bezocht Ik probeer HowManyVisits in te stellen op 1 Ik was in staat om HowManyVisits in te stellen op 1 na 0,001 seconden wachten. HowManyVisits zou nu 1000 moeten zijn, maar we houden de transactie vast Sleutel 'sleutel' is bezocht Ik zal proberen om HowManyVisits in te stellen op 2 Ik was in staat om HowManyVisits in te stellen op 2 na 0.902 seconden wachten. De langzame batch leed aan een rollback

Controleer de tijd op de hoofdthread, wachtend op het einde van de transactie die is gemaakt met de langzame methode.

5. Conclusie

In dit artikel hebben we gezien wat Infinispan is, en de toonaangevende functies en mogelijkheden als cache binnen een applicatie.

Zoals altijd is de code te vinden op Github.