En guide til Infinispan på Java

1. Oversikt

I denne veiledningen lærer vi om Infinispan, en minnelås / verdidatalager som leveres med et mer robust sett med funksjoner enn andre verktøy i samme nisje.

For å forstå hvordan det fungerer, bygger vi et enkelt prosjekt som viser de vanligste funksjonene og sjekker hvordan de kan brukes.

2. Prosjektoppsett

For å kunne bruke det på denne måten, må vi legge til det avhengighet i vår pom.xml.

Den siste versjonen finner du i Maven Central repository:

 org.infinispan infinispan-core 9.1.5.Final 

All nødvendig underliggende infrastruktur vil bli håndtert programmatisk fra nå av.

3. CacheManager Oppsett

De CacheManager er grunnlaget for de fleste funksjonene vi bruker. Den fungerer som en container for alle deklarerte cacher, som kontrollerer livssyklusen, og er ansvarlig for den globale konfigurasjonen.

Infinispan leveres med en veldig enkel måte å bygge CacheManager:

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

Nå kan vi bygge cachene våre med den.

4. Cache-oppsett

En cache er definert av et navn og en konfigurasjon. Den nødvendige konfigurasjonen kan bygges ved hjelp av klassen ConfigurationBuilder, allerede tilgjengelig på klassestien vår.

For å teste cachene våre, bygger vi en enkel metode som simulerer noen tunge spørsmål:

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

For å kunne sjekke for endringer i cachene våre, gir Infinispan en enkel kommentar @Lytter.

Når vi definerer cachen vår, kan vi sende noe objekt som er interessert i enhver hendelse inni den, og Infinispan vil varsle det når du håndterer hurtigbufferen:

@Listener public class CacheListener {@CacheEntryCreated public void entryCreated (CacheEntryCreatedEvent event) {this.printLog ("Adding key '" + event.getKey () + "' to cache", event); } @CacheEntryExpired offentlig ugyldig entryExpired (CacheEntryExpiredEvent hendelse) {this.printLog ("Utløpsnøkkel '" + event.getKey () + "' fra cache", hendelse); } @CacheEntryVisited offentlig ugyldig entryVisited (CacheEntryVisitedEvent hendelse) {this.printLog ("Key" "+ event.getKey () +" 'ble besøkt ", event); } @CacheEntryActivated public void entryActivated (CacheEntryActivatedEvent event) {this.printLog ("Aktiveringstast '" + event.getKey () + "' på cache", hendelse); } @CacheEntryPassivated public void entryPassivated (CacheEntryPassivatedEvent event) {this.printLog ("Passiverende nøkkel '" + event.getKey () + "' fra cache", hendelse); } @CacheEntryLoaded offentlig ugyldig entryLoaded (CacheEntryLoadedEvent hendelse) {this.printLog ("Laster nøkkel '" + event.getKey () + "' til cache", hendelse); } @CacheEntriesEvicted offentlige ugyldige posterEvicted (CacheEntriesEvictedEvent hendelse) {StringBuilder byggherre = ny StringBuilder (); event.getEntries (). forEach ((key, value) -> builder.append (key) .append (",")); System.out.println ("Fjerner følgende oppføringer fra cache:" + builder.toString ()); } privat ugyldig printLog (strenglogg, CacheEntryEvent-hendelse) {if (! event.isPre ()) {System.out.println (log); }}}

Før vi skriver ut meldingen, sjekker vi om hendelsen som blir varslet allerede har skjedd, fordi Infinispan for noen hendelsestyper sender to varsler: en før og en rett etter at den er behandlet.

La oss nå bygge en metode for å håndtere cacheopprettingen for oss:

private Cache buildCache (String cacheName, DefaultCacheManager cacheManager, CacheListener lytter, konfigurasjonskonfigurasjon) {cacheManager.defineConfiguration (cacheName, konfigurasjon); Cache cache = cacheManager.getCache (cacheName); cache.addListener (lytter); returbuffer; }

Legg merke til hvordan vi overfører en konfigurasjon til CacheManager, og bruk deretter det samme cacheName for å få objektet som tilsvarer ønsket cache. Legg også merke til hvordan vi informerer lytteren om selve cache-objektet.

Vi sjekker nå fem forskjellige hurtigbufferkonfigurasjoner, og vi får se hvordan vi kan konfigurere dem og utnytte dem best mulig.

4.1. Enkel hurtigbuffer

Den enkleste typen cache kan defineres i en linje ved hjelp av vår metode buildCache:

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

Vi kan nå bygge en Service:

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

Legg merke til hvordan vi bruker hurtigbufferen, og sjekk først om den ønskede oppføringen allerede er hurtigbufret. Hvis ikke, må vi ringe vårt Oppbevaringssted og deretter cache den.

La oss legge til en enkel metode i testene våre for å tidsbestemme metodene våre:

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

Når vi tester det, kan vi sjekke tiden mellom å utføre to metodeanrop:

@Test offentlig annullert når GetIsCalledTwoTimes_thenTheSecondShouldHitTheCache () {assertThat (timeThis (() -> halloWorldService.findSimpleHelloWorld ())) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findSimpleHelloWorld ())) .isLessThan (100); }

4.2. Utløpsbuffer

Vi kan definere en hurtigbuffer der alle oppføringer har en levetid, med andre ord, elementer vil bli fjernet fra hurtigbufferen etter en gitt periode. Konfigurasjonen er ganske enkel:

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

Nå bygger vi cachen vår ved hjelp av konfigurasjonen ovenfor:

offentlig Cache utløperHelloWorldCache (DefaultCacheManager cacheManager, CacheListener lytter) {returner this.buildCache (EXPIRING_HELLO_WORLD_CACHE, cacheManager, lytter, expiringConfiguration ()); }

Og til slutt, bruk den i en lignende metode fra vår enkle cache ovenfor:

public String findSimpleHelloWorldInExpiringCache () {String cacheKey = "simple-hallo"; String helloWorld = expiringHelloWorldCache.get (cacheKey); hvis (helloWorld == null) {helloWorld = repository.getHelloWorld (); utløperHelloWorldCache.put (cacheKey, helloWorld); } returner HelloWorld; }

La oss teste tiden vår igjen:

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

Når vi kjører den, ser vi at hurtigbufferen treffer. For å vise at utløpet er relativt til oppføringen sette tid, la oss tvinge det i oppføringen:

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

Når du har kjørt testen, må du merke deg hvordan etter den gitte tiden vår oppføring var utløpt fra hurtigbufferen. Vi kan bekrefte dette ved å se på de trykte logglinjene fra lytteren vår:

Utføre noe tungt spørsmål Legge til nøkkel 'enkel-hallo' i hurtigbufferen Utløper nøkkel 'enkel-hei' fra hurtigbuffer Utføre noen tung spørring Legge til nøkkel 'enkel-hei' til hurtigbuffer

Merk at oppføringen er utløpt når vi prøver å få tilgang til den. Infinispan ser etter en utløpt oppføring i to øyeblikk: når vi prøver å få tilgang til den eller når reapertråden skanner cachen.

Vi kan bruke utløp selv i cacher uten det i hovedkonfigurasjonen. Metoden sette godtar flere argumenter:

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

Eller, i stedet for en fast levetid, kan vi gi oppføring maksimalt inaktiv tid:

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

Ved å bruke -1 til attributtet levetid, vil ikke cachen utløpe fra den, men når vi kombinerer den med 10 sekunder inaktiv tid, vi ber Infinispan om å utløpe denne oppføringen med mindre den blir besøkt i denne tidsrammen.

4.3. Cache-utkastelse

I Infinispan kan vi begrense antall oppføringer i en gitt cache med utkastelse konfigurasjon:

private Configuration evictingConfiguration () {return new ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .størrelse (1) .build (); }

I dette eksemplet begrenser vi maksimale oppføringer i denne hurtigbufferen til en, noe som betyr at hvis vi prøver å legge inn en annen, blir den kastet ut fra hurtigbufferen vår.

Igjen, metoden er lik den som allerede er presentert her:

public String findEvictingHelloWorld (String key) {String value = evictingHelloWorldCache.get (key); hvis (verdi == null) {verdi = repository.getHelloWorld (); evictingHelloWorldCache.put (nøkkel, verdi); } returverdi; }

La oss bygge testen vår:

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

Når vi kjører testen, kan vi se på lytterloggen vår over aktiviteter:

Utføre noe tungt spørsmål Legge til nøkkel 'nøkkel 1' i hurtigbuffer Utføre noe tung søking Tømme følgende oppføringer fra hurtigbuffer: nøkkel 1, Legge til nøkkel 'nøkkel 2' til hurtigbuffer Utføre noen tunge spørsmål Tømme følgende oppføringer fra hurtigbuffer: tast 2, Legge til nøkkel 1 'til hurtigbuffer

Sjekk hvordan den første nøkkelen automatisk ble fjernet fra hurtigbufferen da vi satte inn den andre, og deretter fjernet den andre også for å gi plass til vår første nøkkel.

4.4. Passiveringsbuffer

De cache passivering er en av de kraftige funksjonene til Infinispan. Ved å kombinere passivering og utkastelse kan vi lage en cache som ikke opptar mye minne uten å miste informasjon.

La oss ta en titt på en passiveringskonfigurasjon:

private Configuration passivatingConfiguration () {return new ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .størrelse (1) .persistence () .passivation (true) // aktivering av passivering .addSingleFileStore () // i en enkelt fil .purgeOnStartup (true) // rens filen ved oppstart .location (System.getProperty ("java.io.tmpdir")) .build (); }

Vi tvinger igjen bare en oppføring i cacheminnet, men ber Infinispan passivere de gjenværende oppføringene, i stedet for bare å fjerne dem.

La oss se hva som skjer når vi prøver å fylle ut mer enn én oppføring:

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

La oss bygge testen og kjøre den:

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

La oss nå se på lytteraktivitetene våre:

Utføre noe tungt spørsmål Legge til nøkkel 'nøkkel 1' i hurtigbufferen Utføre noen tunge spørringer Passiverende nøkkel 'nøkkel 1' fra cache Tømmer følgende poster fra cache: nøkkel 1, Legge til nøkkel 'nøkkel 2' til cache Passiverende nøkkel 'nøkkel 2' fra cache Tømme følgende oppføringer fra cache: nøkkel 2, Laster nøkkel 'nøkkel 1' til cache Aktivering av nøkkel 'nøkkel 1' på cache Nøkkel 'nøkkel 1' ble besøkt

Legg merke til hvor mange trinn det tok for å beholde cachen vår med bare én oppføring. Legg også merke til rekkefølgen på trinnene - passivering, utkastelse og deretter lasting etterfulgt av aktivering. La oss se hva disse trinnene betyr:

  • Passivasjon - oppføringen vår lagres et annet sted, vekk fra nettlagringen til Infinispan (i dette tilfellet minnet)
  • Eviction - oppføringen fjernes, for å frigjøre minne og for å beholde det konfigurerte maksimale antallet oppføringer i hurtigbufferen
  • Laster - når du prøver å nå vår passiverte oppføring, kontrollerer Infinispan det lagrede innholdet og laster oppføringen i minnet igjen
  • Aktivering - oppføringen er nå tilgjengelig i Infinispan igjen

4.5. Transaksjonell hurtigbuffer

Infinispan leveres med en kraftig transaksjonskontroll. I likhet med databasemotstykket er det nyttig å opprettholde integritet mens mer enn en tråd prøver å skrive den samme oppføringen.

La oss se hvordan vi kan definere en cache med transaksjonsmuligheter:

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

For å gjøre det mulig å teste det, la oss bygge to metoder - en som avslutter transaksjonen raskt, og en som tar litt tid:

public Integer getQuickHowManyVisits () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); Heltall howManyVisits = transactionalCache.get (KEY); howManyVisits ++; System.out.println ("Jeg prøver å sette HowManyVisits til" + howManyVisits); StopWatch = ny StopWatch (); watch.start (); transactionalCache.put (KEY, howManyVisits); watch.stop (); System.out.println ("Jeg klarte å sette HowManyVisits til" + howManyVisits + "etter å ha ventet" + watch.getTotalTimeSeconds () + "seconds"); tm.commit (); returner howManyVisits; }
offentlig ugyldig startBackgroundBatch () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); transactionalCache.put (KEY, 1000); System.out.println ("HowManyVisits skal nå være 1000," + "men vi holder transaksjonen"); Tråd. Søvn (1000 l); tm.rollback (); System.out.println ("Den langsomme batchen fikk tilbakeslag"); }

La oss nå lage en test som utfører begge metodene og sjekke hvordan Infinispan vil oppføre seg:

@Test offentlig ugyldig nårLockingAnEntry_thenItShouldBeInaccessible () kaster InterruptedException {Runnable backGroundJob = () -> transactionalService.startBackgroundBatch (); Tråd bakgrunnTråd = ny tråd (backGroundJob); transactionalService.getQuickHowManyVisits (); backgroundThread.start (); Tråd. Søvn (100); // lar oss vente på tråden vår assertThat (timeThis (() -> transactionalService.getQuickHowManyVisits ())) .isGreaterThan (500) .isLessThan (1000); }

Når vi utfører det, ser vi følgende aktiviteter i konsollen vår igjen:

Legge til nøkkel 'nøkkel' til cache Nøkkel 'nøkkel' ble besøkt. Jeg prøvde å sette HowManyVisits til 1 Jeg klarte å sette HowManyVisits til 1 etter å ha ventet 0,001 sekunder HowManyVisits skulle nå være 1000, men vi holder på transaksjonen Nøkkel 'nøkkel' ble besøkt Jeg prøver å sette HowManyVisits til 2 Jeg klarte å sette HowManyVisits til 2 etter å ha ventet 0,902 sekunder.

Sjekk klokkeslettet på hovedtråden, og vent på slutten av transaksjonen opprettet med den langsomme metoden.

5. Konklusjon

I denne artikkelen har vi sett hva Infinispan er, og det er ledende funksjoner og funksjoner som en cache i et program.

Som alltid kan koden finnes på Github.


$config[zx-auto] not found$config[zx-overlay] not found