Programování

Sdružujte prostředky pomocí Apache Commons Pool Framework

Sdružování prostředků (nazývané také sdružování objektů) mezi více klienty je technika používaná k podpoře opětovného použití objektů a ke snížení režie při vytváření nových zdrojů, což vede k lepšímu výkonu a propustnosti. Představte si náročnou serverovou aplikaci Java, která odesílá stovky dotazů SQL otevíráním a zavíráním připojení pro každý požadavek SQL. Nebo webový server, který obsluhuje stovky požadavků HTTP a zpracovává každý požadavek vytvořením samostatného vlákna. Nebo si představte vytvoření instance analyzátoru XML pro každý požadavek na analýzu dokumentu bez opětovného použití instancí. Toto jsou některé ze scénářů, které vyžadují optimalizaci použitých zdrojů.

Využití zdrojů se může občas ukázat jako rozhodující pro náročné aplikace. Některé slavné webové stránky byly vypnuty z důvodu neschopnosti zvládnout těžká břemena. Většinu problémů souvisejících s těžkými břemeny lze řešit na úrovni maker pomocí funkcí klastrování a vyrovnávání zátěže. Na úrovni aplikace zůstávají obavy ohledně nadměrného vytváření objektů a dostupnosti omezených serverových zdrojů, jako jsou paměť, CPU, vlákna a připojení k databázi, což by mohlo představovat potenciální úzká místa, a pokud nebudou optimálně využity, zničit celý server.

V některých situacích by zásady použití databáze mohly vynutit omezení počtu souběžných připojení. Externí aplikace by také mohla diktovat nebo omezovat počet souběžných otevřených připojení. Typickým příkladem je registr domény (například Verisign), který omezuje počet dostupných aktivních připojení soketu pro registrátory (jako BulkRegister). Ukázalo se, že sdružování zdrojů je jednou z nejlepších možností při řešení těchto typů problémů a do určité míry také pomáhá udržovat požadované úrovně služeb pro podnikové aplikace.

Většina prodejců aplikačních serverů J2EE poskytuje sdružování zdrojů jako nedílnou součást svých kontejnerů Web a EJB (Enterprise JavaBean). U databázových připojení prodejce serveru obvykle poskytuje implementaci Zdroj dat rozhraní, které funguje ve spojení s dodavatelem ovladačů JDBC (Java Database Connectivity) ConnectionPoolDataSource implementace. The ConnectionPoolDataSource implementace slouží jako továrna na připojení správce zdrojů pro sdružené java.sql.Connection předměty. Podobně jsou instance EJB fazolí bezstavové relace, fazolí založených na zprávách a entitních fazolí sdružovány v kontejnerech EJB pro vyšší propustnost a výkon. Instance analyzátoru XML jsou také kandidáty na sdružování, protože vytváření instancí analyzátoru spotřebovává velkou část prostředků systému.

Úspěšnou implementací sdružování zdrojů s otevřeným zdrojovým kódem je DBCP Commons Pool framework, komponenta sdružování databázových připojení od Apace Software Foundation, která se ve velké míře používá v podnikových aplikacích produkční třídy. V tomto článku stručně pojednám o vnitřních částech rámce Commons Pool a poté jej použiji k implementaci fondu vláken.

Nejprve se podívejme na to, co rámec poskytuje.

Rámec Commons Pool

Rámec Commons Pool nabízí základní a robustní implementaci pro sdružování libovolných objektů. K dispozici je několik implementací, ale pro účely tohoto článku používáme nejobecnější implementaci, GenericObjectPool. Používá a CursorableLinkedList, což je implementace dvojnásobně propojeného seznamu (součást Jakarta Commons Collections), jako základní datová struktura pro uchovávání sdružovaných objektů.

Nahoře rámec poskytuje sadu rozhraní, která dodávají metody životního cyklu a pomocné metody pro správu, monitorování a rozšiřování fondu.

Rozhraní org.apache.commons.PoolableObjectFactory definuje následující metody životního cyklu, které se ukáží jako zásadní pro implementaci komponenty sdružování:

 // Vytvoří instanci, kterou může vrátit veřejný objekt fondu makeObject () {} // Zničí instanci, kterou již bazén nepotřebuje, veřejné void destruObject (objekt obj) {} // Ověřit objekt před použitím veřejného boolean validateObject (Object obj) {} // Inicializovat instanci, která má být vrácena veřejným voidem fondu ActivObject (Object obj) {} // Uninitializovat instanci, která má být vrácena do veřejného public void passivateObject (Object obj) {}

Jak můžete zjistit podle podpisů metody, toto rozhraní se primárně zabývá následujícími:

  • makeObject (): Implementace vytvoření objektu
  • destruObject (): Implementujte zničení objektu
  • validateObject (): Ověřte objekt před použitím
  • activObject (): Implementujte inicializační kód objektu
  • passivateObject (): Implementujte kód neinicializace objektu

Další základní rozhraní -org.apache.commons.ObjectPool—Definuje následující metody pro správu a monitorování fondu:

 // Získat instanci z mého fondu Object loanObject () throws Exception; // Vrátí instanci do mého bazénu void returnObject (Object obj) vyvolá výjimku; // Invalidates an object from the pool void invalidateObject (Object obj) throws Exception; // Používá se k předběžnému načtení fondu nečinnými objekty void addObject () vyvolá výjimku; // Vrátí počet nečinných instancí int getNumIdle () vyvolá UnsupportedOperationException; // Vrátí počet aktivních instancí int getNumActive () vyvolá UnsupportedOperationException; // Vymaže nečinné objekty void clear () vyvolá Exception, UnsupportedOperationException; // Close the pool void close () throws Exception; // Nastaví ObjectFactory k použití pro vytváření instancí void setFactory (továrna PoolableObjectFactory) hodí IllegalStateException, UnsupportedOperationException;

The ObjectPool implementace rozhraní trvá a PoolableObjectFactory jako argument ve svých konstruktorech, čímž deleguje vytváření objektů na své podtřídy. Nemluvím tu moc o designových vzorech, protože to není naše zaměření. Pro čtenáře, kteří mají zájem o prohlížení diagramů tříd UML, si přečtěte zdroje.

Jak bylo uvedeno výše, třída org.apache.commons.GenericObjectPool je pouze jednou implementací org.apache.commons.ObjectPool rozhraní. Rámec také poskytuje implementace pro fondy klíčovaných objektů pomocí rozhraní org.apache.commons.KeyedObjectPoolFactory a org.apache.commons.KeyedObjectPool, kde lze přidružit fond ke klíči (jako v HashMap) a tedy spravovat více fondů.

Klíč k úspěšné strategii sdružování závisí na tom, jak nakonfigurujeme fond. Špatně nakonfigurované fondy mohou být zdroji prasat, pokud konfigurační parametry nejsou dobře naladěny. Podívejme se na některé důležité parametry a jejich účel.

Podrobnosti o konfiguraci

Fond lze konfigurovat pomocí GenericObjectPool.Config třída, což je statická vnitřní třída. Alternativně můžeme použít GenericObjectPoolnastavovací metody pro nastavení hodnot.

Následující seznam podrobně popisuje některé z dostupných konfiguračních parametrů pro GenericObjectPool implementace:

  • maxIdle: Maximální počet instancí spánku ve fondu, aniž by byly uvolněny další objekty.
  • min: Minimální počet instancí spánku ve fondu bez vytváření dalších objektů.
  • maxAktivní: Maximální počet aktivních instancí ve fondu.
  • timeB BetweenEvictionRunsMillis: Počet milisekund ke spánku mezi běhy podprocesu vyprazdňování nečinných objektů. Pokud je záporné, nebude spuštěno žádné vlákno evictor nečinného objektu. Tento parametr použijte pouze v případě, že chcete spustit podproces evictor.
  • minEvictableIdleTimeMillis: Minimální doba, po kterou může objekt, pokud je aktivní, nečinně sedět v bazénu, než je způsobilý k vystěhování ze strany vyřazovacího objektu. Je-li zadána záporná hodnota, nejsou vyřazeny žádné objekty pouze kvůli nečinnosti.
  • testOnBorrow: Když je hodnota „true“, objekty jsou ověřeny. Pokud objekt selže při ověřování, bude vyřazen z fondu a fond se pokusí vypůjčit si další.

K dosažení maximálního výkonu a propustnosti by měly být pro výše uvedené parametry poskytnuty optimální hodnoty. Vzhledem k tomu, že se vzor použití liší od aplikace k aplikaci, vylaďte fond různými kombinacemi parametrů, abyste dosáhli optimálního řešení.

Abychom pochopili více o fondu a jeho interních funkcích, pojďme implementovat fond vláken.

Navrhované požadavky na fond vláken

Předpokládejme, že nám bylo řečeno, abychom navrhli a implementovali komponentu fondu podprocesů pro plánovač úloh, aby spouštěli úlohy v zadaných plánech a hlásili dokončení a případně výsledek provedení. V takovém scénáři je cílem našeho fondu podprocesů shromáždit požadovaný počet podprocesů a spustit naplánované úlohy v nezávislých podprocesech. Požadavky jsou shrnuty takto:

  • Vlákno by mělo být schopné vyvolat libovolnou metodu libovolné třídy (naplánovanou úlohu)
  • Vlákno by mělo být schopné vrátit výsledek provedení
  • Vlákno by mělo být schopné hlásit dokončení úkolu

První požadavek poskytuje prostor pro volně vázanou implementaci, protože nás nenutí implementovat podobné rozhraní Spustitelný. Rovněž usnadňuje integraci. Náš první požadavek můžeme implementovat poskytnutím vlákna s následujícími informacemi:

  • Název třídy
  • Název metody, která má být vyvolána
  • Parametry, které mají být předány metodě
  • Typy parametrů předaných parametrů

Druhý požadavek umožňuje klientovi používajícímu vlákno k získání výsledku provedení. Jednoduchou implementací by bylo uložit výsledek provedení a poskytnout přístupovou metodu jako getResult ().

Třetí požadavek do jisté míry souvisí s druhým požadavkem. Hlášení o dokončení úkolu může také znamenat, že klient čeká na získání výsledku provedení. Abychom tuto schopnost zvládli, můžeme poskytnout nějakou formu mechanismu zpětného volání. Nejjednodušší mechanismus zpětného volání lze implementovat pomocí java.lang.Objectje Počkejte() a oznámit() sémantika. Alternativně bychom mohli použít Pozorovatel vzor, ​​ale prozatím nechme věci jednoduché. Můžete být v pokušení použít java.lang.Thread třídy připojit() metoda, ale to nebude fungovat, protože sdružené vlákno nikdy nedokončí své běh() metoda a běží tak dlouho, jak to fond potřebuje.

Nyní, když máme připravené naše požadavky a hrubý nápad, jak implementovat fond vláken, je čas udělat nějaké skutečné kódování.

V této fázi vypadá náš třídní diagram UML navrhovaného designu jako na obrázku níže.

Implementace fondu vláken

Objekt vlákna, který se chystáme sdružovat, je ve skutečnosti obal kolem objektu vlákna. Zavolejme obal WorkerThread třída, která rozšiřuje java.lang.Thread třída. Než můžeme začít kódovat WorkerThread, musíme implementovat rámcové požadavky. Jak jsme viděli dříve, musíme provést PoolableObjectFactory, která funguje jako továrna, aby vytvořila naši poolable WorkerThreads. Jakmile je továrna připravena, implementujeme ThreadPool rozšířením GenericObjectPool. Pak jsme skončili WorkerThread.

Implementace rozhraní PoolableObjectFactory

Začínáme s PoolableObjectFactory rozhraní a zkuste implementovat nezbytné metody životního cyklu pro náš fond vláken. Píšeme tovární třídu ThreadObjectFactory jak následuje:

veřejná třída ThreadObjectFactory implementuje PoolableObjectFactory {

public Object makeObject () {return new WorkerThread (); } public void destruObject (Object obj) {if (obj instanceof WorkerThread) {WorkerThread rt = (WorkerThread) obj; rt.setStopped (true); // Zastavení běžícího vlákna}} public boolean validateObject (Object obj) {if (obj instanceof WorkerThread) {WorkerThread rt = (WorkerThread) obj; if (rt.isRunning ()) {if (rt.getThreadGroup () == null) {return false; } návrat true; }} návrat true; } public void ActivateObject (Object obj) {log.debug ("ActivObject ..."); }

public void passivateObject (Object obj) {log.debug ("passivateObject ..." + obj); if (obj instanceof WorkerThread) {WorkerThread wt = (WorkerThread) obj; wt.setResult (null); // Vyčistěte výsledek popravy}}}

Projdeme si podrobně každou metodu:

Metoda makeObject () vytváří WorkerThread objekt. U každého požadavku se zkontroluje fond, aby se zjistilo, zda má být vytvořen nový objekt nebo zda má být znovu použit stávající objekt. Například pokud je konkrétní požadavek prvním požadavkem a fond je prázdný, ObjectPool volání implementace makeObject () a přidává WorkerThread do bazénu.

Metoda destruObject () odstraní WorkerThread objekt z fondu nastavením booleovského příznaku a tím zastavením běžícího vlákna. Na tento kousek se podíváme znovu později, ale všimněte si, že nyní přebíráme kontrolu nad tím, jak jsou naše objekty ničeny.

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