Razviti generičku uslugu predmemoriranja za poboljšanje izvedbe

Pretpostavimo da vas suradnik zatraži popis svih zemalja na svijetu. Budući da niste stručnjak za geografiju, pregledavate web stranicu Ujedinjenih naroda, preuzimate popis i ispisujete joj je. Međutim, ona želi samo pregledati popis; zapravo ga ne uzima sa sobom. Budući da je zadnje što vam treba još jedan papir na vašem stolu, popis ubacujete u drobilicu.

Dan kasnije, drugi kolega zahtijeva isto: popis svih zemalja na svijetu. Psujući se što niste zadržali popis, opet surfate natrag na web stranicu Ujedinjenih naroda. Tijekom ovog posjeta web mjestu, primijetili ste da UN ažurira popis zemalja svakih šest mjeseci. Preuzimate i ispisujete popis za svog suradnika. On to gleda, zahvaljuje vam i opet ostavlja popis vama. Ovaj put popis odložite porukom na priloženoj bilješci Post-it koja vas podsjeća da ga odbacite nakon šest mjeseci.

Svakako, tijekom sljedećih nekoliko tjedana vaši suradnici nastavljaju tražiti popis iznova i iznova. Čestitate si na podnošenju dokumenta, jer dokument možete izvaditi iz kartoteke brže nego na web mjestu. Vaš koncept ormara za dosijee prihvaća; uskoro svi počinju stavljati predmete u vaš ormarić. Da biste spriječili da kabinet postane neorganiziran, postavili ste smjernice za njegovo korištenje. U svojstvu službenog svojstva upravitelja kartoteke, upućujete svoje suradnike da na sve dokumente postave naljepnice i post-it bilješke koje identificiraju dokumente i datum njihovog odbacivanja / isteka. Oznake pomažu vašim suradnicima da pronađu dokument koji traže, a bilješke nakon objave utvrđuju jesu li podaci ažurirani.

Ormar za spise postaje toliko popularan da u njega uskoro ne možete uložiti nove dokumente. Morate odlučiti što ćete izbaciti, a što zadržati. Iako izbacujete sve istekle dokumente, kabinet i dalje prelijeva papir. Kako odlučiti koje dokumente kojima je istekao rok valjanosti baciti? Odbacujete li najstariji dokument? Možete odbaciti najmanje korištene ili najmanje korištene nedavno; u oba slučaja trebao bi vam zapisnik s popisom kada se pristupilo svakom dokumentu. Ili biste možda mogli odlučiti koje ćete dokumente baciti na temelju neke druge odrednice; odluka je čisto osobna.

Kako bi se gornja stvarna analogija povezala sa računalnim svijetom, ormarić za dosije funkcionira kao predmemorija: memorija velike brzine koja povremeno treba održavati. Dokumenti u predmemoriji predmemorirani su objekti, a svi oni odgovaraju standardima koje ste postavili vi, upravitelj predmemorije. Proces čišćenja predmemorije naziva se pročišćavanjem. Budući da se predmemorirane stavke brišu nakon određenog vremena, predmemorija se naziva vremenskom predmemorijom.

U ovom ćete članku naučiti kako stvoriti 100-postotnu čistu Java predmemoriju koja koristi anonimnu pozadinsku nit za uklanjanje isteklih stavki. Vidjet ćete kako oblikovati takvu predmemoriju dok istovremeno razumijete kompromise koji se uključuju u različite dizajne.

Izgradite predmemoriju

Dosta analogija u kartoteci: prijeđimo na web stranice. Poslužitelji web stranica trebaju se baviti i predmemoriranjem. Poslužitelji više puta primaju zahtjeve za informacijama koji su identični ostalim zahtjevima. Za sljedeći zadatak morate izraditi internetsku aplikaciju za jednu od najvećih svjetskih tvrtki. Nakon četiri mjeseca razvoja, uključujući mnoge neprospavane noći i previše Jolt cola, aplikacija ide u razvojno testiranje s 1.000 korisnika. Test za ovjeru od 5000 korisnika i naknadno uvođenje proizvodnje od 20 000 korisnika slijede razvojno testiranje. Međutim, nakon što primite pogreške izvan memorije dok samo 200 korisnika testira aplikaciju, razvojno testiranje se zaustavlja.

Da biste prepoznali izvor pogoršanja performansi, koristite proizvod za profiliranje i otkrijte da poslužitelj učitava više kopija baza podataka ResultSet, od kojih svaka ima nekoliko tisuća zapisa. Evidencija čini popis proizvoda. Štoviše, popis proizvoda identičan je za svakog korisnika. Popis ne ovisi o korisniku, kao što bi to mogao biti slučaj da je popis proizvoda proizašao iz parametriziranog upita. Brzo odlučite da bi jedna kopija popisa mogla služiti svim istodobnim korisnicima, pa je zato predmemorirajte.

Međutim, postavlja se niz pitanja koja uključuju takve složenosti kao što su:

  • Što ako se lista proizvoda promijeni? Kako predmemorija može isteći popisima? Kako ću znati koliko dugo popis proizvoda treba ostati u predmemoriji prije isteka?
  • Što ako postoje dva različita popisa proizvoda i ako se dva popisa mijenjaju u različitim intervalima? Mogu li isteći svakom popisu pojedinačno ili svi moraju imati isti rok trajanja?
  • Što ako je predmemorija prazna i dva podnositelja zahtjeva istodobno isprobaju predmemoriju? Kad ih oboje smatraju praznima, hoće li stvoriti vlastite popise, a zatim oboje pokušati staviti svoje kopije u predmemoriju?
  • Što ako stavke mjesecima sjede u predmemoriji, a da im se ne pristupi? Neće li pojesti uspomenu?

Da biste se pozabavili tim izazovima, morate stvoriti uslugu softverskog predmemoriranja.

U analogiji s kartotekom, ljudi su uvijek prvo provjeravali kabinet kada su tražili dokumente. Vaš softver mora implementirati isti postupak: zahtjev mora provjeriti uslugu predmemoriranja prije učitavanja svježeg popisa iz baze podataka. Kao programer softvera, vaša je odgovornost pristupiti predmemoriji prije pristupanja bazi podataka. Ako se popis proizvoda već učitao u predmemoriju, tada koristite predmemorirani popis, pod uvjetom da nije istekao. Ako se popis proizvoda ne nalazi u predmemoriji, tada ga učitavate iz baze podataka i odmah predmemorirate.

Napomena: Prije nego što nastavite sa zahtjevima i kodom usluge predmemoriranja, možda biste trebali pogledati bočnu traku u nastavku, "Caching Versus Pooling". Objašnjava udruživanje, povezani koncept.

Zahtjevi

U skladu s dobrim principima dizajna, definirao sam popis zahtjeva za uslugu predmemoriranja koji ćemo razviti u ovom članku:

  1. Bilo koji Java program može pristupiti usluzi predmemoriranja.
  2. Predmeti se mogu staviti u predmemoriju.
  3. Predmeti se mogu izvući iz predmemorije.
  4. Predmemorirani objekti mogu sami odrediti kada ističu, čime omogućuju maksimalnu fleksibilnost. Usluge keširanja kojima ističu svi objekti koji koriste istu formulu isteka ne pružaju optimalnu upotrebu predmemoriranih objekata. Ovaj je pristup neodgovarajući u velikim sustavima, jer se, na primjer, popis proizvoda može mijenjati svakodnevno, dok se popis mjesta trgovina može mijenjati samo jednom mjesečno.
  5. Pozadinska nit koja se izvodi pod niskim prioritetom uklanja predmemorirane objekte kojima je isteklo.
  6. Usluga predmemoriranja može se poboljšati kasnije upotrebom najmanje nedavno korištenog (LRU) ili najmanje korištenog (LFU) mehanizma za pročišćavanje.

Provedba

Da bismo udovoljili Zahtjevu 1, usvajamo 100 posto čisto Java okruženje. Pružajući javnosti geti setmetode u usluzi predmemoriranja, ispunjavamo i zahtjeve 2 i 3.

Prije nego što nastavim s raspravom o zahtjevu 4, kratko ću spomenuti da ćemo ispuniti zahtjev 5 stvaranjem anonimne niti u upravitelju predmemorije; ova nit započinje u statičkom bloku. Također, zadovoljavamo zahtjev 6 identificiranjem točaka u koje će se kasnije dodati kôd za implementaciju LRU i LFU algoritama. O tim zahtjevima pobliže ću govoriti u nastavku članka.

Sada se vratimo Zahtjevu 4, gdje stvari postaju zanimljive. Ako svaki predmemorirani objekt mora sam odrediti je li istekao, tada morate na način pitati objekt je li istekao. To znači da svi predmeti u predmemoriji moraju biti u skladu s određenim pravilima; to postižete u Javi implementiranjem sučelja.

Počnimo s pravilima koja reguliraju objekte smještene u predmemoriju.

  1. Svi objekti moraju imati javnu pozvanu metodu isExpired()koja vraća logičku vrijednost.
  2. Svi objekti moraju imati pozvanu javnu metodu getIdentifier()koja vraća objekt koji razlikuje objekt od svih ostalih u predmemoriji.

Napomena: Prije skoka ravno u kôd morate shvatiti da predmemoriju možete implementirati na mnogo načina. Pronašao sam više od desetak različitih implementacija. Enhydra i Caucho pružaju izvrsne resurse koji sadrže nekoliko implementacija predmemorije.

Kôd sučelja za uslugu predmemoriranja ovog članka pronaći ćete u Popisu 1.

Popis 1. Cacheable.java

/** * Title: Caching Description: This interface defines the methods, which must be implemented by all objects wishing to be placed in the cache. * * Copyright: Copyright (c) 2001 * Company: JavaWorld * FileName: Cacheable.java @author Jonathan Lurie @version 1.0 */ public interface Cacheable { /* By requiring all objects to determine their own expirations, the algorithm is abstracted from the caching service, thereby providing maximum flexibility since each object can adopt a different expiration strategy. */ public boolean isExpired(); /* This method will ensure that the caching service is not responsible for uniquely identifying objects placed in the cache. */ public Object getIdentifier(); } 

Any object placed in the cache -- a String, for example -- must be wrapped inside an object that implements the Cacheable interface. Listing 2 is an example of a generic wrapper class called CachedObject; it can contain any object needed to be placed in the caching service. Note that this wrapper class implements the Cacheable interface defined in Listing 1.

Listing 2. CachedManagerTestProgram.java

/** * Title: Caching * Description: A Generic Cache Object wrapper. Implements the Cacheable interface * uses a TimeToLive stategy for CacheObject expiration. * Copyright: Copyright (c) 2001 * Company: JavaWorld * Filename: CacheManagerTestProgram.java * @author Jonathan Lurie * @version 1.0 */ public class CachedObject implements Cacheable { // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ /* This variable will be used to determine if the object is expired. */ private java.util.Date dateofExpiration = null; private Object identifier = null; /* This contains the real "value". This is the object which needs to be shared. */ public Object object = null; // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ public CachedObject(Object obj, Object id, int minutesToLive) { this.object = obj; this.identifier = id; // minutesToLive of 0 means it lives on indefinitely. if (minutesToLive != 0) { dateofExpiration = new 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() { // Remember if the minutes to live is zero then it lives forever! if (dateofExpiration != null) { // date of expiration is compared. if (dateofExpiration.before(new java.util.Date())) { System.out.println("CachedResultSet.isExpired: Expired from Cache! EXPIRE TIME: " + dateofExpiration.toString() + " CURRENT TIME: " + (new java.util.Date()).toString()); return true; } else { System.out.println("CachedResultSet.isExpired: Expired not from Cache!"); return false; } } else // This means it lives forever! return false; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ public Object getIdentifier() { return identifier; } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } 

The CachedObject class exposes a constructor method that takes three parameters:

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

The table below describes those parameters.

Parameter descriptions of the CachedObject constructor
Name Type Description
Obj Object The object that is shared. It is defined as an object to allow maximum flexibility.
Id Object Id contains a unique identifier that distinguishes the obj parameter from all other objects residing in the cache. The caching service is not responsible for ensuring the uniqueness of the objects in the cache.
minutesToLive Int The number of minutes that the obj parameter is valid in the cache. In this implementation, the caching service interprets a value of zero to mean that the object never expires. You might want to change this parameter in the event that you need to expire objects in less than one minute.

The constructor method determines the expiration date of the object in the cache using a time-to-live strategy. As its name implies, time-to-live means that a certain object has a fixed time at the conclusion of which it is considered dead. By adding minutesToLive, the constructor's int parameter, to the current time, an expiration date is calculated. This expiration is assigned to the class variable dateofExpiration.

Sada isExpired()metoda jednostavno mora utvrditi je li dateofExpirationprije ili poslije trenutnog datuma i vremena. Ako je datum prije trenutnog vremena, a predmemorirani objekt smatra se isteklim, isExpired()metoda vraća true; ako je datum nakon trenutnog vremena, predmemorirani objekt nije istekao i isExpired()vraća false. Naravno, ako dateofExpirationje null, što bi bio slučaj da minutesToLiveje nula, tada isExpired()metoda uvijek vraća false, što ukazuje da predmemorirani objekt živi vječno.