Vodič za Infinispan na Javi

1. Pregled

U ovom ćemo vodiču naučiti o Infinispanu, memorijskoj pohrani podataka ključ / vrijednost koja se isporučuje s robusnijim skupom značajki od ostalih alata iste niše.

Da bismo razumjeli kako to funkcionira, izradit ćemo jednostavan projekt koji prikazuje najčešće karakteristike i provjeriti kako se one mogu koristiti.

2. Postavljanje projekta

Da bismo ga mogli koristiti na ovaj način, morat ćemo dodati da je to ovisnost u našem pom.xml.

Najnoviju verziju možete pronaći u spremištu Maven Central:

 org.infinispan infinispan-jezgra 9.1.5.Završni 

Sva potrebna temeljna infrastruktura od sada će se programski obrađivati.

3. CacheManager Postaviti

The CacheManager je temelj većine značajki koje ćemo koristiti. Djeluje kao spremnik za sve deklarirane predmemorije, kontrolira njihov životni ciklus i odgovoran je za globalnu konfiguraciju.

Infinispan isporučuje zaista jednostavan način za izgradnju CacheManager:

javni DefaultCacheManager cacheManager () {vratiti novi DefaultCacheManager (); }

Sad smo u mogućnosti izgraditi svoje predmemorije s njim.

4. Postavljanje predmemorije

Predmemorija je definirana imenom i konfiguracijom. Potrebna konfiguracija može se izgraditi pomoću klase ConfigurationBuilder, već dostupno u našem razredu.

Da bismo testirali naše predmemorije, izradit ćemo jednostavnu metodu koja simulira neke teške upite:

javna klasa HelloWorldRepository {public String getHelloWorld () {try {System.out.println ("Izvršavanje nekog teškog upita"); Navoj.spavanje (1000); } catch (InterruptedException e) {// ... e.printStackTrace (); } return "Pozdrav svijetu!"; }}

Također, kako bi mogao provjeriti promjene u našim predmemorijama, Infinispan pruža jednostavnu napomenu @ Slušatelj.

Kada definiramo našu predmemoriju, možemo proslijediti neki objekt zainteresiran za bilo koji događaj koji se unutar nje događa, a Infinispan će to obavijestiti prilikom rukovanja predmemorijom:

@Listener javna klasa CacheListener {@CacheEntryCreated javna praznina entryCreated (CacheEntryCreatedEvent događaj) {this.printLog ("Dodavanje ključa" "+ event.getKey () +" 'u predmemoriju ", događaj); } @CacheEntryExpired javna void entryExpired (CacheEntryExpiredEvent događaj) {this.printLog ("Ključ koji istječe '" + event.getKey () + "' iz predmemorije", događaj); } @CacheEntryVisited javni void entryVisited (CacheEntryVisitedEvent događaj) {this.printLog ("Ključ '" + event.getKey () + "' posjećen je", događaj); } @CacheEntryActivated javna praznina entryActivated (CacheEntryActivatedEvent događaj) {this.printLog ("Ključ za aktiviranje '" + event.getKey () + "' na predmemoriji", događaj); } @CacheEntryPassivated public void entryPassivated (CacheEntryPassivatedEvent event) {this.printLog ("Passivating key '" + event.getKey () + "' from cache", event); } @CacheEntryLoaded javna praznina entryLoaded (CacheEntryLoadedEvent događaj) {this.printLog ("Učitavanje ključa '" + event.getKey () + "' u predmemoriju", događaj); } @CacheEntriesEvicted javni void entriesEvicted (CacheEntriesEvictedEvent event) {StringBuilder builder = new StringBuilder (); event.getEntries (). forEach ((ključ, vrijednost) -> builder.append (ključ) .append (",")); System.out.println ("Izbacivanje sljedećih unosa iz predmemorije:" + builder.toString ()); } private void printLog (String log, CacheEntryEvent event) {if (! event.isPre ()) {System.out.println (log); }}}

Prije ispisa naše poruke provjeravamo je li se događaj o kojem se obavještava već dogodio, jer za neke vrste događaja Infinispan šalje dvije obavijesti: jednu prije i jednu odmah nakon što je obrađena.

Sada izgradimo metodu koja će za nas obraditi stvaranje predmemorije:

private Cache buildCache (String cacheName, DefaultCacheManager cacheManager, CacheListener listener, konfiguracija konfiguracije) {cacheManager.defineConfiguration (cacheName, konfiguracija); Predmemorija predmemorije = cacheManager.getCache (cacheName); cache.addListener (slušatelj); povratna predmemorija; }

Primijetite kako prosljeđujemo konfiguraciju CacheManager, a zatim upotrijebite isti cacheName kako bi se dobio objekt koji odgovara željenoj predmemoriji. Također imajte na umu kako slušatelja obavještavamo o samom objektu predmemorije.

Sada ćemo provjeriti pet različitih konfiguracija predmemorije i vidjet ćemo kako ih možemo postaviti i najbolje iskoristiti.

4.1. Jednostavna predmemorija

Najjednostavnija vrsta predmemorije može se definirati u jednom retku, pomoću naše metode buildCache:

javna predmemorija simpleHelloWorldCache (DefaultCacheManager cacheManager, slušatelj CacheListener) {return this.buildCache (SIMPLE_HELLO_WORLD_CACHE, cacheManager, slušatelj, novi ConfigurationBuilder (). build ()); }

Sada možemo napraviti a Servis:

javni String findSimpleHelloWorld () {String cacheKey = "simple-hello"; vrati simpleHelloWorldCache .computeIfAbsent (cacheKey, k -> repozitorij.getHelloWorld ()); }

Primijetite kako koristimo predmemoriju, prvo provjeravajući je li željeni unos već predmemoriran. Ako nije, morat ćemo nazvati naše Spremište a zatim ga predmemoriraj.

Dodajmo jednostavnu metodu u naše testove kako bismo vremenski odredili naše metode:

protected long timeThis (dobavljač dobavljača) {long millis = System.currentTimeMillis (); dobavljač.get (); return System.currentTimeMillis () - milis; }

Testirajući ga, možemo provjeriti vrijeme između izvršavanja dva poziva metode:

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

4.2. Privremena memorija

Možemo definirati predmemoriju u kojoj svi unosi imaju životni vijek, drugim riječima, elementi će se ukloniti iz predmemorije nakon određenog razdoblja. Konfiguracija je vrlo jednostavna:

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

Sada gradimo našu predmemoriju koristeći gornju konfiguraciju:

javna predmemorija koja ističeHelloWorldCache (DefaultCacheManager cacheManager, slušatelj CacheListener) {return this.buildCache (EXPIRING_HELLO_WORLD_CACHE, cacheManager, listener, expiringConfiguration ()); }

I na kraju, upotrijebite ga na sličan način iz naše jednostavne predmemorije gore:

javni String findSimpleHelloWorldInExpiringCache () {String cacheKey = "simple-hello"; Niz helloWorld = expiringHelloWorldCache.get (cacheKey); if (helloWorld == null) {helloWorld = repository.getHelloWorld (); expiringHelloWorldCache.put (cacheKey, helloWorld); } return helloWorld; }

Pokušajmo ponovno s našim vremenima:

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

Pokrećući ga, vidimo da brzo sprema predmemorija. Da pokaže da je istek relativan u odnosu na njegov unos staviti vrijeme, forsirajmo to u našem unosu:

@Test public void whenGetIsCalledTwiceSparsely_thenNeitherHitsTheCache () baca InterruptedException {assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ()) .isGreaterThanOrEqualTo (1000); Navoj.spavanje (1100); assertThat (timeThis (() -> helloWorldService.findExpiringHelloWorld ())) .isGreaterThanOrEqualTo (1000); }

Nakon pokretanja testa, imajte na umu kako je nakon određenog vremena naš unos istekao iz predmemorije. To možemo potvrditi gledanjem ispisanih redaka dnevnika našeg slušatelja:

Izvršenje nekih teških upita Dodavanje ključa 'simple-hello' u predmemoriju Istek ključa 'simple-hello' iz predmemorije Izvođenje nekih teških upita Dodavanje ključa 'simple-hello' u predmemoriju

Imajte na umu da je unos istekao kad mu pokušamo pristupiti. Infinispan provjerava ima li isteka unosa u dva trenutka: kada mu pokušamo pristupiti ili kada nit žetelaca skenira predmemoriju.

Istek možemo koristiti čak i u predmemorijama bez njega u njihovoj glavnoj konfiguraciji. Metoda staviti prihvaća više argumenata:

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

Ili, umjesto fiksnog životnog vijeka, svom ulasku možemo dati maksimum vrijeme mirovanja:

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

Korištenje -1 za atribut životnog vijeka, predmemorija neće pretrpjeti istek, ali kada ga kombiniramo s 10 sekundi vrijeme mirovanja, kažemo Infinispanu da ističe ovom unosu, osim ako ga ne posjete u ovom vremenskom okviru.

4.3. Izbacivanje iz predmemorije

U Infinispanu možemo ograničiti broj unosa u datoj predmemoriji pomoću konfiguracija deložacije:

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

U ovom primjeru ograničavamo maksimalni unos u ovu predmemoriju na jedan, što znači da će, ako pokušamo ući drugi, biti izbačen iz naše predmemorije.

Opet, metoda je slična već ovdje predstavljenoj:

javni String findEvictingHelloWorld (ključ niza) {Vrijednost niza = evictingHelloWorldCache.get (ključ); if (value == null) {value = repozitorij.getHelloWorld (); evictingHelloWorldCache.put (ključ, vrijednost); } povratna vrijednost; }

Izgradimo svoj test:

@Test public void whenTwoAreAdded_thenFirstShouldntBeAvailable () {assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("ključ 2"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findEvictingHelloWorld ("ključ 1"))) .isGreaterThanOrEqualTo (1000); }

Izvodeći test, možemo pogledati zapisnik aktivnosti slušatelja:

Izvršenje nekih teških upita Dodavanje ključa 'tipka 1' u predmemoriju Izvršavanje nekih teških upita Izbacivanje sljedećih unosa iz predmemorije: tipka 1, Dodavanje tipke 'tipka 2' u predmemoriju Izvršavanje nekih teških upita Izbacivanje sljedećih unosa iz predmemorije: tipka 2, Dodavanje tipke 'tipka 1 'u predmemoriju

Provjerite kako je prvi ključ automatski uklonjen iz predmemorije kad smo umetnuli drugi, a zatim i drugi uklonjen kako bismo ponovno dali mjesta za naš prvi ključ.

4.4. Predmemorija pasivizacije

The pasiviranje predmemorije jedna je od moćnih značajki Infinispana. Kombinacijom pasivizacije i deložacije možemo stvoriti predmemoriju koja ne zauzima puno memorije, bez gubitka podataka.

Pogledajmo konfiguraciju pasivizacije:

private Configuration passivatingConfiguration () {return new ConfigurationBuilder () .memory (). evictionType (EvictionType.COUNT) .size (1) .persistence () .passivation (true) // aktiviranje pasivizacije .addSingleFileStore () // u jednoj datoteci .purgeOnStartup (true) // očistite datoteku pri pokretanju .location (System.getProperty ("java.io.tmpdir")) .build (); }

Ponovno forsiramo samo jedan unos u našu predmemoriju, ali poručujemo Infinispanu da pasivizira preostale unose, umjesto da ih samo ukloni.

Pogledajmo što se događa kada pokušamo ispuniti više od jednog unosa:

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

Izgradimo svoj test i pokrenimo ga:

@Test public void whenTwoAreAdded_thenTheFirstShouldBeAvailable () {assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("key 1"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("ključ 2"))) .isGreaterThanOrEqualTo (1000); assertThat (timeThis (() -> helloWorldService.findPassivatingHelloWorld ("ključ 1"))) .isLessThan (100); }

Pogledajmo sada naše aktivnosti slušatelja:

Izvršavanje nekog teškog upita Dodavanje ključa 'ključ 1' u predmemoriju Izvršavanje nekog teškog upita Pasiviranje ključa 'ključ 1' iz predmemorije Izbacivanje sljedećih unosa iz predmemorije: ključ 1, Dodavanje ključa 'ključ 2' u predmemoriju Pasiviranje ključa 'ključ 2' iz predmemorije Izbacivanje sljedeći unosi iz predmemorije: tipka 2, Učitavanje tipke 'tipka 1' u predmemoriju Posjećen je ključ za aktiviranje 'tipka 1' na predmemoriji Ključ 'tipka 1'

Imajte na umu koliko je koraka bilo potrebno da zadržimo našu predmemoriju samo s jednim unosom. Također, imajte na umu redoslijed koraka - pasivizacija, deložacija, a zatim utovar praćen aktivacijom. Pogledajmo što znače ti koraci:

  • Pasivizacija - naš je unos pohranjen na drugom mjestu, daleko od mrežne pohrane Infinispana (u ovom slučaju, memorije)
  • Deložacija - unos se uklanja, kako bi se oslobodila memorija i zadržao konfigurirani maksimalni broj unosa u predmemoriji
  • Učitavam - kada pokušava doći do našeg pasiviziranog unosa, Infinispan provjerava ima li pohranjenog sadržaja i učitava unos u memoriju
  • Aktivacija - unos je sada ponovno dostupan u Infinispanu

4.5. Transakcijska predmemorija

Infinispan se isporučuje s moćnom kontrolom transakcija. Kao i kolega baze podataka, koristan je u održavanju integriteta dok više od jedne niti pokušava napisati isti unos.

Pogledajmo kako možemo definirati predmemoriju s transakcijskim mogućnostima:

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

Da bismo ga mogli testirati, izgradimo dvije metode - jednu koja brzo završava transakciju i drugu koja traje neko vrijeme:

javni Integer getQuickHowManyVisits () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); Cijeli broj howManyVisits = transactionalCache.get (KLJUČ); howManyVisits ++; System.out.println ("Pokušat ću postaviti HowManyVisits na" + howManyVisits); Štoperica = nova štoperica (); watch.start (); transactionalCache.put (KEY, howManyVisits); watch.stop (); System.out.println ("Uspio sam postaviti HowManyVisits na" + howManyVisits + "nakon čekanja" + watch.getTotalTimeSeconds () + "sekundi"); tm.commit (); return howManyVisits; }
javna praznina startBackgroundBatch () {TransactionManager tm = transactionalCache .getAdvancedCache (). getTransactionManager (); tm.begin (); transactionalCache.put (KLJUČ, 1000); System.out.println ("HowManyVisits sada treba biti 1000," + ", ali mi zadržavamo transakciju"); Navoj.spavanje (1000L); tm.rollback (); System.out.println ("Spori paket pretrpio je povrat"); }

Sada kreirajmo test koji izvršava obje metode i provjerimo kako će se Infinispan ponašati:

@Test public void whenLockingAnEntry_thenItShouldBeInaccessible () baca InterruptedException {Runnable backGroundJob = () -> transactionalService.startBackgroundBatch (); Thread backgroundThread = nova nit (backGroundJob); transactionalService.getQuickHowManyVisits (); backgroundThread.start (); Navoj.spavanje (100); // dopuštamo da čekamo da se naša nit zagrije assertThat (timeThis (() -> transactionalService.getQuickHowManyVisits ())) .isGreaterThan (500) .isLessThan (1000); }

Izvršavajući ga, ponovo ćemo vidjeti sljedeće aktivnosti na našoj konzoli:

Posjećeno je dodavanje ključa 'ključ' u predmemoriju Ključ 'ključ' pokušao sam postaviti HowManyVisits na 1 Mogao sam postaviti HowManyVisits na 1 nakon čekanja 0,001 sekunde HowManyVisits bi sada trebao biti 1000, ali mi držimo ključ transakcije 'ključ' je posjećen Pokušavam loše postaviti HowManyVisits na 2 Uspio sam postaviti HowManyVisits na 2 nakon čekanja 0,902 sekunde. Polagani paket pretrpio je povrat

Provjerite vrijeme na glavnoj niti, čekajući kraj transakcije stvorene slow metodom.

5. Zaključak

U ovom smo članku vidjeli što je Infinispan i njegove su vodeće značajke i mogućnosti kao predmemorija unutar aplikacije.

Kao i uvijek, kod se može pronaći na Githubu.