Inleiding tot cafeïne

1. Inleiding

In dit artikel gaan we kijken naar Cafeïne - a krachtige cachebibliotheek voor Java.

Een fundamenteel verschil tussen een cache en een Kaart is dat een cache opgeslagen items verwijdert.

Een uitzettingbeleid bepaalt welke objecten moeten worden verwijderd op elk moment. Dit beleid heeft rechtstreeks invloed op de hitrate van de cache - een cruciaal kenmerk van caching-bibliotheken.

Cafeïne gebruikt de Venster TinyLfu uitzettingsbeleid, dat voorziet in een bijna optimale hit rate.

2. Afhankelijkheid

We moeten de cafeïne afhankelijkheid van onze pom.xml:

 com.github.ben-manes.caffeïne cafeïne 2.5.5 

U kunt de nieuwste versie van cafeïne op Maven Central.

3. Cache vullen

Laten we ons concentreren op cafeïne drie strategieën voor cachepopulatie: handmatig, synchroon laden en asynchroon laden.

Laten we eerst een klasse schrijven voor de soorten waarden die we in onze cache zullen opslaan:

class DataObject {private final String data; privé statische int objectCounter = 0; // standard constructors / getters public statisch DataObject get (String data) {objectCounter ++; retourneer nieuwe DataObject (data); }}

3.1. Handmatig vullen

Bij deze strategie plaatsen we handmatig waarden in de cache en halen ze later op.

Laten we onze cache initialiseren:

Cachecache = Caffeine.newBuilder () .expireAfterWrite (1, TimeUnit.MINUTES) .maximumSize (100) .build ();

Nu, we kunnen wat waarde uit de cache halen met behulp van de getIfPresent methode. Deze methode zal terugkeren nul als de waarde niet aanwezig is in de cache:

String key = "A"; DataObject dataObject = cache.getIfPresent (sleutel); assertNull (dataObject);

Wij kunnen vul de cache handmatig met behulp van de leggen methode:

cache.put (sleutel, dataObject); dataObject = cache.getIfPresent (sleutel); assertNotNull (dataObject);

We kunnen de waarde ook krijgen met behulp van de krijgen methode, waarvoor een Functie samen met een sleutel als argument. Deze functie wordt gebruikt voor het leveren van de fallback-waarde als de sleutel niet aanwezig is in de cache, die na berekening in de cache zou worden ingevoegd:

dataObject = cache .get (key, k -> DataObject.get ("Data voor A")); assertNotNull (dataObject); assertEquals ("Data voor A", dataObject.getData ());

De krijgen methode voert de berekening atomair uit. Dit betekent dat de berekening maar één keer wordt uitgevoerd - zelfs als meerdere threads tegelijkertijd om de waarde vragen. Daarom gebruik makend van krijgen heeft de voorkeur boven getIfPresent.

Soms moeten we ongeldig enkele waarden in het cachegeheugen handmatig:

cache.invalidate (sleutel); dataObject = cache.getIfPresent (sleutel); assertNull (dataObject);

3.2. Synchroon laden

Deze methode om de cache te laden vereist een Functie, die wordt gebruikt voor het initialiseren van waarden, vergelijkbaar met de krijgen methode van de handmatige strategie. Laten we eens kijken hoe we dat kunnen gebruiken.

Allereerst moeten we onze cache initialiseren:

LoadingCache cache = Caffeine.newBuilder () .maximumSize (100) .expireAfterWrite (1, TimeUnit.MINUTES) .build (k -> DataObject.get ("Data voor" + k));

Nu kunnen we de waarden ophalen met de krijgen methode:

DataObject dataObject = cache.get (sleutel); assertNotNull (dataObject); assertEquals ("Gegevens voor" + sleutel, dataObject.getData ());

We kunnen ook een reeks waarden krijgen met behulp van de alles krijgen methode:

Kaart dataObjectMap = cache.getAll (Arrays.asList ("A", "B", "C")); assertEquals (3, dataObjectMap.size ());

Waarden worden opgehaald uit de onderliggende back-endinitialisatie Functie dat werd doorgegeven aan de bouwen methode. Dit maakt het mogelijk om de cache te gebruiken als de hoofdgevel voor toegang tot waarden.

3.3. Asynchroon laden

Deze strategie werkt hetzelfde als het vorige, maar voert bewerkingen asynchroon uit en retourneert een CompletableFuture met de werkelijke waarde:

AsyncLoadingCache cache = Caffeine.newBuilder () .maximumSize (100) .expireAfterWrite (1, TimeUnit.MINUTES) .buildAsync (k -> DataObject.get ("Gegevens voor" + k));

Wij kunnen gebruik de krijgen en alles krijgen methoden, op dezelfde manier, rekening houdend met het feit dat ze terugkeren CompletableFuture:

String key = "A"; cache.get (sleutel) .thenAccept (dataObject -> {assertNotNull (dataObject); assertEquals ("Data voor" + sleutel, dataObject.getData ());}); cache.getAll (Arrays.asList ("A", "B", "C")) .thenAccept (dataObjectMap -> assertEquals (3, dataObjectMap.size ()));

CompletableFuture heeft een rijke en handige API, waarover u in dit artikel meer kunt lezen.

4. Uitzetting van waarden

Cafeïne heeft drie strategieën voor waardeontruiming: gebaseerd op grootte, tijd en referentie.

4.1. Op grootte gebaseerde uitzetting

Dit type uitzetting veronderstelt dat uitzetting vindt plaats wanneer de geconfigureerde limiet voor de grootte van de cache wordt overschreden. Er zijn twee manieren om de maat te krijgen - het tellen van objecten in de cache, of het verkrijgen van hun gewicht.

Laten we eens kijken hoe we dat zouden kunnen doen tel objecten in de cache. Wanneer de cache is geïnitialiseerd, is de grootte gelijk aan nul:

LoadingCache cache = Caffeine.newBuilder () .maximumSize (1) .build (k -> DataObject.get ("Data voor" + k)); assertEquals (0, cache.estimatedSize ());

Wanneer we een waarde toevoegen, neemt de maat uiteraard toe:

cache.get ("A"); assertEquals (1, cache.estimatedSize ());

We kunnen de tweede waarde aan de cache toevoegen, wat leidt tot het verwijderen van de eerste waarde:

cache.get ("B"); cache.cleanUp (); assertEquals (1, cache.estimatedSize ());

Het is de moeite waard te vermelden dat we bel de schoonmaken methode voordat u de cachegrootte ophaalt. Dit komt doordat het verwijderen van de cache asynchroon wordt uitgevoerd, en deze methode helpt om de voltooiing van de uitzetting af te wachten.

We kunnen ook passeer een wegerFunctieom de grootte van de cache te krijgen:

LoadingCache cache = Caffeine.newBuilder () .maximumWeight (10) .weger ((k, v) -> 5) .build (k -> DataObject.get ("Gegevens voor" + k)); assertEquals (0, cache.estimatedSize ()); cache.get ("A"); assertEquals (1, cache.estimatedSize ()); cache.get ("B"); assertEquals (2, cache.estimatedSize ());

De waarden worden uit de cache verwijderd als het gewicht hoger is dan 10:

cache.get ("C"); cache.cleanUp (); assertEquals (2, cache.estimatedSize ());

4.2. Tijdgebonden uitzetting

Deze uitzettingsstrategie is gebaseerd op de vervaltijd van de invoer en heeft drie soorten:

  • Vervalt na toegang - de invoer is verlopen nadat de periode is verstreken sinds de laatste keer lezen of schrijven
  • Vervalt na schrijven - invoer is verlopen nadat de periode is verstreken sinds de laatste schrijfactie
  • Aangepast beleid - een vervaltijd wordt voor elke invoer afzonderlijk berekend door de Vervaldatum implementatie

Laten we de strategie voor verlopen na toegang configureren met behulp van de expireAfterAccess methode:

LoadingCache cache = Caffeine.newBuilder () .expireAfterAccess (5, TimeUnit.MINUTES) .build (k -> DataObject.get ("Data voor" + k));

Om de strategie voor verlopen na schrijven te configureren, gebruiken we de expireAfterWrite methode:

cache = Caffeine.newBuilder () .expireAfterWrite (10, TimeUnit.SECONDS) .weakKeys () .weakValues ​​() .build (k -> DataObject.get ("Gegevens voor" + k));

Om een ​​aangepast beleid te initialiseren, moeten we het Vervaldatum koppel:

cache = Caffeine.newBuilder (). expireAfter (new Expiry () {@Override public long expireAfterCreate (String key, DataObject value, long currentTime) {return value.getData (). length () * 1000;} @Override public long expireAfterUpdate (String key, DataObject waarde, long currentTime, long currentDuration) {return currentDuration;} @Override public long expireAfterRead (String key, DataObject waarde, lange currentTime, lange currentDuration) {return currentDuration;}}). Build (k -> DataObject .get ("Gegevens voor" + k));

4.3. Op referentie gebaseerde uitzetting

We kunnen onze cache configureren om toe te staan garbage-collection van cachesleutels en / of waarden. Om dit te doen, zouden we het gebruik van de Zwakke verdediging voor zowel sleutels als waarden, en we kunnen de SoftReference alleen voor garbage-collection van waarden.

De Zwakke verdediging gebruik maakt garbage-collection van objecten mogelijk als er geen sterke verwijzingen naar het object zijn. SoftReference staat toe dat objecten worden verzameld op basis van de globale Least-Recent-Used-strategie van de JVM. Meer details over referenties in Java vindt u hier.

We zouden moeten gebruiken Cafeïne.zwakke toetsen (), Caffeine.weakValues ​​(), en Caffeine.softValues ​​() om elke optie in te schakelen:

LoadingCache cache = Caffeine.newBuilder () .expireAfterWrite (10, TimeUnit.SECONDS) .weakKeys () .weakValues ​​() .build (k -> DataObject.get ("Gegevens voor" + k)); cache = Caffeine.newBuilder () .expireAfterWrite (10, TimeUnit.SECONDS) .softValues ​​() .build (k -> DataObject.get ("Gegevens voor" + k));

5. Verfrissend

Het is mogelijk om de cache te configureren om vermeldingen na een bepaalde periode automatisch te vernieuwen. Laten we eens kijken hoe we dit kunnen doen met de refreshAfterWrite methode:

Caffeine.newBuilder () .refreshAfterWrite (1, TimeUnit.MINUTES) .build (k -> DataObject.get ("Gegevens voor" + k));

Hier moeten we a begrijpen verschil tussen expireAfter en refreshAfter. Wanneer de vervallen invoer wordt aangevraagd, blokkeert een uitvoering totdat de nieuwe waarde zou zijn berekend door de build Functie.

Maar als het item in aanmerking komt voor het vernieuwen, retourneert de cache een oude waarde en herlaad de waarde asynchroon.

6. Statistieken

Cafeïne heeft een middel van het vastleggen van statistieken over cachegebruik:

LoadingCache cache = Caffeine.newBuilder () .maximumSize (100) .recordStats () .build (k -> DataObject.get ("Data voor" + k)); cache.get ("A"); cache.get ("A"); assertEquals (1, cache.stats (). hitCount ()); assertEquals (1, cache.stats (). missCount ());

We kunnen ook overgaan in recordStats leverancier, waardoor een implementatie van de StatsCounter. Dit object wordt bij elke statistiekgerelateerde wijziging gepusht.

7. Conclusie

In dit artikel hebben we kennis gemaakt met de Caffeine caching-bibliotheek voor Java. We hebben gezien hoe we een cache kunnen configureren en vullen, en hoe we een geschikt verloop- of vernieuwingsbeleid kunnen kiezen op basis van onze behoeften.

De broncode die hier wordt weergegeven, is beschikbaar op Github.