Programmering

Udvikle en generisk cachetjeneste for at forbedre ydeevnen

Antag, at en kollega beder dig om en liste over alle lande i verden. Fordi du ikke er nogen geografiekspert, surfer du over til FNs websted, downloader listen og udskriver den til hende. Imidlertid ønsker hun kun at undersøge listen; hun tager det faktisk ikke med sig. Fordi det sidste, du har brug for, er et andet stykke papir på dit skrivebord, giver du listen til makuleringsmaskinen.

En dag senere anmoder en anden kollega om det samme: en liste over hvert land i verden. Forbandelse på dig selv for ikke at holde listen, surfer du igen til FNs websted. Ved dette besøg på hjemmesiden bemærker du, at FN opdaterer sin landeliste hver sjette måned. Du downloader og udskriver listen til din kollega. Han ser på det tak, og efterlader igen listen med dig. Denne gang arkiverer du listen med en besked på en vedhæftet Post-it-note, der minder dig om at kassere den efter seks måneder.

Sikkert nok, i løbet af de næste par uger fortsætter dine kolleger med at anmode om listen igen og igen. Du lykønsker dig selv med arkivering af dokumentet, da du hurtigere kan udpakke dokumentet fra arkivskabet, end du kan udpakke det fra hjemmesiden. Dit arkivkoncept fanger fat; snart begynder alle at lægge genstande i dit kabinet. For at forhindre skabet i at blive uorganiseret, sætter du retningslinjer for brugen af ​​det. I din officielle egenskab som arkivchef, du instruerer dine kollegaer om at placere etiketter og Post-it-noter på alle dokumenter, der identificerer dokumenterne og deres kassering / udløbsdato. Etiketterne hjælper dine kolleger med at finde det dokument, de leder efter, og Post-it-notaterne kvalificerer, om oplysningerne er opdaterede.

Arkivskabet bliver så populært, at du snart ikke kan arkivere nye dokumenter i det. Du skal beslutte, hvad du skal smide ud, og hvad du skal beholde. Selvom du smider alle udløbne dokumenter, løber kabinettet stadig over med papir. Hvordan beslutter du, hvilke ikke-udløbne dokumenter der skal kastes? Kasserer du det ældste dokument? Du kan kassere de mindst anvendte eller de sidst anvendte; i begge tilfælde har du brug for en log, der er angivet, når hvert dokument blev åbnet. Eller måske kan du beslutte, hvilke dokumenter du vil kassere på baggrund af en anden determinant; beslutningen er rent personlig.

For at relatere ovennævnte analogi med computerverdenen fungerer arkivskabet som en cache: en højhastighedshukommelse, der lejlighedsvis har brug for vedligeholdelse. Dokumenterne i cachen er cachelagrede genstande, som alle er i overensstemmelse med de standarder, du har angivet cache manager. Processen med at rense cachen kaldes udrensning. Da cache-genstande renses, efter at en vis tid er gået, kaldes cachen a tidsbestemt cache.

I denne artikel lærer du, hvordan du opretter en 100 procent ren Java-cache, der bruger en anonym baggrundstråd til at rense udløbne emner. Du vil se, hvordan man arkiverer en sådan cache, mens man forstår de kompromiser, der er involveret i forskellige designs.

Byg cachen

Nok arkiveringskabinetsanalogier: lad os gå videre til websteder. Websiteservere skal også håndtere caching. Servere modtager gentagne gange anmodninger om information, som er identiske med andre anmodninger. Til din næste opgave skal du oprette en internetapplikation til en af ​​verdens største virksomheder. Efter fire måneders udvikling, inklusive mange søvnløse nætter og alt for mange Jolt cola, går applikationen i udviklingstest med 1.000 brugere. En 5.000-bruger certificeringstest og en efterfølgende 20.000-brugers produktionsudrulning følger udviklingstesten. Når du dog modtager fejl uden for hukommelsen, mens kun 200 brugere tester applikationen, stopper udviklingstest.

For at skelne kilden til ydelsesforringelsen bruger du et profileringsprodukt og opdager, at serveren indlæser flere kopier af databasen ResultSets, som hver har flere tusinde poster. Optegnelserne udgør en produktliste. Desuden er produktlisten identisk for hver bruger. Listen afhænger ikke af brugeren, som det kunne have været tilfældet, hvis produktlisten var resultatet af en parametreret forespørgsel. Du beslutter hurtigt, at en kopi af listen kan tjene alle samtidige brugere, så du cache den.

Der opstår dog en række spørgsmål, der inkluderer kompleksiteter som:

  • Hvad hvis produktlisten ændres? Hvordan kan cachen udløbe listerne? Hvordan ved jeg, hvor længe produktlisten skal forblive i cachen, før den udløber?
  • Hvad hvis der findes to forskellige produktlister, og de to lister ændres med forskellige intervaller? Kan jeg udløbe hver liste individuelt, eller skal de alle have samme holdbarhed?
  • Hvad hvis cachen er tom, og to anmodere prøver cachen nøjagtigt på samme tid? Når de begge finder det tomt, vil de oprette deres egne lister, og så prøver begge at sætte deres kopier i cachen?
  • Hvad hvis elementer sidder i cachen i flere måneder uden at få adgang til dem? Vil de ikke spise hukommelsen op?

For at imødegå disse udfordringer skal du oprette en software-cachetjeneste.

I arkivskabets analogi kontrollerede folk altid kabinettet først, når de søgte efter dokumenter. Din software skal implementere den samme procedure: en anmodning skal kontrollere cachetjenesten, før den indlæser en ny liste fra databasen. Som softwareudvikler er dit ansvar at få adgang til cachen inden adgang til databasen. Hvis produktlisten allerede er indlæst i cachen, skal du bruge cachelisten, forudsat at den ikke er udløbet. Hvis produktlisten ikke er i cachen, skal du indlæse den fra databasen og cache den med det samme.

Bemærk: Inden du fortsætter med cachetjenestens krav og kode, kan du tjekke sidebjælken nedenfor, "Caching Versus Pooling." Det forklarer pooling, et beslægtet koncept.

Krav

I overensstemmelse med gode designprincipper definerede jeg en kravliste til cachetjenesten, som vi vil udvikle i denne artikel:

  1. Alle Java-applikationer har adgang til cachetjenesten.
  2. Objekter kan placeres i cachen.
  3. Objekter kan udvindes fra cachen.
  4. Cache-objekter kan selv bestemme, hvornår de udløber, hvilket giver maksimal fleksibilitet. Cachetjenester, der udløber alle objekter ved hjælp af den samme udløbsformel, giver ikke optimal brug af cachelagrede objekter. Denne tilgang er utilstrækkelig i store systemer, for eksempel kan en produktliste muligvis ændre sig dagligt, mens en liste over butikslokationer måske kun ændres en gang om måneden.
  5. En baggrundstråd, der kører under lav prioritet, fjerner udløbne cachelagrede objekter.
  6. Cachetjenesten kan forbedres senere ved hjælp af en mindst mulig anvendt (LRU) eller mindst hyppigt anvendt (LFU) rensemekanisme.

Implementering

For at opfylde krav 1 vedtager vi et 100 procent rent Java-miljø. Ved at give offentligheden og sæt metoder i cachetjenesten, opfylder vi også krav 2 og 3.

Før jeg fortsætter med en diskussion af krav 4, nævner jeg kort, at vi vil opfylde krav 5 ved at oprette en anonym tråd i cache-manager; denne tråd starter i den statiske blok. Vi opfylder også krav 6 ved at identificere de punkter, hvor kode senere vil blive tilføjet til implementering af LRU- og LFU-algoritmer. Jeg vil gå nærmere ind på disse krav senere i artiklen.

Nu tilbage til krav 4, hvor tingene bliver interessante. Hvis hvert cachelagret objekt selv skal bestemme, om det er udløbet, skal du have en måde at spørge objektet, om det er udløbet. Det betyder, at objekter i cachen alle skal overholde bestemte regler; du opnår det i Java ved at implementere en grænseflade.

Lad os begynde med de regler, der styrer de objekter, der er placeret i cachen.

  1. Alle objekter skal have en offentlig metode kaldet isExpired (), som returnerer en boolsk værdi.
  2. Alle objekter skal have en offentlig metode kaldet getIdentifier (), som returnerer et objekt, der adskiller objektet fra alle andre i cachen.

Bemærk: Før du hopper direkte ind i koden, skal du forstå, at du kan implementere en cache på mange måder. Jeg har fundet mere end et dusin forskellige implementeringer. Enhydra og Caucho giver fremragende ressourcer, der indeholder flere cache-implementeringer.

Du finder interfacekoden til denne artikels cachetjeneste i Listing 1.

Fortegnelse 1. Cacheable.java

/ ** * Titel: Cache Beskrivelse: Denne grænseflade definerer metoderne, som skal implementeres af alle objekter, der ønsker at blive placeret i cachen. * * Ophavsret: Copyright (c) 2001 * Virksomhed: JavaWorld * Filnavn: Cacheable.java @ forfatter Jonathan Lurie @version 1.0 * / offentlig grænseflade Cacheable {/ * Ved at kræve, at alle objekter bestemmer deres egne udløb, bliver algoritmen abstraheret fra caching-service, hvilket giver maksimal fleksibilitet, da hvert objekt kan vedtage en anden udløbsstrategi. * / public boolean isExpired (); / * Denne metode vil sikre, at cachetjenesten ikke er ansvarlig for entydigt at identificere objekter, der er placeret i cachen. * / public Object getIdentifier (); } 

Ethvert objekt, der placeres i cachen - a Snor, for eksempel - skal pakkes ind i en genstand, der implementerer Cacheable interface. Liste 2 er et eksempel på en generisk wrapper-klasse kaldet Cacheobjekt; det kan indeholde ethvert objekt, der skal placeres i cachetjenesten. Bemærk, at denne indpakningsklasse implementerer Cacheable interface defineret i liste 1.

Notering 2. CachedManagerTestProgram.java

/ ** * Titel: Cache * Beskrivelse: En generisk cache-objektindpakning. Implementerer den cacheable grænseflade * bruger en TimeToLive-strategi til udløb af CacheObject. * Copyright: Copyright (c) 2001 * Virksomhed: JavaWorld * Filnavn: CacheManagerTestProgram.java * @forfatter Jonathan Lurie * @version 1.0 * / offentlig klasse CachedObject implementerer Cacheable {// ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++ ++++ / * Denne variabel bruges til at bestemme, om objektet er udløbet. * / private java.util.Date dateofExpiration = null; privat objekt-id = null; / * Dette indeholder den reelle "værdi". Dette er det objekt, der skal deles. * / public Object object = null; // +++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++ public CachedObject (Object obj, Object id, int minutesToLive) {this.object = obj; this.identifier = id; // minutesToLive på 0 betyder, at den lever på ubestemt tid. hvis (minutesToLive! = 0) {dateofExpiration = ny java.util.Date (); java.util.Calendar cal = java.util.Calendar.getInstance (); cal.setTime (dateofExpiration); cal.add (cal.MINUTE, minutesToLive); dateofExpiration = cal.getTime (); }} // ++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ public boolean isExpired () {// Husk, at minutterne at leve er nul, så lever det for evigt! hvis (dateofExpiration! = null) {// dato for udløb sammenlignes. hvis (dateofExpiration.before (new java.util.Date ())) {System.out.println ("CachedResultSet.isExpired: Expired from Cache! EXPIRE TIME:" + dateofExpiration.toString () + "CURRENT TIME:" + ( ny java.util.Date ()). toString ()); returner sandt; } andet {System.out.println ("CachedResultSet.isExpired: Udløbet ikke fra Cache!"); returner falsk; }} andet // Dette betyder, at det lever for evigt! returner falsk; } // +++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++ Public Object getIdentifier () {return identifikator; } // +++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++} 

Det Cacheobjekt klasse udsætter en konstruktormetode, der tager tre parametre:

public CachedObject (Object obj, Object id, int minutesToLive) 

Tabellen nedenfor beskriver disse parametre.

Parameterbeskrivelser af CachedObject-konstruktøren
NavnTypeBeskrivelse
ObjObjektObjektet, der deles. Det er defineret som et objekt, der giver maksimal fleksibilitet.
IdObjektId indeholder en unik identifikator, der skelner mellem obj parameter fra alle andre objekter, der findes i cachen. Cachingtjenesten er ikke ansvarlig for at sikre entydigheden af ​​objekterne i cachen.
minutesToLiveIntAntallet af minutter, som obj parameter er gyldig i cachen. I denne implementering fortolker cachetjenesten en værdi på nul, så objektet aldrig udløber. Du vil måske ændre denne parameter, hvis du har brug for at udløbe objekter på mindre end et minut.

Konstruktormetoden bestemmer udløbsdatoen for objektet i cachen ved hjælp af a time-to-live strategi. Som navnet antyder betyder time-to-live, at et bestemt objekt har en fast tid ved afslutningen, som det betragtes som død. Ved at tilføje minutesToLive, konstruktørens int parameter beregnes en udløbsdato til det aktuelle tidspunkt. Dette udløb tildeles klassevariablen dateofExpiration.

Nu, den isExpired () metoden skal simpelthen afgøre, om dateofExpiration er før eller efter den aktuelle dato og tid. Hvis datoen er før det aktuelle tidspunkt, og det cachelagrede objekt anses for udløbet, vises isExpired () metoden returnerer sand; hvis datoen er efter det aktuelle klokkeslæt, er det cachelagrede objekt ikke udløbet, og isExpired () returnerer falsk. Selvfølgelig, hvis dateofExpiration er nul, hvilket ville være tilfældet, hvis minutesToLive var nul, så blev isExpired () metoden returnerer altid falsk, hvilket indikerer, at det cachelagrede objekt lever for evigt.