Programmering

Pool ressurser ved hjelp av Apache's Commons Pool Framework

Å samle ressurser (også kalt object pooling) blant flere klienter er en teknikk som brukes til å fremme gjenbruk av objekter og for å redusere overhead for å skape nye ressurser, noe som resulterer i bedre ytelse og gjennomstrømning. Tenk deg et kraftig Java-serverprogram som sender hundrevis av SQL-spørsmål ved å åpne og lukke tilkoblinger for hver SQL-forespørsel. Eller en webserver som serverer hundrevis av HTTP-forespørsler, som håndterer hver forespørsel ved å gyte en egen tråd. Eller forestill deg å lage en XML-parserforekomst for hver forespørsel om å analysere et dokument uten å bruke forekomsten på nytt. Dette er noen av scenariene som garanterer optimalisering av ressursene som brukes.

Ressursbruk kan til tider være kritisk for tunge applikasjoner. Noen kjente nettsteder har stengt på grunn av manglende evne til å håndtere tunge belastninger. De fleste problemer relatert til tunge belastninger kan håndteres på makronivå ved hjelp av klynging og lastbalansering. Bekymringer forblir på applikasjonsnivå med hensyn til overdreven oppretting av objekter og tilgjengeligheten av begrensede serverressurser som minne, CPU, tråder og databaseforbindelser, som kan representere potensielle flaskehalser og, når de ikke utnyttes optimalt, få ned hele serveren.

I noen situasjoner kan databasebrukspolitikken håndheve en begrensning på antall samtidige tilkoblinger. En ekstern applikasjon kan også diktere eller begrense antall åpne åpne forbindelser. Et typisk eksempel er et domeneregister (som Verisign) som begrenser antall tilgjengelige aktive socket-tilkoblinger for registrarer (som BulkRegister). Å samle ressurser har vist seg å være et av de beste alternativene for å håndtere denne typen problemer, og hjelper til en viss grad også med å opprettholde de nødvendige servicenivåene for bedriftsapplikasjoner.

De fleste J2EE-applikasjonsserverleverandører tilbyr ressurssamling som en integrert del av deres web- og EJB (Enterprise JavaBean) -beholdere. For databasekoblinger gir serverleverandøren vanligvis en implementering av Datakilde grensesnitt, som fungerer sammen med JDBC (Java Database Connectivity) driverleverandører ConnectionPoolDataSource gjennomføring. De ConnectionPoolDataSource implementering fungerer som en ressursansvarlig tilkoblingsfabrikk for samlet java.sql.Tilkobling gjenstander. På samme måte samles EJB-forekomster av statsløse øktbønner, meldingsdrevne bønner og enhetsbønner i EJB-beholdere for høyere gjennomstrømning og ytelse. XML-parserforekomster er også kandidater til å samle, fordi opprettelsen av parserforekomster bruker mye av systemets ressurser.

En vellykket implementering av ressurs-pooling med åpen kildekode er Commons Pool-rammeverkets DBCP, en komponentkomponent for databasetilkobling fra Apace Software Foundation som brukes mye i bedriftsapplikasjoner i produksjonsklasse. I denne artikkelen diskuterer jeg kort det indre av Commons Pool-rammeverket, og bruker det deretter til å implementere en trådpool.

La oss først se på hva rammeverket gir.

Commons Pool rammeverk

Commons Pool-rammeverket tilbyr en grunnleggende og robust implementering for å samle vilkårlige objekter. Flere implementeringer er gitt, men for denne artikkelens formål bruker vi den mest generiske implementeringen, GenericObjectPool. Den bruker en CursorableLinkedList, som er en dobbeltkoblet listeimplementering (en del av Jakarta Commons Collections), som den underliggende datastrukturen for å holde objektene som samles.

På toppen gir rammeverket et sett med grensesnitt som leverer livssyklusmetoder og hjelpermetoder for å administrere, overvåke og utvide bassenget.

Grensesnittet org.apache.commons.PoolableObjectFactory definerer følgende livssyklusmetoder, som viser seg å være avgjørende for implementering av en pooling-komponent:

 // Oppretter en forekomst som kan returneres av bassenget public Object makeObject () {} // Ødelegger en forekomst som ikke lenger er nødvendig av bassenget public void destroyObject (Object obj) {} // Valider objektet før du bruker det public boolean validateObject (Objektobjekt) {} // Initialiser en forekomst som skal returneres av bassenget offentlig ugyldig aktivObject (Objektobjekt) {} // Uninitialiser en forekomst som skal returneres til bassenget offentlig ugyldig passivateObject (Objektobjekt) {}

Som du kan finne ut av metodesignaturene, handler dette grensesnittet primært om følgende:

  • makeObject (): Implementere objektopprettelsen
  • destroyObject (): Implementere objektet ødeleggelse
  • validateObject (): Valider objektet før det brukes
  • activeObject (): Implementere koden for initialisering av objektet
  • passivateObject (): Implementere koden for ikke-initialisering av objektet

Et annet kjernegrensesnitt -org.apache.commons.ObjectPool—Definerer følgende metoder for å administrere og overvåke bassenget:

 // Få en forekomst fra bassenget mitt Object loanObject () kaster Unntak; // Returner en forekomst til bassenget mitt ugyldig returnObject (Objekt obj) kaster Unntak; // Invalidates an object from the pool void ugyldiggjøreObject (Object obj) kaster Unntak; // Brukes til å forhåndslaste et basseng med inaktive objekter ugyldig addObject () kaster Unntak; // Returner antall inaktive forekomster int getNumIdle () kaster UnsupportedOperationException; // Returner antall aktive forekomster int getNumActive () kaster UnsupportedOperationException; // Fjerner de inaktive objektene ugyldig clear () kaster Exception, UnsupportedOperationException; // Lukk bassenget tomrom lukk () kaster Unntak; // Still ObjectFactory som skal brukes til å lage forekomster ugyldig setFactory (PoolableObjectFactory-fabrikk) kaster IllegalStateException, UnsupportedOperationException;

De ObjectPool grensesnittets implementering tar en PoolableObjectFactory som et argument i konstruktørene, og derved delegere objektskaping til underklassene. Jeg snakker ikke mye om designmønstre her, siden det ikke er vårt fokus. For lesere som er interessert i å se på UML-klassediagrammene, se Ressurser.

Som nevnt ovenfor, klassen org.apache.commons.GenericObjectPool er bare en implementering av org.apache.commons.ObjectPool grensesnitt. Rammeverket gir også implementeringer for nøkkelobjekter som bruker grensesnittene org.apache.commons.KeyedObjectPoolFactory og org.apache.commons.KeyedObjectPool, hvor man kan knytte et basseng til en nøkkel (som i HashMap) og dermed administrere flere bassenger.

Nøkkelen til en vellykket pooling-strategi avhenger av hvordan vi konfigurerer bassenget. Dårlig konfigurerte bassenger kan være ressurssvin, hvis konfigurasjonsparametrene ikke er godt innstilt. La oss se på noen viktige parametere og deres formål.

Konfigurasjonsdetaljer

Bassenget kan konfigureres ved hjelp av GenericObjectPool.Config klasse, som er en statisk indre klasse. Alternativt kan vi bare bruke GenericObjectPoolsetter metoder for å sette verdiene.

Følgende liste viser noen av de tilgjengelige konfigurasjonsparametrene for GenericObjectPool gjennomføring:

  • maxIdle: Maksimalt antall soveforekomster i bassenget, uten at ekstra gjenstander frigjøres.
  • minIdle: Minimum antall soveforekomster i bassenget, uten at det blir opprettet ekstra objekter.
  • maxActive: Maksimalt antall aktive forekomster i bassenget.
  • timeBetweenEvictionRunsMillis: Antall millisekunder som skal sove mellom løpene på tomgangsobjekt-utløsertråden. Når det er negativt, kjører ingen tomgangsobjekt-tråden. Bruk denne parameteren bare når du vil at evictor-tråden skal kjøre.
  • minEvictableIdleTimeMillis: Minste tid et objekt, hvis det er aktivt, kan sitte inaktiv i bassenget før det er kvalifisert for utkastelse av tomgangsobjektutkasteren. Hvis en negativ verdi blir gitt, blir ingen gjenstander kastet ut på grunn av inaktiv tid alene.
  • testOnBorrow: Når "sant" blir objekter validert. Hvis objektet mislykkes med validering, vil det bli droppet fra bassenget, og bassenget vil prøve å låne et annet.

Optimale verdier bør gis for de ovennevnte parametrene for å oppnå maksimal ytelse og gjennomstrømning. Siden bruksmønsteret varierer fra applikasjon til applikasjon, må du stille bassenget med forskjellige kombinasjoner av parametere for å komme til den optimale løsningen.

For å forstå mer om bassenget og dets interne, la oss implementere et trådbasseng.

Foreslåtte krav til trådbasseng

Anta at vi fikk beskjed om å designe og implementere en trådbassengkomponent for en jobbplanlegger for å utløse jobber ved spesifiserte tidsplaner og rapportere fullføring og muligens resultatet av utførelsen. I et slikt scenario er målet for trådgruppen vår å samle et forutgående antall tråder og utføre de planlagte jobbene i uavhengige tråder. Kravene er oppsummert som følger:

  • Tråden skal kunne påkalle hvilken som helst vilkårlig klassemetode (den planlagte jobben)
  • Tråden skal kunne returnere resultatet av en henrettelse
  • Tråden skal kunne rapportere fullføringen av en oppgave

Det første kravet gir rom for en løst koblet implementering, da det ikke tvinger oss til å implementere et grensesnitt som Kjørbar. Det gjør det også enkelt å integrere. Vi kan implementere vårt første krav ved å gi tråden følgende informasjon:

  • Navnet på klassen
  • Navnet på metoden som skal påberopes
  • Parametrene som skal overføres til metoden
  • Parametertypene til parametrene som er sendt

Det andre kravet gjør det mulig for en klient som bruker tråden, å motta utførelsesresultatet. En enkel implementering vil være å lagre resultatet av utførelsen og gi en tilgangsmetode som getResult ().

Det tredje kravet er noe relatert til det andre kravet. Rapportering av en oppgaves fullføring kan også bety at klienten venter på å få resultatet av utførelsen. For å håndtere denne muligheten kan vi tilby en eller annen form for tilbakeringingsmekanisme. Den enkleste tilbakeringingsmekanismen kan implementeres ved hjelp av java.lang.Objekts vente() og gi beskjed() semantikk. Alternativt kan vi bruke Observatør mønster, men foreløpig la vi holde ting enkelt. Du kan bli fristet til å bruke java.lang.Tråd klassen bli med() metoden, men det fungerer ikke siden den samlede tråden aldri fullfører løpe() metode og fortsetter å kjøre så lenge bassenget trenger det.

Nå som vi har våre krav klare og en grov ide om hvordan vi skal implementere trådbassenget, er det på tide å gjøre noen reelle kodinger.

På dette stadiet ser vårt UML-klassediagram over den foreslåtte designen ut som figuren nedenfor.

Implementering av trådbassenget

Trådobjektet vi skal samle er faktisk en omvikling rundt trådobjektet. La oss kalle innpakningen ArbeiderTråd klasse, som utvider java.lang.Tråd klasse. Før vi kan begynne å kode ArbeiderTråd, må vi implementere rammekravene. Som vi så tidligere, må vi implementere PoolableObjectFactory, som fungerer som en fabrikk, for å skape vår bassengbare ArbeiderTråds. Når fabrikken er klar, implementerer vi ThreadPool ved å utvide GenericObjectPool. Så avslutter vi ArbeiderTråd.

Implementering av PoolableObjectFactory-grensesnittet

Vi begynner med PoolableObjectFactory grensesnitt og prøv å implementere de nødvendige livssyklusmetodene for trådgruppen vår. Vi skriver fabrikkklassen ThreadObjectFactory som følger:

offentlig klasse ThreadObjectFactory implementerer PoolableObjectFactory {

public Object makeObject () {return new WorkerThread (); } public void destroyObject (Object obj) {if (obj instanceof WorkerThread) {WorkerThread rt = (WorkerThread) obj; rt.setStopped (true); // Gjør den løpende tråden til å stoppe}} public boolean validateObject (Object obj) {if (obj instanceof WorkerThread) {WorkerThread rt = (WorkerThread) obj; if (rt.isRunning ()) {if (rt.getThreadGroup () == null) {return false; } returner sant; }} returner sant; } offentlig ugyldig aktivertObjekt (Objekt obj) {log.debug ("aktiverObjekt ..."); }

public void passivateObject (Object obj) {log.debug ("passivateObject ..." + obj); if (obj instanceof WorkerThread) {WorkerThread wt = (WorkerThread) obj; wt.setResult (null); // Rydd opp resultatet av utførelsen}}}

La oss gå gjennom hver metode i detalj:

Metode makeObject () skaper ArbeiderTråd gjenstand. For hver forespørsel blir bassenget sjekket for å se om et nytt objekt skal opprettes eller et eksisterende objekt skal brukes på nytt. For eksempel, hvis en bestemt forespørsel er den første forespørselen og bassenget er tomt, blir ObjectPool implementering samtaler makeObject () og legger til ArbeiderTråd til bassenget.

Metode destroyObject () fjerner ArbeiderTråd objekt fra bassenget ved å sette et boolsk flagg og derved stoppe den løpende tråden. Vi vil se på dette stykket igjen senere, men legger merke til at vi nå tar kontroll over hvordan objektene våre blir ødelagt.

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