En guide til Infinispan i Java

1. Oversigt

I denne vejledning lærer vi om Infinispan, en in-memory-nøgle / værdidatalager, der leveres med et mere robust sæt funktioner end andre værktøjer i samme niche.

For at forstå, hvordan det fungerer, bygger vi et simpelt projekt, der viser de mest almindelige funktioner og kontrollerer, hvordan de kan bruges.

2. Opsætning af projekt

For at kunne bruge det på denne måde bliver vi nødt til at tilføje dets afhængighed i vores pom.xml.

Den seneste version kan findes i Maven Central repository:

 org.infinispan infinispan-core 9.1.5.Final 

Fra nu af håndteres al nødvendig underliggende infrastruktur programmatisk.

3. CacheManager Opsætning

Det CacheManager er grundlaget for de fleste funktioner, som vi bruger. Det fungerer som en container til alle deklarerede cacher, kontrollerer deres livscyklus og er ansvarlig for den globale konfiguration.

Infinispan leveres med en rigtig nem måde at opbygge CacheManager:

offentlig DefaultCacheManager cacheManager () {returner ny DefaultCacheManager (); }

Nu er vi i stand til at opbygge vores cacher med det.

4. Cache-opsætning

En cache defineres af et navn og en konfiguration. Den nødvendige konfiguration kan bygges ved hjælp af klassen ConfigurationBuilder, allerede tilgængelig på vores klassesti.

For at teste vores cacher bygger vi en enkel metode, der simulerer nogle tunge forespørgsler:

offentlig klasse HelloWorldRepository {public String getHelloWorld () {prøv {System.out.println ("Udfører nogle tunge forespørgsler"); Tråd. Søvn (1000); } fange (InterruptedException e) {// ... e.printStackTrace (); } returner "Hello World!"; }}

For at kunne kontrollere ændringer i vores cache giver Infinispan også en simpel kommentar @Lytter.

Når vi definerer vores cache, kan vi videregive et objekt, der er interesseret i enhver begivenhed inde i det, og Infinispan giver besked om det, når cache håndteres:

@Listener public class CacheListener {@CacheEntryCreated public void entryCreated (CacheEntryCreatedEvent event) {this.printLog ("Adding key '" + event.getKey () + "' to cache", event); } @CacheEntryExpired offentlig ugyldig postExpired (CacheEntryExpiredEvent begivenhed) {this.printLog ("Udløbsnøgle" "+ event.getKey () +" 'fra cache ", begivenhed); } @CacheEntryVisited offentlig ugyldig entryVisited (CacheEntryVisitedEvent begivenhed) {this.printLog ("Key" "+ event.getKey () +" 'blev besøgt ", begivenhed); } @CacheEntryActivated public void entryActivated (CacheEntryActivatedEvent event) {this.printLog ("Aktiveringstast '" + event.getKey () + "' på cache", begivenhed); } @CacheEntryPassivated public void entryPassivated (CacheEntryPassivatedEvent event) {this.printLog ("Passiverende nøgle '" + event.getKey () + "' fra cache", begivenhed); } @CacheEntryLoaded offentlig ugyldig entryLoaded (CacheEntryLoadedEvent begivenhed) {this.printLog ("Indlæser nøgle '" + event.getKey () + "' til cache", begivenhed); } @CacheEntriesEvicted offentlige ugyldige posterEvicted (CacheEntriesEvictedEvent begivenhed) {StringBuilder builder = ny StringBuilder (); event.getEntries (). forEach ((key, value) -> builder.append (key) .append (",")); System.out.println ("Fjernelse af følgende poster fra cache:" + builder.toString ()); } privat ugyldigt printLog (String log, CacheEntryEvent event) {if (! event.isPre ()) {System.out.println (log); }}}

Før vi udskriver vores meddelelse, kontrollerer vi, om den begivenhed, der bliver underrettet, allerede er sket, fordi Infinispan for nogle begivenhedstyper sender to underretninger: en før og en lige efter at den er behandlet.

Lad os nu bygge en metode til at håndtere cache-oprettelsen for os:

privat Cache buildCache (String cacheName, DefaultCacheManager cacheManager, CacheListener lytter, konfigurationskonfiguration) {cacheManager.defineConfiguration (cacheName, konfiguration); Cache cache = cacheManager.getCache (cacheName); cache.addListener (lytter); retur cache; }

Læg mærke til, hvordan vi sender en konfiguration til CacheManager, og brug derefter det samme cacheName for at få objektet svarende til den ønskede cache. Bemærk også, hvordan vi informerer lytteren om selve cache-objektet.

Vi kontrollerer nu fem forskellige cache-konfigurationer, og vi ser, hvordan vi kan konfigurere dem og udnytte dem bedst.

4.1. Enkel cache

Den enkleste type cache kan defineres i en linje ved hjælp af vores metode buildCache:

offentlig cache simpleHelloWorldCache (DefaultCacheManager cacheManager, CacheListener lytter) {returner this.buildCache (SIMPLE_HELLO_WORLD_CACHE, cacheManager, lytter, ny ConfigurationBuilder (). build ()); }

Vi kan nu bygge en Service:

public String findSimpleHelloWorld () {String cacheKey = "simple-hej"; returner simpleHelloWorldCache .computeIfAbsent (cacheKey, k -> repository.getHelloWorld ()); }

Bemærk, hvordan vi bruger cachen, og kontroller først, om den ønskede post allerede er cache. Hvis det ikke er tilfældet, skal vi ringe til vores Datalager og cache det derefter.

Lad os tilføje en enkel metode i vores test for at tidsbestemme vores metoder:

beskyttet lang tidThis (leverandørleverandør) {long millis = System.currentTimeMillis (); leverandør.get (); returnere System.currentTimeMillis () - millis; }

Når vi tester det, kan vi kontrollere tiden mellem at udføre to metodeopkald:

@Test offentlig ugyldig, nårGetIsCalledTwoTimes_thenTheSecondShouldHitTheCache () {assertThat (timeThis (() -> helloWorldService.findSimpleHelloWorld ())) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findSimpleHelloWorld ())) .isLessThan (100); }

4.2. Udløbscache

Vi kan definere en cache, hvor alle poster har en levetid, med andre ord, elementer fjernes fra cachen efter en given periode. Konfigurationen er ret enkel:

privat konfiguration udløberConfiguration () {returner ny ConfigurationBuilder (). udløb () .lifespan (1, TimeUnit.SECONDS) .build (); }

Nu bygger vi vores cache ved hjælp af ovenstående konfiguration:

offentlig Cache, der udløberHelloWorldCache (StandardCacheManager cacheManager, CacheListener-lytter) {returner this.buildCache (EXPIRING_HELLO_WORLD_CACHE, cacheManager, lytter, expiringConfiguration ()); }

Og til sidst skal du bruge den i en lignende metode fra vores enkle cache ovenfor:

public String findSimpleHelloWorldInExpiringCache () {String cacheKey = "simple-hej"; String helloWorld = expiringHelloWorldCache.get (cacheKey); hvis (helloWorld == null) {helloWorld = repository.getHelloWorld (); udløberHelloWorldCache.put (cacheKey, helloWorld); } returner helloWorld; }

Lad os teste vores tider igen:

@Test offentligt ugyldigt nårGetIsCalledTwoTimesQuickly_thenTheSecondShouldHitTheCache () {assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isLessThan (100); }

Når vi kører det, ser vi, at cachen hurtigt rammer. At fremvise, at udløbet er relativt til dets indtastning sætte tid, lad os tvinge det i vores indrejse:

@Test offentlig ugyldigt nårGetIsCalledTwiceSparsely_thenNeitherHitsTheCache () kaster InterruptedException {assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ()) .isGreaterThanOrEqualTo (1000); Tråd. Søvn (1100); assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isGreaterThanOrEqualTo (1000); }

Når du har kørt testen, skal du bemærke, hvordan vores post efter den givne tid var udløbet fra cachen. Vi kan bekræfte dette ved at se på de trykte loglinjer fra vores lytter:

Udfører nogle tunge forespørgsler Tilføjelse af nøgle 'simple-hej' til cache Udløbende nøgle 'simple-hej' fra cache Udførelse af nogle tunge forespørgsler Tilføjelse af nøgle 'simple-hej' til cache

Bemærk, at posten er udløbet, når vi prøver at få adgang til den. Infinispan kontrollerer for en udløbet post i to øjeblikke: når vi prøver at få adgang til den, eller når reaper-tråden scanner cachen.

Vi kan bruge udløb selv i cacher uden det i deres hovedkonfiguration. Metoden sætte accepterer flere argumenter:

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

Eller i stedet for en fast levetid kan vi give vores adgang maksimalt spildtid:

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

Ved hjælp af -1 til attributten levetid, vil cachen ikke udløbe af den, men når vi kombinerer den med 10 sekunders spildtid, vi beder Infinispan om at udløbe denne post, medmindre den besøges inden for denne tidsramme.

4.3. Cache-udsættelse

I Infinispan kan vi begrænse antallet af poster i en given cache med udsættelse konfiguration:

privat konfiguration evictingConfiguration () {returner ny ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .størrelse (1) .build (); }

I dette eksempel begrænser vi de maksimale poster i denne cache til en, hvilket betyder, at hvis vi prøver at indtaste en anden, vil den blive fjernet fra vores cache.

Igen svarer metoden til den allerede præsenterede her:

public String findEvictingHelloWorld (String key) {String value = evictingHelloWorldCache.get (key); hvis (værdi == null) {værdi = repository.getHelloWorld (); evictingHelloWorldCache.put (nøgle, værdi); } returværdi }

Lad os bygge vores test:

@Test offentlig ugyldig nårTwoAreAdded_thenFirstShouldntBeAvailable () {assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("key 2"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("nøgle 1"))) .isGreaterThanOrEqualTo (1000); }

Når vi kører testen, kan vi se på vores lytterlogbog over aktiviteter:

Udfører noget tung forespørgsel Tilføjer nøgle 'nøgle 1' til cache Udfører nogle tunge forespørgsler Fjernelse af følgende poster fra cache: nøgle 1, Tilføj nøgle 'nøgle 2' til cache Udførelse af nogle tunge forespørgsler Fjernelse af følgende poster fra cache: nøgle 2, Tilføj nøgle 'nøgle 1 'til cache

Kontroller, hvordan den første nøgle automatisk blev fjernet fra cachen, da vi indsatte den anden, og derefter blev den anden fjernet også for at give plads til vores første nøgle igen.

4.4. Passiveringscache

Det cache passivering er en af ​​de kraftfulde funktioner i Infinispan. Ved at kombinere passivering og udsættelse kan vi oprette en cache, der ikke optager meget hukommelse uden at miste information.

Lad os se på en passiveringskonfiguration:

privat konfiguration passivatingConfiguration () {returner ny ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .størrelse (1) .persistence () .passivation (true) // aktivering af passivering .addSingleFileStore () // i en enkelt fil .purgeOnStartup (true) // rens filen ved opstart. placering (System.getProperty ("java.io.tmpdir")) .build (); }

Vi tvinger igen kun en post i vores cachehukommelse, men beder Infinispan om at passivere de resterende poster i stedet for bare at fjerne dem.

Lad os se, hvad der sker, når vi prøver at udfylde mere end en post:

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

Lad os bygge vores test og køre den:

@Test offentlig ugyldig nårTwoAreAdded_thenTheFirstShouldBeAvailable () {assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("tast 2"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("key 1"))) .isLessThan (100); }

Lad os nu se på vores lytteraktiviteter:

Udførelse af nogle tunge forespørgsler Tilføjelse af nøgle 'nøgle 1' til cache Udførelse af tunge forespørgsel Passiverende nøgle 'nøgle 1' fra cache Tømmer følgende poster fra cache: nøgle 1, Tilføj nøgle 'nøgle 2' til cache Passiverende nøgle 'nøgle 2' fra cache Evicting følgende poster fra cache: tast 2, Indlæsningstast 'tast 1' til cache Aktivering af tast 'tast 1' på cache Nøgle 'tast 1' blev besøgt

Bemærk, hvor mange trin der var taget for at beholde vores cache med kun en post. Bemærk også rækkefølgen af ​​trin - passivering, udsættelse og derefter indlæsning efterfulgt af aktivering. Lad os se, hvad disse trin betyder:

  • Passivering - vores post er gemt et andet sted væk fra infinispan (i dette tilfælde hukommelsen)
  • Udkastelse - posten fjernes for at frigøre hukommelse og for at bevare det konfigurerede maksimale antal poster i cachen
  • Indlæser - når vi prøver at nå vores passiverede post, kontrollerer Infinispan det lagrede indhold og indlæser posten i hukommelsen igen
  • Aktivering - posten er nu tilgængelig i Infinispan igen

4.5. Transaktionel cache

Infinispan leveres med en kraftig transaktionskontrol. Ligesom databasens modstykke er det nyttigt at opretholde integritet, mens mere end en tråd forsøger at skrive den samme post.

Lad os se, hvordan vi kan definere en cache med transaktionsfunktioner:

privat konfiguration transactionalConfiguration () {returner ny ConfigurationBuilder () .transaction (). transactionMode (TransactionMode.TRANSACTIONAL) .lockingMode (LockingMode.PESSIMISTIC) .build (); }

For at gøre det muligt at teste det, lad os bygge to metoder - en, der afslutter sin transaktion hurtigt, og en, der tager et stykke tid:

offentligt heltal getQuickHowManyVisits () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); Heltal howManyVisits = transactionalCache.get (KEY); howManyVisits ++; System.out.println ("Jeg prøver at indstille HowManyVisits til" + howManyVisits); StopWatch ur = nyt StopWatch (); watch.start (); transactionalCache.put (KEY, howManyVisits); watch.stop (); System.out.println ("Jeg kunne sætte HowManyVisits til" + howManyVisits + "efter at have ventet" + watch.getTotalTimeSeconds () + "sekunder"); tm.commit (); returner howManyVisits; }
offentlig ugyldig startBackgroundBatch () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); transactionalCache.put (KEY, 1000); System.out.println ("HowManyVisits skal nu være 1000," + "men vi holder transaktionen"); Tråd. Søvn (1000 l); tm.rollback (); System.out.println ("Den langsomme batch led en tilbageførsel"); }

Lad os nu oprette en test, der udfører begge metoder og kontrollere, hvordan Infinispan opfører sig:

@Test offentlig ugyldig nårLockingAnEntry_thenItShouldBeInaccessible () kaster InterruptedException {Runnable backGroundJob = () -> transactionalService.startBackgroundBatch (); Tråd baggrundTråd = ny tråd (backGroundJob); transactionalService.getQuickHowManyVisits (); backgroundThread.start (); Tråd. Søvn (100); // lad os vente vores tråd opvarmning assertThat (timeThis (() -> transactionalService.getQuickHowManyVisits ()) .isGreaterThan (500) .isLessThan (1000); }

Når vi udfører det, ser vi følgende aktiviteter i vores konsol igen:

Tilføjelse af nøgle 'nøgle' ​​til cache Nøgle 'nøgle blev besøgt. Jeg prøvede at indstille HowManyVisits til 1 Jeg kunne sætte HowManyVisits til 1 efter at have ventet 0,001 sekunder HowManyVisits skulle nu være 1000, men vi holder transaktionen nøgle' ​​nøgle 'blev besøgt Jeg prøver at sætte HowManyVisits til 2 Jeg var i stand til at sætte HowManyVisits til 2 efter at have ventet 0,902 sekunder. Den langsomme batch fik en tilbageførsel

Kontroller klokkeslættet på hovedtråden, og vent på afslutningen af ​​transaktionen oprettet ved den langsomme metode.

5. Konklusion

I denne artikel har vi set, hvad Infinispan er, og det er førende funktioner og funktioner som en cache i en applikation.

Som altid kan koden findes på Github.