Thread pooly v Play! frameworku, šité aplikacím na míru

Play! framework se chlubí vysokou škálovatelností, za kterou vděčí implementaci poolu obslužných vláken. Aby bylo možné nasadit framework s klidným svědomím i na produkci, je potřeba mj. hlouběji proniknout právě do použití thread poolů a jejich konfigurace.

Asynchronní vs. synchronní zpracování

Thread pooly zpracovávající požadavky jsou při výchozím nastavení standardně nakonfigurovány pro aplikace používající asynchronní zpracování requestů. Pokud je charakter operací v aplikaci opravdu neblokující, tj. jsou použity asynchronní databázové drivery, aplikace nepoužívá dlouhé blokující I/O operace apd., vlákna nejsou zbytečně blokována dlouho trvajícími synchronními operacemi a obslužné vlákno je schopné okamžitě přejít k vyřizování dalšího požadavku, zatímco původní požadavek čeká ve frontě na konečné odbavení - než budou opravdu k dispozici výsledná data. Frameworku tak stačí pro obsluhu požadavků i poměrně malý počet vláken.

Pokud se vyskytují v aplikaci časově náročnější blokující operace, je potřeba je provádět nejlépe v separátním thread poolu, který bude mít nakonfigurováno (podstatně) více vláken, abychom měli dostatek prostředků na vyřízení většího počtu requestů i za podmínek, že vlákna budou poměrně dlouhou dobu blokována. Vlastní separátní thread pooly pro jednotlivé typy blokujících operací si můžeme ve frameworku nakonfigurovat.

Obecně platí, že i když jsou vlákna v pracovním poolu všechna vytížená, jsou přijímány nové requesty a zařazeny do fronty ke spuštění. Pokud je requestů hodně, odpovídajícím způsobem se prodlužuje také čekání na response.

ForkJoinPool vs. ThreadPoolExecutor

Díky neblokujícím voláním, která lze ve frameworku standardně používat, a okamžité recyklaci (znovupoužití) vláken může být framework schopen vyřídit opravdu velké množství požadavků. Doug Lea přepracoval implementaci spouštění tasků v thread poolu (původní ThreadPoolExecutor a ForkJoinPool z JDK 7) a nová implementace ForkJoinPoolu (některá vylepšení ForkJoinPoolu jsou výhledově dostupná pro JDK 8) se nyní používá v Play! frameworku jako výchozí vysoce škálovatelný pool obslužných vláken.

Typy thread poolů ve frameworku

Thread pooly v Play! 2.1 se docela liší od thread poolů dostupných v Play! 2.0. Výchozí velikost poolů je typicky nastavena na počet dostupných procesorů. Velikost poolů lze nastavovat v konfiguračním souboru Play! aplikace application.conf. Blíže se podíváme na pooly v novější verzi 2.1.

Interní pooly frameworku

  • Netty boss/worker thread pools - vyhrazené pouze pro Netty server, nad kterým Play! aplikace běží.
  • Play Internal Thread Pool - vyhrazen pouze pro interní použití Play! frameworkem.

Thread pooly použitelné aplikací

  • Play default thread pool - výchozí thread pool, ve kterém je spouštěn veškerý kód aplikace, kromě některého kódu, který používá knihovnu iteratees, a kódu, který definuje a spouští vlastní aktory pomocí Akka pluginu pro Play!. Default thread pool je Akka dispatcherem a konfiguruje se pomocí standardní konfigurace pro Akku.
  • Iteratee thread pool - používán knihovnou iteratees.
  • Akka thread pool - používán Akka pluginem pro Play!. Konfigurace je stejná jako konfigurace prováděná přímo v Akka frameworku.
  • Uživatelsky definovaný thread pool, typicky pro spouštění určitého typu blokujících operací.

V Play! 2.0 existoval např. i pool pro actions-dispatcher, ve kterém se spouštěl kód akcí controllerů obsluhujících požadavky, a pool pro promises-dispatcher. V Play! 2.1 jsou tyto pooly nahrazeny default thread poolem.

Spouštění kódu ve vlastním thread poolu

Standardně se provádí spouštění v default thread poolu:

Můžeme si nadefinovat vlastní thread pooly, což je vhodné pro všechny déle trvající blokující operace. Pokud chceme přesnou kontrolu nad jednotlivými typy prováděných operací, můžeme např. vytvořit a používat různé thread pooly pro méně náročná a náročná čtení z databáze, zápisy do databáze, jiné I/O operace a náročné výpočty zatěžující CPU.

Pro vytvoření a použití poolu je potřeba nejdříve nadefinovat implicitní kontext poolu:

a nastavit parametry poolu v application.conf:

V poolu bude při tomto nastavení iniciálně 1.0 x (počet procesorů) vláken a maximálně 200 vláken. Spuštění kódu v našem poolu pak vypadá následovně:

Anebo s explicitním předáním kontextu poolu takto:

Rozložením blokujících operací na vlastní thread pooly a odladěním parametrů poolů pak můžeme získat řešení šité na míru aplikaci, díky kterému dokáže aplikace zpracovat paralelně velké množství requestů. Neblokující operace můžeme nechat spouštět ve výchozím thread poolu Play! frameworku. Také méně náročné I/O operace můžeme ještě spouštět ve výchozím poolu, pokud v něm navýšíme max. počet vláken.

Pokud chceme, můžeme specifické náročné operace spouštět ve vyhrazeném vlastním poolu s malým počtem vláken, a kontrolovat tak max. počet paralelně spuštěných náročných operací, kterých by nemělo být mnoho.

Článek obsahuje 0 komentářů