MASARYKOVA UNIVERZITA FAKULTA INFORMATIKY
Optimalizace aplikací Java/EE Diplomová práce
Bc. Martin Vala
Brno, 2014
Prohlášení
Prohlašuji, že tato práce je mým původním autorským dílem, které jsem vypracoval samostatně. Všechny zdroje, prameny a literaturu, které jsem při vypracování používal nebo z nich čerpal, v práci řádně cituji s uvedením úplného odkazu na příslušný zdroj.
Bc. Martin Vala
Vedoucí práce: Ing. Mgr. et Mgr. Zdeněk Říha, Ph.D.
Poděkování
Děkuji svému vedoucímu práce Ing. Mgr. et Mgr. Zdeňku Říhovi, Ph.D. za rady a připomínky, které mi pomohly při tvorbě práce. Děkuji také společnosti SeaComp s.r.o. za možnost zpracovat dané téma a za cenné konzultace. V neposlední řadě děkuji své rodině a blízkým za podporu při studiu a psaní práce.
Shrnutí
Cílem této diplomové práce je zmapovat možnosti hledání a optimalizace problémových častí aplikace na platformě Java EE. Tyto poznatky poté aplikovat na systémy používané v reálném prostředí. V praktické části práce byly vytvořeny dvě aplikace. První aplikace je modelem reálné logistické aplikace, která byla použita pro zhodnocení migrace na novější aplikační servery. Druhá aplikace analyzuje vazby mezi entitami.
Klíčová slova
optimalizace, Java EE, profilování, aplikační server, testování výkonu
Obsah
1 Úvod .......................................................................................................................................... 1 2 Aplikace na platformě Java EE ............................................................................................ 3 2.1 Aplikační model ............................................................................................................... 3 2.1.1 Klientská vrstva ......................................................................................................... 5 2.1.2 Webová vrstva ........................................................................................................... 6 2.1.3 Aplikační vrstva ........................................................................................................ 7 2.1.4 Integrační vrstva ........................................................................................................ 7 2.2 Aplikační servery ............................................................................................................. 7 2.2.1 JBoss AS (WildFly) .................................................................................................... 8 2.2.2 Glassfish ...................................................................................................................... 9 3 Výkon Java EE aplikací ....................................................................................................... 10 3.1 Definice výkonu aplikací............................................................................................... 11 3.1.1 Odezva pro koncového uživatele.......................................................................... 11 3.1.2 Propustnost požadavků ......................................................................................... 11 3.1.3 Využití zdrojů .......................................................................................................... 12 3.1.4 Dostupnost aplikace ................................................................................................ 14 3.1.5 SLA ............................................................................................................................ 14 3.2 Aplikační výkonnostní management .......................................................................... 14 3.2.1 APM ve fázi návrhu ................................................................................................ 15 3.2.2 APM ve fázi vývoje ................................................................................................. 16 3.2.3 APM ve fázi kontroly kvality ................................................................................ 16 3.2.4 APM v předprodukci .............................................................................................. 17 3.2.5 APM v produkci ...................................................................................................... 17 3.3 Základní důvody problémů s výkonem aplikací ...................................................... 18 3.3.1 Rozdělení aplikace do vrstev ................................................................................. 18 3.3.2 Špatné plánování kapacity systému ..................................................................... 19 3.3.3 Aplikační rámce a pomocné aplikace ................................................................... 19 3.3.4 Flexibilita Javy.......................................................................................................... 19
3.4 Měření výkonu ............................................................................................................... 20 3.4.1 Získávání aplikačních metrik ................................................................................ 21 3.4.2 Získávání metrik aplikačního serveru .................................................................. 22 3.4.3 Získávání metrik JVM ............................................................................................. 22 3.4.4 Interpretace dat ........................................................................................................ 24 3.4.5 Agregace dat ............................................................................................................ 25 3.5 Profilování ....................................................................................................................... 25 3.5.1 Profilování paměti ................................................................................................... 26 3.5.2 Profilování kódu ...................................................................................................... 26 3.5.3 Profilování pokrytí .................................................................................................. 27 3.5.4 VisualVM .................................................................................................................. 27 3.5.5 JProfiler ..................................................................................................................... 30 4 Využití optimalizačních technik v praxi.......................................................................... 34 4.1 Porovnání aplikačních serverů na logistické aplikaci ............................................... 34 4.1.1 Popis modelu skladové aplikace ........................................................................... 35 4.1.2 Porovnání aplikačních serverů pomocí skladové aplikace ............................... 36 4.1.3 Zhodnocení aplikačních serverů ........................................................................... 40 4.2 Aplikace pro analýzu vazeb mezi entitami v JPA ..................................................... 40 4.2.1 Java Persistance API a vazby mezi entitami ........................................................ 40 4.2.2 Implementace aplikace a výstup ........................................................................... 42 4.2.3 Zhodnocení aplikace na analýzu vazeb ............................................................... 44 5 Závěr ....................................................................................................................................... 45 6 Použitá literatura .................................................................................................................. 47 7 Přílohy .................................................................................................................................... 49
1 Úvod
Mnoho aplikací se v poslední době potýká s problémy s výkonem, což je dáno hlavně rostoucí složitostí moderních systémů. Požadavky na výkon jsou stále stejné, ale složitost dnešních aplikací se se staršími nedá vůbec srovnat. Některé vývojářské firmy nedostatečně testují výkon svých systémů před vydáním a zátěžové testy dost často vynechávají úplně. Tyto aplikace v reálném prostředí pravidelně selhávají a nesplňují očekávání a požadavky zákazníka. Oprava takového problému u systému, který je už v produkci, je nesrovnatelně náročnější, než při odhalení stejného problému v dřívějších fázích vývoje. Proto je důležité se při vývoji složitých aplikací soustředit také na výkon při velké zátěži. Problémy s výkonem se nevyhýbají ani vývoji na platformě Java Enterprise Edition. Specifické pro aplikace na této platformě je, že jsou rozděleny do vrstev a každá vrstva může běžet na jiném stroji. Pokud se objeví problém s výkonem, je potřeba zanalyzovat všechny vrstvy a nejdříve najít, ve které se problém nachází. U větších aplikací poté musí nastoupit specialista na danou vrstvu a pokusit se problém vyřešit. Tento postup je velice časově náročný, a proto je vhodná prevence a dostatečné výkonnostní testování. I problémy v hotových aplikacích se ale dají řešit. Existuje slušné množství profilovacích nástrojů, které v těchto případech mohou pomoci odhalit spoustu problémů, jak u aplikací ve vývoji, tak u těch v produkci. Při optimalizaci platí pravidlo, že dvacet procent úsilí dokáže vyřešit osmdesát procent problémů, takže použití profilovacího nástroje na odhalení chyb a následná optimalizace může každé aplikaci pomoci. Cílem této diplomové práce je zmapovat možnosti hledání a optimalizace problémových částí aplikací na platformě Java EE. Tyto poznatky poté aplikovat na reálné problémy systémů používaných ve firmě SeaComp s.r.o., která diplomovou práci zaštiťuje. V kapitole 2 Aplikace na platformě Java EE popisuji fungování a technologie používané při vývoji aplikací na této platformě. Jsou zde popsány všechny jejich vrstvy, fungování a používané technologie. Poslední část kapitoly je věnována aplikačním serverům. V kapitole 3 Výkon Java EE aplikací se zabývám teorií problémů s výkonem a jejich možným řešením. Jsou zde popsána nejčastější problémová místa aplikací, u kterých je
1
potřebná zvýšená pozornost při vývoji. Na konci se práce zabývá měřením výkonu a jsou zde i ukázky použití profilovacích nástrojů. V kapitole 4 Využití optimalizačních technik v praxi využiji načerpané poznatky k vytvoření dvou aplikací, které by měly pomoci řešit reálné problémy firemních systémů. V první části je vytvořena jednoduchá skladová aplikace, na které je zhodnocena možnost migrace na novější servery. Ve druhé části je vytvořena aplikace, která analyzuje vazby mezi entitami a ceny jejich načítání.
2
2 Aplikace na platformě Java EE
Java 2 Enteprise Edition (J2EE) je platforma, která byla navrhnuta, aby zjednodušila vývoj velkých distribuovaných aplikací. Programátor se může více soustředit na části, které jsou specifické pro jeho projekt, jelikož J2EE dodává dostatečnou množinu API1, které zkracují čas vývoje, snižují komplexitu systému a zlepšují výkon. Vývojář se tedy nemusí zabývat obecnými problémy, které je potřeba řešit v každé aplikaci [1]. Java EE aplikace jsou vyvíjeny jako množina spolupracujících komponent, které by měly být na sobě co nejméně závislé a znovupoužitelné. Pokud máme takovéto komponenty, je snadné je měnit a přizpůsobovat jejich chovaní [2]. Od verze Java EE 6 se zjednodušuje programovací model, jelikož komponenty už není potřeba konfigurovat pomocí XML 2 , ale stačí jednoduše přidat anotaci přímo do zdrojového kódu vedle elementu, který anotace ovlivňuje. Java EE server poté nakonfiguruje komponenty při nasazení a běhu programu [3]. Dalším zjednodušením je Dependency Injection, které umožňuje automatické vkládání referencí na ostatní potřebné komponenty nebo zdroje pomocí anotací. Dependency Injection se dá aplikovat na všechny zdroje, které komponenta potřebuje a úspěšně skrývá vytváření a vyhledávaní těchto komponent z kódu aplikace.
2.1 Aplikační model Základem Java EE aplikačního modelu je programovací jazyk Java a Java Virtual Machine3, kteří společně poskytují portabilitu, bezpečnost a jednoduchou programovatelnost. Java EE je navržena pro podporu aplikací, které implementují podnikové služby pro zákazníky, zaměstnance, dodavatele, partnery a ostatní, kteří spolupracují s podnikem. Takové aplikace jsou komplexní, přistupují k datům z více zdrojů a distribuují funkcionalitu různým klientům [1]. Proto Java EE platforma používá pro podnikové aplikace vícevrstvý aplikační model. Aplikační logika je rozdělena do komponent podle funkcí a tyto aplikační komponenty jsou instalovány na různých strojích podle vrstvy, do které komponenta patří. Java EE Application Programming Interface specifikuje, jak spolu mají komunikovat jednotlivé softwarové komponenty. 2 Extensible Markup Language 3 JVM – virtuální stroj, který dokáže spouštět Java bytecode tzv. mezikód. 1
3
aplikace se skládají ze tří, nebo čtyř vrstev, podle toho jaké použijeme dělení (Obrázek 1). Dělení podle komponent:
Integrační vrstva (Enterprise Information System Tier) Aplikační vrstva (Business Tier) Webová vrstva (Web Tier) Klientská vrstva (Client Tier)
Dělení podle stroje, na kterém komponenta běží:
Databázový server Aplikační server Počítač klienta
Dělení podle vrstvy:
Perzistence dat Aplikační logika Prezentační vrstva
Obrázek 1. Java EE vícevrstvá aplikace [2].
4
Při dělení podle komponent může mít aplikace tři nebo čtyři vrstvy, jelikož prezentační vrstva může mít buď obě komponenty, tedy klientskou i webovou vrstvu, nebo jen jednu z nich.
2.1.1 Klientská vrstva Klientská vrstva je spouštěna na počítači klienta a komunikuje přímo s ostatními vrstvami běžícími na aplikačním serveru. Pro lepší pochopení klientské vrstvy a komunikace mezi vrstvami je vhodný obrázek 2. 2.1.1.1 Webový klient Webový klient je takzvaným tenkým klientem, který je spouštěn na počítači uživatele. Tenký klient nezadává dotazy do databáze ani nevykonává složité aplikační pravidla. Při použití tenkého klienta jsou tyto složité a náročné operace delegovány do aplikační vrstvy, která běží na serveru. Webový klient se skládá ze dvou částí. Z webových stránek, které obsahují různé druhy značkovacích jazyků jako HTML4 nebo XML. A z webového prohlížeče, který zobrazuje webové stránky, které obdržel ze serveru. Webové stránky mohou také obsahovat applety. Applet je malá klientská aplikace, napsaná v programovacím jazyce Java, která se spouští v JVM nainstalovaném ve webovém prohlížeči. 2.1.1.2 Aplikační klient Aplikační klient je také spouštěn na počítači uživatele a poskytuje mu bohatší možnosti, než mohou nabídnout značkovací jazyky. Aplikační klient obvykle poskytuje grafické uživatelské prostředí (GUI), vytvořené například pomocí API Swing. Existují také klienti bez grafického uživatelského prostředí, kteří jsou ovládáni pomocí příkazové řádky. Aplikační klient má přímý přístup k aplikační logice, která běží na aplikačním serveru. Tito klienti nemusí být napsáni jen v programovacím jazyce Java, ale i aplikace napsané v jiných jazycích mohou s aplikačním serverem komunikovat [1].
4
HyperText Markup Language
5
Obrázek 2. Komunikace mezi vrstvami v Java EE aplikaci [1].
2.1.2 Webová vrstva Webové komponenty jsou buď servlety, nebo webové stránky vytvořené pomocí technologií Java Server Faces, nebo Java Server Pages. Servlety jsou třídy napsané v jazyku Java, které dynamicky zpracovávají požadavky a vytvářejí odpovědi. JSP stránky jsou textové dokumenty, které se spouštějí jako servlety, ale dovolují přirozenější přístup k vytváření statického obsahu. JSF technologie je postavena na servletech a JSP, a poskytuje uživatelské prostředí pro webové aplikace. Statické HTML stránky a applety jsou spojeny s webovými komponentami při nasazování aplikace, ale nejsou považovány za komponenty webové vrstvy z pohledu Java EE specifikace.
6
2.1.3 Aplikační vrstva V aplikační vrstvě se nachází business logika aplikace, která řeší požadavky dané podnikové domény, jako například bankovnictví, prodej, nebo finance. Požadavky jsou zpracovávány v komponentách zvaných Enterprise Bean, které běží na serveru v aplikační vrstvě. Možné jsou tyto komponenty i ve vrstvě webové. Enterprise Bean je, zjednodušeně, třída napsaná v jazyce Java, která běží na aplikačním serveru a řeší určitou část business logiky. Aplikační vrstva přijímá data z klientské a webové vrstvy, zpracuje je a posílá do integrační vrstvy, kde se data ukládají. Také data z integrační vrstvy získává, zpracovává a posílá zpátky do klientské, nebo webové vrstvy [1].
2.1.4 Integrační vrstva V integrační vrstvě se nacházejí databázové systémy, kde se ukládají veškerá data, které aplikace potřebuje. Mohou se zde nacházet také takzvané legacy systems, což může být například předchozí systém, na kterém je nová aplikace postavena a je stále potřebný, nebo jakýkoliv jiný systém, který je potřebný k běhu. K integrační vrstvě přistupuje jen vrstva aplikační, která zde ukládá data, nebo si odtud data bere. O tento přístup se stará JDBC. Java Database Connectivity je API v Javě, která zajišťuje přístup do různých databází. JDBC je orientován na relační databáze, a proto je pro vývoj jednodušší použít nějaký Object-relational mapping (Objektově relační mapování, ORM) nástroj, který implementuje rozhraní Java Persistence API (například Hibernate, TopLink, EclipseLink), který se stará o mapování objektů do relační databáze [2]. Samozřejmě využití ORM nástroje způsobuje jistou režii, a proto je důležité se správně rozhodnout, jestli využít ORM, nebo jestli použít jen JDBC.
2.2 Aplikační servery Aplikační server je prostředí, kde mohou běžet Java EE aplikace. Server se skládá z různých kontejnerů, kde každý poskytuje jiné služby. Můžeme například mít servletový kontejner, který zajišťuje uživatelské prostředí, Enterprise JavaBean kontejner,
7
který zajišťuje business logiku, nebo JNDI 5 kontejner, který zajišťuje vyhledávání jednotlivých služeb [4]. Aplikační servery můžeme dělit následovně [2]:
Plnohodnotné open source o JBoss (WildFly) o Glassfish
Open source pouze se servlet kontejnerem o Tomcat o Jetty
Placené o WebSphere (IBM) o WebLogic (Oracle, dříve BEA)
Nejvíce se tato práce zaměřuje na systémy na aplikačním serveru JBoss, který je ještě s dalším populárním open source serverem Glassfish představen v další části. Ostatní servery jsou zde uvedeny jen pro přehled.
2.2.1 JBoss AS (WildFly) Od verze 5 je aplikační server JBoss vyvíjen pod společností Red Hat, která JBoss koupila v roce 2006. Jedná se o plnohodnotný aplikační server, který je stavebním kamenem balíčku technologíí společnosti Red Hat, jenž se nazývá JBoss Enterprise Middleware Suite (JEMS). Všechny ostatní JEMS produkty fungují s aplikačním serverem JBoss a mnoho z nich může fungovat i bez něj v Java SE aplikacích. Součástí tohoto balíčku je například již zmiňovaný ORM nástroj Hibernate. JBoss zůstává i nadále open source, což ale vůbec nesnižuje jeho hodnotu a je schopný konkurovat i komerčním řešením. Jelikož je ale JBoss dostupný zdarma, jeho popularita se nedá změřit stejně jako u komerčního softwaru, tedy počtem prodaných licencí. Dotazníkové studie jako například ta od BZ Research6 z roku 2005 ale ukazují, že JBoss se v této době dělil s WebSphere od IBM o prvenství v popularitě a server WebLogic je až za nimi.
5 6
Java Naming and Directory Interface – pomáhá vyhledávat zdroje pomocí jména. http://www.bzmedia.com/bzresearch/
8
Takže fakt, že JBoss zůstává nadále open source, má spíše výhody. Každý programátor, který se rozhodne, že by chtěl JBoss zkusit, ho může stáhnout, nainstalovat, spustit a nasadit na něj aplikaci za méně než deset minut. Vše bez toho, aby si musel dělat starosti s licencí. Vývojový proces serveru je navíc naprosto transparentní a každý ho může vidět. Pokud někdo chce a má dostatečné schopnosti, tak může dokonce i vypomoct například nahlašováním bugů, opravováním chyb, nebo i vývojem. Další výhodou je, že kolem populárních open source produktů se srocují velké komunity a tedy většina bugů je nalezena a opravena mnohem dříve, než u ostatních produktů [5]. Nedávno byl aplikační server JBoss přejmenován na WildFly, který začíná své číslování od osmé verze, jelikož JBoss skončil na verzi sedmé. S aplikačním serverem WildFly 8.0.0 se lépe seznámíme v praktické části této práce.
2.2.2 Glassfish Projekt Glassfish začal v roce 2005, pod firmou Sun, která v té době vyvíjela také programovací jazyk Java. První verze vyšla rok poté a podporovala Java EE verze 5 a přispěla k její popularitě. Jedná se o populární open source aplikační server, který je teď podporován firmou Oracle a je už ve čtvrté verzi, která podporuje Java EE verze 7. Jeho popularita pramení také z toho, že nová verze je vždy vydána záhy po vydání nové verze Javy, takže nedočkaví programátoři si vždy mohou s tímto serverem zkusit nové funkce [6]. Rozdílem oproti serveru JBoss je například využití nástroje TopLink, jako standardního ORM poskytovatele, ale samozřejmě není problém na server nainstalovat jiný ORM nástroj. Se serverem GlassFish 4.0 se lépe seznámíme v praktické části této práce.
9
3 Výkon Java EE aplikací
Mnoho firem má problémy s výkonem u svých Java aplikací i v dnešní době, kdy se na výkon hledí mnohem více, než dříve. Steven Haines, velice zkušený programátor zabývající se výkonem Javových aplikací, ve své publikaci [7] udává, že v roce 2005 mělo více než 80 % firem významné výkonnostní problémy se svými systémy v jazyce Java. Požadavky na výkon aplikace jsou definované v tzv. service level agreement (SLA). SLA udávají přesné údaje o tom, jak dlouho by mělo trvat zpracovat nějakou jednotku práce a velké množství aplikací těchto hodnot nedosahuje. Špatný výkon má dopad na produktivitu, zákazníkovu důvěru a příjem. Když má aplikace špatnou dobu odezvy, zákazník platí své zaměstnance za čekání na odpověď programu a tím ztrácí produktivitu zaměstnanců. Tento problém musí být také opraven vývojářem, jenž ztrácí čas, který by mohl věnovat něčemu jinému, například vývoji nových funkcí [8]. Je naprosto přirozené, že zákazník ztrácí důvěru ve firmu, která nedokáže splnit své sliby ohledně výkonu aplikace, což může vést k tomu, že na další důležitý projekt si raději vybere někoho jiného. Toto negativně ovlivňuje příjem firmy a může vést až k existenčním problémům. Symptomy výkonnostních problémů mohou být:
Pomalu běžící aplikace
Úniky paměti, které zpomalují výkon aplikace
Velké úniky paměti, které způsobují pády aplikace
Zamrzávající aplikace
Aplikace, které mají problémy při velké zátěži
Problémy, které se objevují na systému v produkci, ale nemohou být reprodukovány v testovacím prostředí
Aplikace, které postupně zpomalují
10
3.1 Definice výkonu aplikací Z pohledu Java EE aplikací a aplikačních serverů jsou nejpoužívanější výkonnostní metriky odezva pro koncového uživatele, propustnost požadavků, využití zdrojů a dostupnost. Všechny tyto metriky jsou důležité pro definici výkonu jako takového.
3.1.1 Odezva pro koncového uživatele Tato metrika definuje, jak dlouho uživatel musí čekat na jeho požadavek, tedy poukazuje na uživatelův celkový dojem z aplikace, a proto je ze všech ostatních nejvíce na očích. Pomalá odezva má za následek celkovou nespokojenost zákazníka. Malé množství dlouhých odezev je horší, než více kratších. Nejvíce výrazné jsou odezvy, které jsou delší než 90 % všech ostatních. Odezva se dá měřit dvěma způsoby, aktivně a pasivně. Aktivně pomocí syntetických transakcí, které posílají požadavky na naše aplikační prostředí v daném čase a měří se odezva těchto požadavků. Výhodou aktivního měření je, že se dá použít i předtím, než špatná odezva ovlivní našeho uživatele, tedy předtím, než je produkt nasazen. Pasivní monitoring na druhou stranu sleduje požadavky, které se objevují na produktu nasazeném v reálném prostředí, a proto takto dostáváme přesnější data o uživatelově chování. Bohužel problémy s výkonem v tomto případě řešíme až po nasazení aplikace, což může mít negativní dopad na zákazníka.
3.1.2 Propustnost požadavků Propustnost ukazuje, kolik věcí stihne aplikace zpracovat v nějakém časovém úseku. V oblasti Java EE definujeme propustnost požadavků jako počet požadavků, které je aplikace schopna zvládnout za jednu sekundu. Vysoký počet zpracovaných požadavků poukazuje na to, že rychle a efektivně zpracováváme požadavky, což se kladně odráží na odezvě.
11
3.1.3 Využití zdrojů Zdroje podporují, nebo ulehčují práci aplikace a jejich využití je další důležitou metrikou výkonu. Mezi zdroje patří:
Halda
Zásobníky vláken
Zásobníky JDBC připojení
Vyrovnávací paměti
Zásobníky bezstavových komponent (Stateless Bean)
JMS servery
Zásobníky JCA7 připojení
Zdroj, který je vhodné analyzovat jako první je halda. Zajímá nás využívání haldy a chování garbage collectoru. Jelikož celá aplikace běží v haldě, její špatná konfigurace může mít za následek špatný výkon bez ohledu na to, jak dobře je aplikace napsána a jak dobře je nakonfigurován aplikační server. Automatická správa paměti je jedna z funkcí, která láká programátory do vývoje aplikací v Javě. Správa paměti je zjednodušena tak zvaným garbage collectorem, který sám hledá dereferencované objekty a maže je z paměti. Tato funkce je v jednom momentě zároveň požehnáním i prokletím. Na jednu stranu eliminuje úniky paměti známé například z jazyka C++. Na druhou stranu není možné použít manuální správu paměti, protože garbage collector je jediný mechanismus na její správu. Proto je vhodné věnovat dostatek času jeho konfiguraci. Když aplikační server obdrží požadavek od uživatele, vloží ho do fronty požadavků. Tato fronta je udržována zásobníkem vláken, který odebere požadavek z fronty a zpracuje ho. Využití a výkon zásobníku může mít velký vliv na celkový výkon aplikace. Pokud je využití zásobníku malé, tak zbytečně využíváme prostředky serveru, které by mohly být použity jinde. Na druhou stranu pokud je využití
7
Java EE Connector Architecture
12
zásobníku nad 85 %, docházejí nám volná vlákna pro zpracování požadavků a tyto požadavky musejí čekat ve frontě, což snižuje propustnost celé aplikace. Většina aplikací potřebuje ke své práci databázi. Všechny interakce s databází jsou řízeny přes zásobník spojení. Každý požadavek musí nejdříve obdržet spojení s databází ze zásobníku, aby mohl provést svůj dotaz do databáze. Pokud zásobník nemá žádná volná spojení, musí požadavek čekat, což snižuje celkovou dobu odezvy uživatelského požadavku. Vyrovnávací paměť slouží ke snížení počtu volání do databáze. Načtení dat z vyrovnávací paměti je mnohem rychlejší, než volání do databáze, ale vyrovnávací paměť má omezenou velikost, a proto špatná konfigurace může způsobit pády aplikace. V Java EE jsou objekty odebírány z vyrovnávací paměti a vkládány do databáze v procesu zvaném pasivace. Nové požadavky jsou ukládány do mezipaměti v procesu zvaném aktivace. Využití mezipaměti a poměry aktivace a pasivace mohou ovlivnit výkon. Bezstavové komponenty jsou udržovány v zásobnících. Pokud proces potřebuje zdroj, vyžádá si ho ze zásobníku, použije a vrátí zpátky. Tyto zásobníky způsobují problémy s výkonem hlavně, když jsou příliš malé. Důležité je využít na správu zásobníku co nejméně paměti a přitom udržovat dostatečné množství zdrojů k obsloužení všech požadavků. Pokud není v zásobníku dostatek zdrojů, má to za následek rapidní snížení výkonu. Aplikace, které potřebují komunikovat s legacy systémem, nebo používat asynchronní zpracování, tak činí pomocí JMS (Java Message Service) serverů. V JMS serveru je fronta, přes kterou jsou zprávy zpracovávány a velikost této fronty se dá nakonfigurovat. Pokud je JMS server plný, tak do fronty nejdou přidat žádné další zprávy, což má za následek buď ztrátu celého požadavku, nebo zpomalení, protože program zkouší zprávu posílat tak dlouho, dokud neuspěje. Zásobníky JCA spojení jsou podobné JDBC zásobníkům s rozdílem, že pomocí JCA nekomunikujeme s databází, ale s jakýmkoliv systémem, který podporuje JCA. Často se používá na komunikaci s legacy systémy. Pro aplikace, které JCA používá, je správná konfigurace velice důležitá [7].
13
3.1.4 Dostupnost aplikace Dostupnost se měří podle toho, jestli je aplikace dostupná vždy, když je to nutné, což by mělo být definováno ve SLA v podobě procentuální hodnoty dostupnosti. Pokud aplikace není dostupná a porušuje hodnoty dané ve SLA, zákazník může většinou vymáhat nějaké dohodnuté finanční sankce.
3.1.5 SLA Jak bylo řečeno výše, požadavky na výkon aplikace by měly být obsaženy v service level agreement. Tyto požadavky by měly být dobře zváženy všemi klíčovými osobnostmi projektu a je nutná důsledná analýza. Hlavní osobnosti, které se na SLA musejí podílet, jsou aplikační obchodní specialista a technický specialista. Pokud bychom vynechali obchodního specialistu, tak by uživatelovy požadavky nemusely být splněny a pokud bychom vynechali technického specialistu, mohli bychom dostat požadavky, které není možné technicky splnit. Dobře napsané SLA musejí být specifické, flexibilní a realistické. Specifické musejí být proto, abychom mohli při testování zjistit, jestli aplikace tyto hodnoty splňuje. Například není vhodné uvádět, že musí zvládnutí požadavku trvat okolo pěti vteřin, protože tato hodnota není přesná. Flexibilita znamená, že daný požadavek musí obsahovat procentuální hodnotu, kdy může aplikace porušit dané hodnoty ve SLA, protože vždy mohou nastat nějaké neočekávané situace. Například nemusí být považováno za problém, pokud jsou SLA pro daný požadavek porušeny jednou z třiceti případů. Realističnost daných SLA je důležitá a je důvodem, proč u sepisování dohody o SLA musejí být obchodní i technický specialista. Pokud vývojáři dostanou neproveditelné požadavky na výkon aplikace, tak je prostě ignorují a jsme ve stejné situaci, jako bychom žádné neměli [7].
3.2 Aplikační výkonnostní management Aplikační výkonnostní management (Application Performance Management, dále jen APM) je metoda, která zajišťuje výkon aplikace ve všech fázích a cyklech vývoje a nasazení. APM začíná při návrhu, je aplikován při vývoji, je odzkoušen při testování a zůstává, i když je aplikace již v produkci.
14
Důvodem proč se s APM musí začínat již ve fázi návrhu je, že opravení chyby v rámci počáteční fáze vývoje je nepopsatelně levnější, než v pokročilejších fázích, kdy cena opravy roste exponenciálně (Obrázek 3). Například pokud objevíme chybné rozhodnutí ve fázi návrhu, dokumentace může být změněna za několik hodin. Pokud stejnou chybu objevíme ve fázi vývoje, musíme ještě navíc udělat změny v kódu. Pokud se ale chyba najde až ve fázi testování, musí být řešení znovu navrhnuto, naimplementováno a otestováno. Nejhorší je samozřejmě, když se chyba najde až v produkci, protože to může navíc špatně ovlivnit náš vztah se zákazníkem.
Obrázek 3. Závislost mezi cenou opravení problému a fází vývoje [7].
3.2.1 APM ve fázi návrhu Jelikož je důležité objevit problémy s výkonem co nejdříve, měl by návrh systému určitě zohledňovat výkon aplikace, ne-li na něm být založen. Často se stává, že softwarový architekti opomínají výkon ve svých návrzích, i když by bylo vhodné, kdyby všechny návrhy případů užití obsahovaly předem domluvené SLA, což ale vyžaduje další námahu architekta. Diskuze o SLA nemůže být provedena pouze se zákazníkem, ale také s technickým poradcem, kterému zákazník důvěřuje, protože jinak by mohl mít nereálné výkonnostní požadavky na některé prvky aplikace. Takovým nereálným požadavkem může například být, že doba odezvy některého prvku musí být pod jednu vteřinu, i když technický specialista ví, že pod dvě vteřiny není možné něco podobného provést.
15
3.2.2 APM ve fázi vývoje Při vývoji větších aplikací je každá komponenta vytvořena jiným týmem, nebo osobou. Například jeden tým implementuje persistentní vrstvu, jiný tým aplikační vrstvu a jiný prezentační vrstvu. Důležité je aby si každý programátor zodpovědně otestoval všechny podkomponenty, na kterých pracuje pomocí jednotkových testů. Typické jednotkové testy se soustřeďují jen na funkcionalitu a zanedbávají dva důležité aspekty výkonu aplikace: využívání paměti a efektivitu algoritmů. Abychom se vyhnuli problémům s výkonem, vývojáři musejí dobře otestovat využívání paměti a hledat objekty, které zbytečně setrvávají v paměti, nebo cyklí. Efektivita algoritmů se dá zjistit pomocí profilovacích nástrojů, které zanalyzují výpočetní čas řádek po řádku a umožňují tak programátorovi najít problémové části jeho kódu. Dalším důležitým nástrojem při testování výkonu je pomůcka, která ukazuje jak velká a která část kódu je procházena v průběhu testu, jelikož se těžko najde slabé místo v aplikaci, pokud přes něj nikdy neprojde žádný test. Pokud spojujeme dobře otestované komponenty do jednoho celku a objeví se nějaký problém, víme, že tento problém způsobilo spojení částí a ne jednotlivé komponenty, což nám zjednoduší fázi hledání problému. Kdybychom stavěli auto, také bychom chtěli mít jistotu, že veškeré součástky fungují a až potom bychom tyto součástky spojovaly dohromady. Při vývoji auta nám tento postup přijde naprosto přirozený, ale bohužel při vytváření softwaru tyto návyky dost často chybí [7].
3.2.3 APM ve fázi kontroly kvality Po tom co byly všechny komponenty řádně otestovány a spojeny dohromady, nastupuje tým kontroly kvality (Quality Assurance, dále jen QA), aby zjistil, jestli celá aplikace funguje, jak má. V kontextu APM, nestačí aby QA tým otestoval jen správnou funkcionalitu, ale musí také otestovat, jestli je dodržován dostatečný výkon, který byl definován ve SLA. Pokud při testování nějakého případu užití program funguje správně, ale nesplňuje nároky definované ve SLA, pak QA test nemůže projít a celá část se musí vrátit k vývojářskému týmu. K problému s výkonem by se mělo přistupovat stejně jako ke každé jiné chybě v programu, tedy buď je potřeba změnit jen implementaci, nebo dokonce celý návrh problémové části. Pokud necháme testování výkonu aplikace úplně nakonec fáze kontroly kvality, kdy je už celá hotová, zvyšujeme cenu oprav těchto problémů. A pokud jsme při celém vývoji nevěnovali pozornost výkonu, je pravděpodobné, že v této fázi najdeme závažné
16
problémy, které mohou mít za následek i nutnost kompletního předělání nějaké vrstvy a většiny komponentů, které s ní komunikují. Po otestování jednotlivých částí podle definovaných SLA je důležité otestovat celou aplikaci pod zátěží, kterou očekáváme v produkci, na což se často zapomíná a má za následek, že aplikace, která skvěle fungovala pro pár uživatelů při testování, najednou absolutně vyhoří po pár hodinách v produkci. Při velké zátěži bývají největší problémy s neefektivností algoritmů a špatnou prací s pamětí. Tyto problémy se neukáží při jednotkovém testování, jelikož pro nás není problém, pokud relace pro jednoho uživatele zabírá 1 MB paměti. Ale když máme najednou připojených tisíc uživatelů, potom už musíme uchovávat 1 GB, což může vést k pádům nebo zamrzání aplikace.
3.2.4 APM v předprodukci Před nasazením aplikace do produkce by se mělo provést stanovení kapacity. I když jsou splněny SLA při dané zátěži, měli bychom vědět, při jaké zátěži aplikace začne ztrácet rychlost a poruší dané SLA. Pokud jsou definované SLA pro tisíc uživatelů, dosahujeme těchto hodnot jen těsně a přidání dalších deseti uživatelů způsobí dosažení kapacitního stropu, měli bychom se dále zabývat zlepšením výkonu, protože je pravděpodobné, že aplikace v produkci neuspěje. Pokud máme stanovenou kapacitu systému, víme, pod jakou zátěži začneme porušovat dané SLA a kdy aplikace spadne úplně. Tyto informace pomohou při rozhodování, jestli investovat do rychlejšího hardwaru, nebo jestli jsme spokojení s nynějším výkonem.
3.2.5 APM v produkci Když se aplikace dostane do produkce, je potřeba monitorovat uživatele, abychom se ujistili, že se chová, tak jak jsme předpokládali při testování. Pokud jsme testovali výkon při jiných průchodech aplikace, než jaké uživatel opravdu používá, pak se musejí zopakovat zátěžové testy s novými uživatelskými případy užití, které opravdu imitují uživatelské chování. Je těžké dopředu přesně odhadnout, jak bude uživatel aplikaci používat, ale dobrá analýza uživatelského chování je důležitá a zvýší přesnost odhadů. Například se může stát, že budeme předpokládat, že uživatel se pokaždé před zavřením browseru odhlásí z naší internetové stránky. Toto v produkci způsobí nepříjemné úniky paměti, protože většina uživatelů se z internetových stránek
17
neodhlašuje, ale jen zavře browser. Uživatelská relace poté může zůstat v paměti několik hodin, než je tato paměť uvolněna pro další uživatele. Dále musíme v produkci sledovat, jestli aplikace správně využívá zdroje a sledovat odezvu pro koncového uživatele. Je důležité zjistit, jestli jsou uživatelé spokojeni při používání aplikace, tak jak jsme si představovali před nasazením do produkce. Dalším důležitým aspektem v produkci je ukládat si záznamy o užívání, které můžeme využít k odhadu směřování aplikace v budoucnu [7].
3.3 Základní důvody problémů s výkonem aplikací Jedním z důvodů problémů moderních aplikací s výkonem je rychlá evoluce. Požadavky na rychlost aplikace jsou pořád stejné, ale rychle roste jejich složitost. Například rozdíl mezi rychlostí Windows 3.1 a Windows 7 není až tak znatelný, ale složitost těchto operačních systémů se nedá vůbec srovnat. Při zvyšování složitosti je vždy mnohem těžší odhalit výkonnostní problémy [8].
3.3.1 Rozdělení aplikace do vrstev Rozdělení Java EE aplikací do vrstev, může způsobovat problémy při hledání problému s výkonem. Tyto aplikace jsou nasazeny na aplikačním serveru, na kterém běží JVM v operačním systému na fyzickém počítači. V nejjednodušší formě běží všechny tyto komponenty na jednom stroji, čemuž se říká vertikální složitost. Mnohem horší na hledání problémů s výkonem a také mnohem častější varianta je ale horizontální složitost, kde aplikace běží v distribuovaném prostředí. Když se zjistí problém s výkonem, musí se hledat ve všech vrstvách na všech strojích, což je skoro nemožné jen pro jednoho specialistu. Proto pro vyhledání problému v takovýchto rozsáhlých systémech je potřeba tým zkušených specialistů [7]. Vrstvou, ve které jsou často problémy, je ta databázová. Špatná optimalizace SQL dotazů a indexů v databázi zpomaluje výkon objemných aplikací, které často dotazují databázovou vrstvu. Při nevhodném přístupu k datům, může také docházet k zamykání některých tabulek nebo řádků v databázi. Problém mohou dělat také externí nebo legacy systémy, které s naší aplikací komunikují. Dlouhé čekání na odpověď externího systému může mít za následek dominový efekt a pád celé aplikace. Řešením může být zavedení časových limitů, které v případě neobdržení odpovědi z externího systému v nějakém časovém
18
intervalu ohlásí chybu. Důležité je také správně odhadnout a naplánovat, jak na dosažení limitu správně zareagovat [9].
3.3.2 Špatné plánování kapacity systému Problémy s výkonem aplikace mohou začít už ve fázi návrhu. Špatné, nebo dokonce žádné, plánování kapacity systému (capacity planning), může mít za důsledek, že hotová aplikace nezvládá nápor uživatelů v produkci. Proto by se vždy ve fázi návrhu mělo odhadnout, kolik souběžných uživatelů bude k systému přistupovat a podle toho přizpůsobit návrh aplikace, databázi, hardware, na kterém by systém měl běžet a zátěžové testy [9].
3.3.3 Aplikační rámce a pomocné aplikace Problém může být v implementaci zavedených aplikačních rámců (application framework) jako je například Spring8. Používání těchto rámců zjednodušuje a zrychluje vývoj aplikace, ale může také způsobit problémy. Většina vývojářů, kteří je využívají, vědí přesně, jak je používat, ale nevědí, jak fungují vnitřně, což vytváří tzv. černou skřínku, do které vložíme nějaký vstup, ona provede nějakou funkcionalitu a vrátí nám výstup, ale my pořádně nevíme, co provedla. Tímto vlastně v aplikaci vzniká další vrstva, kterou musíme analyzovat, když nastane problém a pokud opravdu zjistíme, že náš problém je v používaném aplikačním rámci, musíme ho buď sami opravit, což vyžaduje jeho dobrou znalost, nebo musíme požádat poskytovatele o opravení chyby, což muže trvat i měsíce. Vývojářská prostředí a pomůcky mohou psaní aplikace ovlivnit podobně jako aplikační rámce. Na jednu stranu nám urychlují a zjednodušují práci, ale na druhou zase člověk většinou nerozumí tomu, co se děje pod pokličkou a tyto nástroje mohou nabídnout nevhodná řešení pro naše nynější potřeby [7].
3.3.4 Flexibilita Javy Java EE je velice flexibilní platforma vhodná jak pro vývoj malých nenáročných aplikací, tak větších podnikových aplikací. Velká flexibilita může způsobit, že se vyvine systém, který funguje skvěle při testování, ale doslova se rozpadne při velké zátěži. Toto může být způsobeno i tím, že se Java v poslední době stává velice 8
http://spring.io/
19
populární platformou a spousta programátorů, přechází na Javu z jiných jazyků, ze kterých si přináší špatné návyky. Proto by v každém úspěšném týmu měl vždy být nějaký zkušený Java vývojář.
3.4 Měření výkonu Ke správnému a užitečnému měření výkonu potřebujeme prvně získat data o výkonu aplikace a tato data poté správně interpretovat. Získání dat je technickou částí, ke které potřebujeme dobrou znalost aplikace a aplikačního serveru, zatímco ke správné interpretaci dat potřebujeme hlavně zkušenosti s analýzou výkonu, nebo detailní instrukce. Nestačí ale získat data jen o naší aplikaci, jelikož potřebujeme znát informace o celé infrastruktuře, která je s aplikací spojena, protože Java EE aplikace jsou vícevrstvé. Potřebujeme tedy získat následující informace z těchto vrstev:
Aplikace – doba odezvy služby, počet volání služby, doba odezvy na úrovni tříd a na úrovni metod, počet volání tříd a metod, alokace a dealokace objektů a další
Aplikační server – metriky zásobníku vláken, metriky zásobníku databázových spojení, metriky zásobníku JCA spojení, metriky zásobníku enterprise bean, metriky JMS serveru a transakční metriky
JVM – využití paměti a metriky garbage collectoru
Operační systém – využití procesoru, využití fyzické paměti, I/O metriky a síťové metriky
Aplikační metriky prezentují specifické výkonnostní charakteristiky aplikace, které můžeme využít na zjištění slabých míst aplikace i externích zdrojů. Sledováním doby odezvy metody, která jde mimo JVM, například volání JDBC, které vykonává SQL dotaz na databázi, můžeme zjistit, jestli jde o problém v aplikačním kódu, nebo v externím zdroji. Navíc monitorováním doby odezvy a počtu volání můžeme lépe porozumět chování uživatele. Aplikační server obsahuje listener, který přijímá příchozí požadavky a frontu, která řadí požadavky ke zpracování. Obsahuje jeden, nebo více zásobníků vláken, které vyjmou požadavek z fronty a zpracují ho. Dále obsahuje zásobník spojení, který se používá na spojení s externími systémy nebo na služby zpráv. Další důležitou částí je
20
správce transakcí, který zajišťuje spolehlivost aplikace. Z hlediska výkonu jsou důležité všechny tyto části. Získání informací o JVM je důležité, jelikož aplikace běží na aplikačním serveru, který běží na JVM. Takže každý problém s JVM ovlivní aplikační server a to ovlivní naši aplikaci. Důležité je zaměřit se na správu paměti a garbage collector. Pokud garbage collector uklízí velké množství objektů, tak zastaví všechny ostatní procesy v JVM, což má za následek zamrznutí aplikačního serveru i naší aplikace. Proto je důležité věnovat JVM čas. Nakonec je důležité sbírat informace z operačního systému. Vytížení procesoru nám ukazuje, kolik práce provádí aplikační server. Využití fyzické paměti nám ukazuje, kolik paměti JVM spotřebovává mimo svoji haldu. Monitorováním sítě můžeme zjistit, jestli jsou naše komunikační algoritmy efektivní. Každá část je důležitá a může nám ukázat, kde se nacházejí problémy s výkonem [7].
3.4.1 Získávání aplikačních metrik Nejužitečnější informace o aplikaci získáváme pomocí instrumentace kódu, jako informace o počtu volání metod, době odezvy metod a vytváření a mazání objektů. Pokročilejší implementace nesledují tyto informace jen pro jednotlivé metody, ale sledují celou dráhu volání požadavku. Toto nám umožňuje vidět celou dráhu požadavku a odhalit, které části nejvíce ovlivňují dobu odezvy. Některé implementace dokonce ukládají i argumenty metod, což pomáhá například při zjišťování problému u JDBC volání, protože víme, jaký SQL dotaz způsoboval problémy. Instrumentace kódu, má dvě formy: vlastní a automatickou. Vlastní instrumentace kódu, je vložení kódu, který monitoruje výkon aplikace vývojářem. Výhodou je, že získáváme jen data, která nás zajímají a navíc ve formátu, který si sami zvolíme. Pří automatické instrumentaci, vkládá monitorovací kód do aplikace nějaký nástroj. Výhody automatické instrumentace jsou následující:
Kód aplikace zůstane nezměněn, takže zůstane stejně dobře čitelný.
Nástroj dokáže monitorovat celou aplikaci, což by vkládáním vlastního kódu bylo náročné a zdlouhavé u větších aplikací.
V nástroji se dá nastavit, jaké množství dat si přejeme monitorovat, což by jinak vyžadovalo změny v kódu.
21
Tyto nástroje vkládají monitorování do byte-kódu a ne do Java kódu, takže monitorování je lépe optimalizované.
Informace jsou shromažďovány a udržovány v centrálním repozitáři.
Většina nástrojů poskytuje intuitivní uživatelské rozhraní, se kterým je analýza výsledků jednodušší.
3.4.2 Získávání metrik aplikačního serveru V dnešní době už není získání dat takový problém, protože většina výrobců aplikačních serverů umožňuje zobrazovat informace o monitorování aplikace pomocí Java Management Extension (JMX). JMX používá objekty zvané MBeany (Managed Beans), k získání dat o běhu aplikace. Tyto MBeany musejí být zaregistrovány na MBean Serveru, který funguje jako správce všech MBean. K připojení k MBean serveru používáme JMX konektory, například přímo v JDK můžeme použít JConsole, nebo u aplikačních serverů JMX konzoli [10].
3.4.3 Získávání metrik JVM Jednotlivé JVM prezentují informace o výkonu různými způsoby. Například velká část aplikačních serverů, jako například JBoss, poskytují tyto informace přes již zmiňovaný JMX. Nejdůležitější metriky z hlediska výkonu jsou tyto:
Využití haldy – potřebujeme znát stávající velikost, maximální velikost a další dostupné informace
Četnost garbage collectoru – zjišťujeme, jak často garbage collector běží
Trvání garbage collectoru – potřebujeme vědět, jak dlouho garbage collectoru trvá dokončit práci
Efektivita garbage collectoru – zjišťujeme, jestli byl garbage collector schopný získat dostatečné množství paměti
Otálející objekty – potřebujeme vědět, jestli procesy zanechávají v paměti nějaké objekty, které už nepotřebujeme
22
Cyklické objekty – zjišťujeme, jestli jsou nějaké objekty vytvářeny a mazány příliš často a zbytečně
Některé informace jsou zjistitelné bez většího zatížení aplikace jako využití haldy, ale velice náročné je nalezení otálejících a cyklických objektů. Získání informací o garbage collectoru jsou středně náročné a pohybuje se kolem pěti procent režie navíc. Otálející objekty v paměti zanechává nějaký požadavek, který je před svým ukončením nedereferencuje. Podobné chování může být chtěné, jako například potřeba udržovat v paměti, uživatelovo přihlášení k systému, nebo nechtěné, například když se v paměti udržuje celá tabulka databáze, která už není potřeba. Toto garbage collector odhalit nemůže a musíme se o správnou správu starat my [7]. Cyklické objekty jsou pravým opakem. Žádný objekt udržován dlouho v paměti není a všechny jsou při požadavku vytvořeny znovu a před koncem požadavku jsou zničeny. Když se toto opakuje příliš často, zahlcujeme JVM, která ztrácí volnou paměť, což často spouští garbage collector a zpomaluje celou aplikaci. V extrémních případech to může způsobit i obyčejný for cyklus, jako v ukázce kódu 1. for (Object object : objects) { Integer threshold = system.getThreshold(); if (object.getThing() > threshold) { // Do something } } Ukázka kódu 1. Nevhodný for cyklus.
V tomto případě nastane problém, pokud máme velké množství objektů, například několik set tisíc, nebo kdybychom měli zanořených více takovýchto iterací v sobě. Při každé iteraci totiž alokujeme a mažeme proměnnou threshold. Pokud bychom se na tento příklad podívali pomocí profilovacího nástroje, viděli bychom několik set tisíc instancí, které jsou vytvářeny a hned zase mazány. Jednoduchou změnou snížíme dopad této iterace na výkon aplikace z velkého na minimální, viz ukázka kódu 2.
23
int threshold = system.getThreshold().intValue(); for (Object object : objects) { if (object.getThing() > threshold) { // Do something } } Ukázka kódu 2. Oprava nevhodného for cyklu.
3.4.4 Interpretace dat I když máme přístup k datům o výkonu aplikace, neznamená to ještě, že máme lepší znalosti. Až správná interpretace dat nám tyto znalosti může poskytnout. Ke správné interpretaci musíme nadefinovat model, který reprezentuje prostředí naší Java EE aplikace. Potřebujeme měřit tři různé kategorie metrik:
Doba odezvy
Propustnost aplikace
Využití zdrojů
Obrázek 4. Ukazatele při zvýšení zátěže aplikace [7].
24
Na obrázku číslo 4, můžeme vidět jak se ukazatele využití zdrojů, propustnosti aplikace a doby odezvy mění v závislosti na počtu uživatelů. Využití zdrojů se zvyšuje při zvyšování zátěže, protože systém musí zvládnout více práce. Více uživatelů potřebuje více paměti a silnější procesor. Zvyšování zátěže je přirozené, ale systém nakonec dojde do bodu, kdy jsou zdroje nasyceny. Když se systém snaží zpracovat více požadavků, než může zvládnout, procesor stráví více času přepínáním mezi vlákny, než zpracováváním požadavků. Propustnost se také zvyšuje při zvyšování zátěže, protože více uživatelů nutí systém více pracovat. Ale při výše zmiňovaném nasycení začne propustnost klesat, protože procesor stráví více řízením sebe sama. Doba odezvy také pomalu roste se zvyšováním zátěže systému. Problém ale nastává, když se po nasycení zdrojů zátěž stále zvyšuje, protože potom začne doba odezvy růst exponenciálně.
3.4.5 Agregace dat Někdy není lehké se v získaných datech orientovat, například když se snažíme zanalyzovat požadavek, který byl za třicet minut volán až tisíckrát. V této situaci není moc efektivní zkoumat každé volání zvlášť, a tak je dobré data rozdělit do menších částí a agregovat. Pro každou část spočítáme počet volání a nejmenší, největší a průměrnou dobu odezvy. Pokud máme data takto zpracována, je mnohem jednodušší se v nich orientovat a odhalit problémy [7].
3.5 Profilování Profilování je forma dynamické analýzy programu, která může měřit například náročnost programu na paměť, časovou náročnost programu nebo četnost a průběh volání funkcí. Jak už bylo řečeno, nejvhodnější fází k profilování je fáze vývoje programu, přesněji jednotkové testování, protože si chceme být jistí, že při skládání komponent dohromady budou všechny části splňovat požadavky na výkon aplikace [8]. Nejpoužívanějším aplikačním rámcem pro jednotkové testování je open source JUnit9. Tento Framework podporuje vytváření jednotkových testů při vývoji komponentů a 9
http://junit.org/
25
validování funkcionality každé komponenty. Existují i vývojová paradigmata, kde se testy píší před vývojem jednotlivých komponent, jelikož to zlepšuje pochopení používání komponent před implementací. JUnit se soustřeďuje hlavně na testování funkcionality, ale jeho odnože obsahují také testování výkonu a škálovatelnosti. Výkonnostním testováním zjišťujeme doby odezvy a testováním škálovatelnosti zjišťujeme, jak se aplikace chová pod větší zátěží. Pomocí jednotkového testování výkonu bychom měli dosáhnout následujícího [7]:
Identifikovat problémy s pamětí
Identifikovat pomalé metody a algoritmy
Změřit, jestli jednotkové testy pokrývají většinu kódu
3.5.1 Profilování paměti Nebezpečné a těžko zjistitelné problémy s pamětí v Java aplikacích jsou úniky paměti. Nejlepší cestou, jak se zbavit úniků, je zanalyzovat komponenty profilováním paměti. Paměťový profilovací nástroj vytvoří snímek haldy, potom spustíme náš jednotkový test a po dokončení testu nástroj vytvoří další snímek haldy. Po této proceduře nástroj zobrazí všechny objekty, které v haldě zůstaly. Analýzou rozdílu obou snímků najdeme objekty, které test nechal v paměti. U každého se musíme rozhodnout, jestli je žádoucí, aby tam objekt zůstával, nebo jestli se jedná o chybu. Pomocí paměťových profilovacích nástrojů je také možné odhalit další nebezpečný problém Java aplikací s pamětí a to cyklické objekty. Takovýto objekt se nám zobrazí jako stejný objekt, který se mnohokrát vytvoří a zase uvolní z paměti. Většinou takové chování žádoucí není a jednoduchou změnou kódu se ho dokážeme zbavit.
3.5.2 Profilování kódu Profilování kódu nám umožňuje zjistit výkon metod a algoritmů tím, že nám najde nejvíce procházené sekce kódu a sekce, u kterých provádění kódu trvá nejdéle. Tím nám odhalí důležité body, u kterých se můžeme pokusit a optimalizaci. Profilování kódu by mělo být spouštěno s jednotkovými testy, protože ty by měly odrážet jednotlivé akce uživatele.
26
3.5.3 Profilování pokrytí Profilování pokrytí ukazuje kolik procent tříd, metod a řádek kódu bylo použito při testu. Zjištění pokrytí je důležité při odhadování efektivity jednotkových testů. Pokud výsledky profilování paměti a kódu jsou dobré, ale profilování pokrytí ukazuje jen 20 % kódu, potom nemůžeme mít důvěru v náš program, ani v naše testy a musíme je rozšířit [7].
3.5.4 VisualVM VisualVM je open source monitorovací a profilovací nástroj, který je součástí Java Development Kit balíku od verze 6, nebo je dostupná ke stažení samostatně z internetových stránek visualvm.java.net. Jedná se o jednoduchý, nenáročný a přehledný nástroj, který se nejčastěji používá na zjišťování problému s hotovou aplikací, jelikož profilování JUnit testů není dobře integrováno [11]. Po spuštění aplikace se zobrazí přehledný panel, který ukazuje všechny Java aplikace, které v současnosti běží na lokálních i vzdálených JVM. Po výběru aplikace se zobrazí záložky, ve kterých si uživatel může vybrat oblast, která ho zajímá. V záložkách je na výběr Overview, Monitor, Threads, Sampler a Profiler. V Overview jsou zobrazeny základní informace o aplikaci jako hlavní třída aplikace a její argumenty, verze Javy nebo systémové a JVM argumenty. V záložce Monitor jsou čtyři přehledné grafy (obrázek 5). První ukazuje využití procesoru a aktivitu garbage collectoru. Druhý ukazuje velikost haldy a její využití. Třetí graf zobrazuje statistiku načtených tříd a čtvrtý ukazuje statistiku aktivních vláken. Pro lepší přehled jednotlivých grafů se dají ostatní schovat. Z tohoto pohledu je možné spustit garbage collector nebo vytvořit snímek haldy, ve kterém můžeme prohledávat veškeré instance, které v době pořízení snímku byly v haldě. Důležité je i zobrazení referencí u každé instance, protože tak můžeme zjistit, co drží tuto instanci v paměti. Tento přístup by byl vhodný u obrázku číslo 5, jelikož nám v malém čase nebezpečně roste halda aplikace a potřebujeme zjistit, které objekty nám zůstávají v paměti a zamezit tomu.
27
Obrázek 5. Monitorovací grafy ve VisualVM.
V záložce Threads jsou přehledně ukázána všechna vlákna aplikace na časové ose. Každé vlákno má barvu podle toho, jestli běží, čeká, spí, nebo je monitorováno. Tento pohled je vhodný na odhalení vláken, která jsou z nějakého důvodu zablokovaná. Je možné vytvořit i snímek vláken, který se dá prozkoumat později. Záložky Sampler a Profiler ukazují stejné informace. Rozdíl mezi profilováním a samplováním je, že samplování je méně náročné na výpočetní čas a nezatěžuje tolik aplikaci, jelikož nevkládá nic do bytekódu tříd, ale vytváří snímky vláken, které pak analyzuje. V těchto záložkách máme na výběr, jestli budeme profilovat procesor, nebo paměť. Při profilování procesoru vidíme všechny metody, které aplikace provádí a důležité informace o nich, jako jak dlouho se metoda prováděla, kolikrát byla zavolána, nebo kolik z celkového času běhu metoda zabrala. Takto můžeme jednoduše odhalit metody, které zabírají příliš mnoho času, viz obrázek číslo 6.
28
Obrázek 6. CPU profilování ve VisualVM.
Na
tomto
obrázku
můžeme
vidět
na
první
pohled
podezřelou
metodu
getOrderById(Long) z třídy OrderManagerImpl, která zabírá 50 % celého výpočetního
času a přitom je volána nejméně krát ze všech metod ostatních náročných metod. Taková jednoduchá metoda, která zabírá velké množství výpočetního času, je podezřelá a měla by být prozkoumána a změněna. Při profilování paměti nástroj přehledně ukazuje všechny objekty alokované v paměti právě v daném momentě. Objekty jsou seřazeny podle toho, kolik zabírají bajtů. Další zajímavý zobrazený údaj je počet objektů každé třídy nacházejících se v paměti. Na obrázku číslo 7 můžeme vidět ukázku profilování paměti. Zde jsou na první pohled podezřelé instance třídy OrderDto. Zabírají nám přes 20 % paměti a máme přes 200 tisíc instancí. S touto třídou budou nejspíše souviset i velká čísla u třídy Long, jelikož OrderDto obsahuje atributy této třídy a velká čísla u pole objektů. Pokud nevíme, kde se podezřelé objekty vytvářejí, můžeme vytvořit snímek haldy a prozkoumat ho, nebo zkusit profilování kódu, které nám ukáže, jaké metody se volají.
29
Obrázek 7. Profilování paměti ve VisualVM.
Velice šikovná funkce je možnost filtrovat jak profilování procesoru, tak paměti podle Java balíků. Tak dostaneme informace jen o zvolených metodách a zvolených objektech, ale na druhou stranu můžeme přehlédnout důležitá data.
3.5.5 JProfiler JProfiler je komerční profilovací nástroj vyvíjen Německou společností ej-technologies. Pomocí JProfileru je možné profilovat aplikace na lokálních JVM i vzdálených aplikačních serverech. Nabízí podobné možnosti profilování jako nekomerční řešení, ale obsahuje spoustu výhod a zlepšení, pro zjednodušení práce a lepší profilování aplikací. Výhodou pro uživatele vývojového prostředí Eclipse je dobrá integrace v tomto prostředí, která se dá nastavit už při instalaci. JProfiler je možné používat ve zkušební verzi po dobu deseti dní [12]. Po spuštění aplikace nabízí Quick Start menu, ve kterém je na výběr například profilování lokální JVM, aplikačního serveru, nebo aplikací na vzdálených serverech. Pro začínající uživatele je vhodné vybrat možnost demo, ve které jsou tři ukázkové aplikace, na kterých si může uživatel vyzkoušet jednotlivé funkce. Tato část je dobře zpracovaná a uživatel má možnost spustit úniky paměti, nebo podobné problémy a vidět, jak se to projeví na monitorování.
30
Po vybrání aplikace, kterou chceme monitorovat, zobrazí JProfiler ještě přehledné nastavení profilování, kde se zobrazuje, jakou režii vybrané nastavení způsobí. Zde lze vybrat, jestli chceme aplikaci profilovat, nebo samplovat. Je zde také nastavení filtrů, kde je možné vybrat jen ty Java balíky, které nás zajímají a tím snížit režii. Nastavení se zobrazí také poté, co spustíme profilování z prostředí Eclipse, kde se po nainstalování pluginu zobrazí možnost spuštění aplikace, nebo testu v profilovacím módu, který spustí JProfiler. Poté už můžeme přistoupit k samotnému profilování. Jako první menu je zobrazeno profilování paměti, kde první záložka ukazuje alokované objekty v paměti podobně jako VisualVM. Nápomocné jsou ale i ostatní záložky, které má v této kategorii JProfiler navíc. Například strom alokací zobrazí strom metod, které vytvářejí objekty, které nám ukázal přehled objektů v paměti. V tomto stromu je pak lehké najít jednotlivé metody, které zahlcují paměť (Obrázek 8).
Obrázek 8. Strom alokací v aplikaci JProfiler.
Menu profilování paměti nabízí také více možností zobrazení, která zjednodušují hledání problému. Například lze zobrazit metody ve stromě stejně jako u alokací, nebo je zde zajímavá možnost prohlédnout si graf volání problémové funkce. Na obrázku 9 můžeme vidět graf volání problémové metody getOrderById, kterou jsme odhalili při profilování procesoru. Volání jsou zobrazena v kolonkách a jsou obarvena podle toho, kolik výpočetního času spotřebují. Takto máme možnost jednoduše zjistit, které volání naší metody zabírá nejvíce času a optimalizovat ho. Zde například vidíme, že nejvíce času zabere třídění kolekce a hned druhé nejnáročnější je přidání prvku do kolekce.
31
Obrázek 9. Graf volání v aplikaci JProfiler.
Následují menu, která známe z aplikace VisualVM jako ukazatel vytížení haldy, aktivita garbage collectoru, zátěž procesoru, přehled vláken, přehled tříd a další. Samozřejmě je zde také možnost vytvořit snímek haldy. Vláknům je zde také věnován celý pohled, ale v JProfileru je čistší a nezobrazuje tolik, pro nás, nedůležitých vláken. Dále je zde navíc pohled ukazující zamknutí vláken. V databázovém menu nám aplikace dává možnost profilovat volání do databází. V JDBC záložce můžeme vidět všechny důležité informace o připojeních k databázi, jako všechna připojení na časové ose a jejich akce, čas vytvoření a zavření připojení, nebo všechny dotazy do databází seřazené podle výpočetního času. Tento pohled je důležitý pro hledání problému s databází, můžeme zde zkontrolovat, jestli zavíráme všechny JDBC připojení k databázi nebo jestli jsou naše dotazy do databáze optimální.
32
Obrázek 10. Profilování JDBC dotazů do databáze v aplikaci JProfiler.
Poslední záložka nám dává možnost získat více informací o Java EE aplikacích, například o servletech, JNDI volání, JMS komunikaci, nebo RMI volání. Celkově jde na první pohled vidět, že je JProfiler placenou aplikací a tedy se s ním mohou open source aplikace jen těžko srovnávat. VisualVM je dobrá a jednoduchá aplikace pro počáteční profilování, ale pro ty kteří to myslí s profilováním vážně je vhodné zaplatit si JProfiler, který více ulehčuje práci a nabízí mnoho užitečných pohledů, které vnesou do optimalizačních problémů nové světlo.
33
4 Využití optimalizačních technik v praxi
V praktické části diplomové práce se zaměřuji na reálné problémy s výkonem aplikací ve firmě SeaComp s.r.o., které bylo potřeba zanalyzovat. Prvním úkolem bylo vytvořit zjednodušený model logistické aplikace, která by odpovídala reálně používaným logistickým systémům. Na tomto modelu bylo potřeba zhodnotit možnosti migrace na novější verze aplikačních serverů. Tuto aplikaci k prezentaci svých možností používá i druhá implementační část, která se zabývá analýzou vazeb mezi entitami v Java EE aplikacích.
4.1 Porovnání aplikačních serverů na logistické aplikaci Pro tento účel jsem vytvořil jednoduchý model skladové aplikace na platformě Java EE. Tento model byl vytvořen podle reálných skladových systémů používaných ve firmě SeaComp s.r.o. a využívá stejné technologie. Databázovou vrstvu zajišťuje databáze od firmy Oracle. Jako nástroj pro objektově relační mapování je využíván aplikační rámec Hibernate vyvíjený firmou Red Hat. Aplikační logika je zapouzdřena pomocí Entity JavaBeans (EJB). Jednoduchý samostatný aplikační klient se dorozumívá s aplikací běžící na aplikačním serveru pomocí webových služeb. Aplikační klient je jediným rozdílem oproti reálným skladovým aplikacím firmy SeaComp s.r.o., jelikož ty využívají klienty napsané v jazyce C#. Toto není problém, jelikož nás hlavně zajímá Java EE aplikace na aplikačním serveru, takže rychlost klienta je pro nás vlastně irelevantní. Aplikace je k nalezení v archivu diplomové práce. Verze spustitelná na serveru WildFly 8.0.0 je v souboru WarehouseModelWildFly-1.0.jar a verze pro server GlassFish 4.0 v souboru WarehouseModelGlassFish-1.0.jar.
34
4.1.1 Popis modelu skladové aplikace Datový model aplikace je zjednodušený oproti reálné skladové aplikaci a obsahuje jen šest entit. Relace mezi pěti entitami můžeme vidět na obrázku číslo 11. Šestá entita představuje požadavek uživatele (Request) a není v relaci s žádnou z ostatních entit. Statická data aplikace představují položka (Item) a lokace (Location). Dynamická data představuje zboží (Stock), hlavička objednávky (Order Header) a řádek objednávky (Order Line).
Obrázek 11. Entitně relační diagram skladové aplikace.
Zboží je vždy na jedné lokaci a obsahuje jednu položku v dané kvantitě, představuje tedy fyzicky přítomnou zásobu položek ve skladu. Hlavička objednávky obsahuje vždy jednu, nebo více řádků a dále lokaci, kam má být objednávka dodána. Lokace tedy nepředstavuje jen místa ve skladu, ale i lokace mimo sklad. Řádek objednávky obsahuje vždy jeden předmět a požadovanou kvantitu. Při vyřizování objednávky je také k položce přiřazena jedno, nebo více zboží, pokud je požadovaná položka na skladě. Pro manipulaci s daty, přijímá aplikace požadavky pomocí webových služeb. Tyto požadavky jsou zjednodušené a obsahují jen typ požadavku. Při požadavku na vytvoření objednávky je vytvořena hlavička objednávky s náhodnou lokací a náhodným počtem řádků od jedné do pěti. Každý řádek má náhodně vybraný předmět v náhodné kvantitě.
35
Pro vyřízení objednávky čeká aplikace na požadavek ke spuštění otevřených objednávek. Po přijmutí tohoto požadavku aplikace najde potřebné zboží a přiřadí je k objednávce. Pokud existuje dostatečná zásoba zboží pro realizace celé objednávky (tedy všech řádků objednávky), zboží je přesunuto na požadovanou lokaci zadanou v hlavičce. Pokud není dostatek zboží na skladu, zůstávají předměty ve skladu a objednávka je označena jako nevyřízená. Tento průběh čeká všechny nevyřízené objednávky v databázi. Další možné požadavky jsou požadavek na vytvoření nového zboží s náhodnou kvantitou a požadavek, který simuluje čtení informací o skladu. Zpracování požadavků na spuštění otevřených objednávek a na vytvoření nového zboží je asynchronní a o zpracování se starají komponenty zvané message driven bean. Což znamená, že požadavky se ukládají do fronty a poté je aplikace zpracovává. Tento systém se využívá, pokud je třeba mít jistotu, že se jistá funkcionalita provede, ale není potřebná okamžitá odpověď.
4.1.2 Porovnání aplikačních serverů pomocí skladové aplikace Hlavním důvodem tohoto porovnání je zhodnocení možnosti migrace ze staré verze aplikačního serveru JBoss 4.2.3, který je používán na některých skladových systémech ve firmě SeaComp, na modernější server. Po zvážení byly vybrány dva nejnovější open source zástupci vývojářských společností Oracle a Red Hat, tedy GlassFish 4.0 a WildFly 8.0.0 (dříve zvaný JBoss AS). Oba tyto servery podporují nejnovější verzi Java Persistence API 2.1 a tedy i Hibernate 4.3 a vyšší. Pro tuto potřebu byl vytvořen jednoduchý aplikační klient, který využívá nejjednodušší metodu manuální instrumentace kódu, tedy zaznamenává délky jednotlivých volání a vypisuje je na standardní výstup. Po spuštění klienta se jako první spustí inicializace skladu, která vytvoří potřebné lokace, předměty a naplní sklad zásobou předmětů, která činí deset tisíc položek. Následně se vytvoří tisíc nových objednávek a spustí se jejich vyřizování. Poté se pošle požadavek na doplnění skladu o pět tisíc nových zásob a následně se vytvoří a provede sto nových objednávek. Mezi každým voláním se navíc posílá požadavek na čtení informací z databáze, který simuluje zobrazování dat uživateli. Tento test byl spuštěn na každém aplikačním serveru celkem pětkrát a výsledky můžeme vidět v tabulkách 1 a 2.
36
Celkový čas
Iniciace skladu
Tvorba 1000 obj.
Exekuce 1000 obj.
5x doplnění skladu
Tvorba 100 obj.
Exekuce 100 obj.
Prům. doba čtení
Průměr 236754,8
16405
21370,6
163804,8
9324,6
4114,4
16827
934,6
WildFly
Max
244252
18681
23284
173368
10952
5909
17406
986
Min
226162
15487
19914
156179
8348
2944
15731
879
Tabulka 1. Délky jednotlivých volaní v ms na serveru WildFly.
GlassFish
Celkový čas
Iniciace skladu
Tvorba 1000 obj.
Exekuce 1000 obj.
5x doplnění skladu
Tvorba 100 obj.
Exekuce 100 obj.
Prům. doba čtení
Průměr
143067,6
8181,2
24037,4
78019
13929
5794,6
5619,4
1450,2
Max
165554
8740
26702
101544
15012
7872
6059
1841
Min
135338
7651
22255
70493
13051
4509
5369
1335
Tabulka 2. Délky jednotlivých volaní v ms na serveru GlassFish.
Při porovnání těchto dvou tabulek je na první pohled jasné, že provedení testu zvládal server GlassFish o více než minutu rychleji, než konkurenční WildFly. To je podezřelé, jelikož by tyto dva servery měly být na podobné úrovni. Při bližším pohledu můžeme vidět, že ačkoliv byl WildFly lepší ve většině částí testu, nabral zpoždění při asynchronním zpracovávání většího množství požadavků. Zde můžeme porovnat asynchronní zpracovávání objednávek s asynchronním doplňováním skladu. Pět požadavků na doplnění skladu zvládl WildFly rychleji, ale se sto objednávkami měl už problémy, nemluvě o tisíci. Při porovnání obou serverů na tomto úseku v profilovacím nástroji JProfiler, jsem zjistil, že WildFly se mnohem více zaobírá režií zpráv. Nutno podotknout, že oba servery využívají různou implementaci JMS, takže je přirozené, že se liší, i když takovýto rozdíl je podezřelý. Při revizi kódu jsem narazil na místo, kde se vytváří relace fronty, která posílá jednotlivé JMS zprávy (viz ukázka kódu 3).
37
Context context = new InitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup(CONNECTION_FACTORY); QueueConnection connection = factory.createQueueConnection(); QueueSession session = connection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE); Queue queue = (Queue) context.lookup(ORDER_QUEUE_LOOKUP); QueueSender sender = session.createSender(queue); Ukázka kódu 3. Vytvoření relace fronty ve třídě RequestApi.
Při
vytváření
instance
metody možnost specifikovat, jestli má být relace transakční, což ukazuje první atribut, který je v tomto případě false. Druhý atribut metody je mód potvrzování, který je v tomto případě automatický, což znamená, že po každém poslání zprávy čekáme, jestli tuto zprávu JMS poskytovatel přijal a postará se o doručení zprávy třídě, která tyto zprávy zpracovává, v tomto případě se jedná o message driven bean. Zde tedy odesíláme tisíc zpráv a u každé čekáme na odpověď JMS poskytovatele, jestli zprávu přijal, což není moc efektivní. createQueueSession(boolean
třídy Queue transacted, int
Session pomocí acknowledgeMode) je
Pokud označíme relaci jako transakční, tedy změníme první atribut metody na true, druhý atribut se ignoruje a automaticky se posílá transakční mód potvrzování. Při posílání zpráv JMS poskytovateli nečekáme na odpověď a poskytovatel si všechny tyto zprávy ukládá do mezipaměti, ale zatím je nedoručuje. Poskytovatel začne doručovat zprávy až v momentě, kdy potvrdíme poslání pomocí metody session.commit(). V tomto momentě máme jistotu, že JMS poskytovatel všechny zprávy obdržel, protože jinak by potvrzení neprošlo [13]. Při posílání většího množství zpráv, jako v tomto případě, je tento postup mnohem efektivnější, protože nemusíme čekat na velké množství odpovědí od JMS poskytovatel. Opravený kód můžeme vidět v ukázce kódu 4.
38
Context context = new InitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup(CONNECTION_FACTORY); QueueConnection connection = factory.createQueueConnection(); QueueSession session = connection.createQueueSession(true, QueueSession.SESSION_TRANSACTED); Queue queue = (Queue) context.lookup(ORDER_QUEUE_LOOKUP); QueueSender sender = session.createSender(queue); for (Number orderHeaderId : orderHeaderIds) { ObjectMessage objectMessage = session.createObjectMessage(); objectMessage.setObject(orderHeaderId.longValue()); sender.send(objectMessage); } session.commit();
Ukázka kódu 4. Efektivnější posílání většího množství zpráv ve třídě RequestApi.
Po této změně jsem znovu pětkrát spustil celý test a výsledky byly o něco málo lepší, než dosahoval server GlassFish, což můžeme vidět v tabulce 3.
WildFly
Celkový Iniciace čas skladu
Průměr 137406 16204,4
Tvorba 1000 obj.
Exekuce 1000 obj.
5x doplnění skladu
Tvorba 100 Exekuce 100 obj. obj.
Prům. doba čtení
21253,8
74934,4
8022,2
4539,4
7138,2
1000,4
Max
142297
16820
22478
78851
8730
7668
7747
1149
Min
133412
15720
20364
71450
7630
2625
6689
861
Tabulka 3. Délky jednotlivých volaní v ms na serveru WildFly po optimalizaci.
Stejnou změnu jsem udělal i v kódu aplikace, kterou jsem nasadil na serveru GlassFish, ale trvání testu se nijak nezměnilo a hodnoty zůstaly obdobné. To by mohlo poukazovat na to, že posílání zpráv GlassFish řeší transakčně, i když bylo nastaveno něco jiného. Jde tedy vidět, že každý server se chová jinak a co dobře funguje na jednom, nemusí efektivně fungovat na druhém.
39
4.1.3 Zhodnocení aplikačních serverů V tomto testu o celkem zanedbatelnou část zvítězil server WildFly, ale jinak jsou oba dva servery vyrovnané, což se od dvou špičkových open source aplikačních serverů dá očekávat. Další malou výhodou serveru WildFly je, že startuje zhruba o polovinu rychleji při nasazení této malé aplikace. Při migraci je také potřeba zvážit, že migrujeme ze serveru JBoss 4.2.3 a migrace na novější verzi serveru JBoss není tak náročná, jako kdybychom migrovali na server GlassFish, protože při takové migraci je potřeba udělat více změn v kódu a nastavení aplikace. Tento test nám také ukázal, že se úplně stejná aplikace může na dvou různých serverech chovat jiným způsobem. Po zvážení všech okolností byl k migraci vybrán server JBoss 7.1.1, který také po otestování dosahoval v podobných výsledků, jelikož aplikace je už na jeden server od firmy Red Hat optimalizována, a proto je větší šance, že bude fungovat lépe na jiném serveru od této firmy. Server WildFly je dostupný jen pár měsíců a ještě k němu není dostatečné množství informací a vyřešených problémů uživatelů, a proto je bezpečnější přechod na předchozí verzi.
4.2 Aplikace pro analýzu vazeb mezi entitami v JPA Aplikace byla vytvořena díky častým problémům aplikací s horlivým načítáním vazeb mezi entitami. Aplikace je schopna prohledat všechny entity analyzované Java EE aplikace a nalézt všechny vazby mezi entitami, případně spočítat, kolik entit se průměrně načítá při načtení vazby. Zjednodušená verze aplikace, která nepočítá ceny načítání, je k nalezení v archivu diplomové práce v souboru EntityAnalyzer-1.0.jar.
4.2.1 Java Persistance API a vazby mezi entitami Java Persistence API je standardním rozhraním pro objektově relační mapování v persistentní vrstvě Java EE aplikací, ale je možné ji využít i v Java SE aplikacích. JPA zjednodušuje vývoj persistentní vrstvy pro programátora. Jako implementaci JPA používá model skladové aplikace Hibernate, ale aplikace pro analýzu vazeb by měla být použitelná pro všechny implementace JPA.
40
Položku v databázi lze vytvořit jako jednoduchou POJO10 třídu, která se nakonfiguruje pomocí anotací, nebo XML souboru. V aplikaci jsou využity anotace, protože jsou jednodušší a přehlednější. Každá třída, která má být entitou v databázi musí mít anotaci @Entity [1]. Pro tuto aplikaci jsou nejdůležitější anotace pro vazby mezi entitami. Pomocí anotací můžeme specifikovat, jestli má být vytvořena vazba jedna ku jedné entitě, jedna ku mnoha, mnoho ku jedné, nebo mnoho ku mnoha. Dvě hojně využívané anotace vazeb můžeme vidět v ukázce kódu 5, spolu se zjednodušenou entitou OrderHeader, která, zde obsahuje jen čtyři ukázkové proměnné. Reálná entita obsahuje navíc dva další atributy. U vazeb se používá standardní režim načítání, pokud není v anotaci specifikován jiný. Existují dva druhy načítání vazeb, líné (Lazy) a horlivé načítání (Eager). Pokud má entita vazbu na jinou entitu a režim načítání je líný, znamená to, že když načteme entitu z databáze, vazba se nenačte, dokud se na ní přímo nedotážeme. Horlivé načítání znamená, že kdykoliv načteme entitu z databáze, je rovnou načtena i její vazba, tedy další entita [14]. @Entity public class OrderHeader implements Serializable { private static final long serialVersionUID = 1L; @Id @SequenceGenerator(name = "OrderHeaderId", allocationSize = 1, sequenceName = "O_HEADER_SQ") @GeneratedValue(strategy = GenerationType.AUTO, generator = "OrderHeaderId") @Column(name = "ID") private Long id; @OneToMany(mappedBy = "header", cascade = CascadeType.REMOVE) private Set
lines; @ManyToOne(optional = false) @JoinColumn(name = "LOCATION_ID") private Location location; @Column(name = "STATUS", nullable = false, unique = false) private OrderStatus status; // + Getters and Setters } Ukázka kódu 5. Ukázka anotací v entitě OrderHeader. 10
Plain Old Java Object
41
Při příliš častém horlivém načítání ve složitějším systému s mnoha entitami, může nastat problém, že když se dotazujeme na jednu entitu, načte se nám množství dalších entit, které nás ale při dotazu nezajímají. Největší problém nastává u vztahů jedna ku mnoha, nebo mnoho ku mnoha, proto je zde standardní režim načítání líný. U těchto vztahů entita může odkazovat na množství entit z jiné tabulky a ta zase může odkazovat dále a při načtení první entity musíme načíst všechny. Při líném načítání může nastat problém v případě, že načteme jistou entitu, která má línou vazbu do jiné tabulky a poté se v cyklu stále ptáme na tuto vazbu, tedy stále znovu načítáme všechny odkazované entity, což může být časově náročné. Proto je v tomto případě vhodnější změnit implementaci, nebo pokud to není možné, změnit režim načítání na horlivý.
4.2.2 Implementace aplikace a výstup Aplikace se zabývá hlavně hledáním problému s horlivým načítáním, ale umí nalézt všechny druhy vazeb. Využívá k tomu instanci třídy EntityManager, která je sdružená s persistentním kontextem11 a pomocí které aplikace dotazuje databázi. Instance této třídy také obsahuje všechny Java třídy všech entit. Z těchto Java tříd aplikace pomocí reflexe najde všechny vazby a označí je podle toho, jestli mají horlivé, nebo líné načítání. Je také možné, aby program spočítal cenu jednotlivých volání, tedy kolik dalších entit v průměru aplikace načte při načtení jedné entity, což má smysl hlavně při vypsání grafu entit s horlivým načítání. Tímto získáme celkový graf entit se všemi vazbami a případně cenou vazeb. Tento graf je poté vypsán do přehledné tabulky ve formátu CSV12. Analýza cen načítání je náročná na výpočetní čas, a proto bych ji nedoporučoval spouštět na systémech s velkými databázovými schématy. Pokud máme podezření, že jen některé entity načítají spoustu nepotřebných dat, můžeme využít možnost analyzátoru spustit analýzu jen na jedné, nebo na omezeném množství entit. V tabulce 4 můžeme vidět ukázku výstupu analyzátoru na entitě OrderHeader ze skladové aplikace po spuštění výše zmiňovaného testu. Aby byl výstup zajímavý i na takto malém projektu, všechny vazby byly předělány, aby se načítaly horlivě
11 12
Persistentní kontext se stará o entity a jejich data, která mají být vložena do databáze. Comma-separated values
42
OrderHeader - Cost: 5 Level Entity Association 1 OrderLine OneToMany 2 Stock OneToMany 3 Item ManyToOne 3 Location ManyToOne 1 Location ManyToOne
Fetch Eager Eager Eager Eager Eager
Average 2,43 0,922 1 1 1
Max 4 1 1 1 1
Min 1 0 1 1 1
Tabulka 4. Výstup analyzátoru vazeb pro entitu OrderHeader.
Důležitým sloupcem pro analýzu vazeb mezi entitami je Level (Úroveň). Podle úrovně poznáme, kudy jsme se k této entitě dostali. V tomto případě vidíme, že na entitu OrderLine jsme se dostali přímo z entity OrderHeader. Na entitu Stock jsme se dostali z entity OrderLine a tato entita načítá entity Item a Location. Na entitu Location se můžeme dostat také přímo z entity OrderHeader. Některé entity se v tabulce mohou ukazovat vícekrát, protože pokud se dostaneme k entitě odjinud, můžeme načítat jiné instance, takže nám to ovlivní celkovou cenu. Tato tabulka obsahuje také informace o cenách načítání vazeb. Nejdůležitější je průměrná cena načtení jedné instance entity OrderHeader. Zaokrouhlené číslo 5 nám říká, že pokud načteme jednu instanci, přes horlivé načítání se nám načte v průměru okolo pěti instancí ostatních entit. Sloupec Average ukazuje, kolik v průměru instancí se načte při načtení entity vyšší úrovně. Jelikož máme u entity spočítané ceny načítání, můžeme zde vidět podezřelou vazbu mezi entitou OrderLine a Stock, která je sice jedna ku mnoha, ale nikdy neobsahuje více než jednu položku Stock. V tomto případě je vhodné zanalyzovat datový model a zjistit, jestli je zde opravdu tato vazba potřeba, nebo jestli by mohla být změněna na vazbu jedna ku jedné. Analyzátor byl spuštěn i na datech reálné skladové aplikace, bohužel tyto tabulky nemohou být z důvodu zachování firemního tajemství zveřejněny. Naštěstí jeden odhalený problém se dá simulovat i na vytvořeném modelu skladové aplikace, pokud přidáme vazbu s horlivým načítáním z entity Location na Stock. Tato vazba způsobuje problémy při načítání hlaviček objednávek, protože při každém načtení jedné hlavičky se automaticky načte lokace, na kterou má být objednávka doručena a na takové lokaci může být opravdu hodně zásob předmětů. Při horlivém načítání mezi lokací a zásobou tedy načítáme velké množství dat, které při většině případů vůbec nepotřebujeme.
43
Stejná problémová vazba byla tedy přidána do skladové aplikace a test byl spuštěn znovu. Na první pohled trval mnohem déle, než v předchozím případě a výsledek analýzy vazeb pro entitu OrderHeader můžeme vidět na tabulce 5.
OrderHeader - Cost: 504 Level Entity Association 1 OrderLine OneToMany 2 Stock OneToMany 3 Item ManyToOne 3 Location ManyToOne 1 Location ManyToOne 2 Stock OneToMany 3 OrderLine ManyToOne 4 Item ManyToOne
Fetch Eager Eager Eager Eager Eager Eager Eager Eager
Average 2,486 0,978 1 1 1 500 1 1
Max 4 1 1 1 1 1275 1 1
Min 1 0 1 1 1 0 1 1
Tabulka 5. Výstup analyzátoru vazeb pro entitu OrderHeader po přidání problémové vazby.
Na první pohled je vidět, že cena při načtení jedné entity vzrostla skoro až stokrát, protože při každém načtení entity OrderHeader, načteme přes entitu Location v průměru pět set instancí entity Stock. Oprava je v tomto případě jednoduchá, jelikož stačí u vazby z lokace na zásobu předmětů změnit režim načítání na líný. Poté načítáme zásoby předmětů z lokací jen v případě, pokud to opravdu potřebujeme a načtení této vazby si vyžádáme.
4.2.3 Zhodnocení aplikace na analýzu vazeb Jedná se o celkem jednoduchou aplikaci, která může být vložena do jakékoliv aplikace, která používá Java Persistence API. K chodu potřebuje jen instanci třídy Entity Manager. Výstup této aplikace může být prospěšný každému, kdo má podezření na nějaké problémy s načítáním vazeb mezi entitami. Jedinou částí, která vyžaduje delší výpočetní čas je spočítání cen načítání, což je ale pochopitelné, protože ke zjištění potřebných informací musí projít všechny načítané entity. Proto je vhodnější zanalyzovat celé schéma nejdříve bez počítání cen, a poté u podezřelých entit spočítat ceny načítání. Aplikace je také vhodná k rozšíření, jelikož anotace entit obsahují mnoho dalších důležitých informací, o které se dá analyzovaný graf entit rozšířit, například informace o indexech v databázi.
44
5 Závěr
Optimalizace a kvalitní testování výkonu aplikací se stává stále důležitější disciplínou při návrhu a vývoji softwaru. Tato potřeba se nevyhýbá ani systémům na platformě Java EE. Mnoho softwarových firem si tento fakt už uvědomuje, ale je stále množství takových, kde se na výkon v počátečních fázích vývoje tolik nehledí a problémy se řeší až za běhu programu v reálném prostředí. Toto bohužel vede k častému krachu podobných aplikací a ztrátě důvěry zákazníka. Každá firma by si měla uvědomit, že pokud vynaloží úsilí ke kvalitnímu testování v prvotních fázích, ušetří si starosti ve fázích ostatních, kde cena opravy roste exponenciálně. Není ale vyhráno, ani pokud nasazujeme dobře otestovanou aplikaci, protože o výkon je potřeba se starat v každé fázi projektu. Pokud má testování výkonu aplikace uspět, musí být dobře stanoveny limity, které je potřeba splňovat. Nereálné limity vývojáři velmi často ignorují, a proto je důležité věnovat dostatek času analýze potřeb výkonu aplikace. Tento fakt dokazuje, že výkonem je potřeba se zabývat úplně ve všech fázích projektu a pokud je nějaká fáze vynechána, může to mít za následek nepříjemné problémy. K dobré optimalizaci a efektivnímu vyhledávání problémových částí programů, je potřeba dobrá znalost platformy Java EE. Proto práce na začátku nabízí základní informace o vývoji aplikací na této platformě a aplikačních serverech, které jsou potřebné k běhu programů. Optimalizace a testování výkonu v každé fázi projektu je přehledně shrnuto v následující částí teoretického oddílu diplomové práce. Zde je možné dozvědět se o nejčastějších problémech s výkonem u Java EE aplikací a jak se těmto problémům vyhnout, nebo je řešit. Také se tato část zabývá měřením výkonu a hledáním problémových částí programů pomocí profilovaní. Teoretická část se nesnaží zabíhat příliš do detailů tématu a spíše nabízí obecný přehled a časté problémy. V praktické části byly vytvořeny, na základě stávajících potřeb firmy SeaComp s.r.o., dvě aplikace, které by hlavně v budoucnu měly pomoci optimalizovat systémy vyvíjené na platformě Java EE. První je zjednodušeným modelem typické logistické aplikace reálně používané ve firmě a je postavena na stejných technologiích. Datový model odpovídá reálnému datovému modelu a diagram tříd také. Aplikace tedy může sloužit mnoha potřebám. Je na ní možné testovat nové technologie, které se zvažují pro využití v reálném prostředí, jelikož je často rizikové nasazovat novou neotestovanou technologii přímo
45
na systémech, které už zákazník používá. Díky jednoduchosti aplikace a datového modelu je vhodná k dalšímu rozšíření a implementaci složitějších procesů. Dala by se také využít k prvotnímu seznámení nových zaměstnanců s Java EE aplikacemi a technologiemi používanými ve firmě. Tato aplikace byla využita na zhodnocení možnosti migrace reálného logistického systému na novější verze aplikačních serverů. Pomocí ní jsou porovnány nejnovější modely dvou populární open source aplikačních serverů WildFly 8.0.0 a GlassFish 4.0. Oba servery dosahovaly podobných výsledků v testu, ale zajímavou skutečností je, že i takto jednoduchá aplikace se na každém serveru chovala trochu jinak. Na základě testu byl zvolen jako nejvhodnější možností k migraci server JBoss 7.1.1. Aplikace by měla být k tomuto účelu využívána i v budoucnu, v případě že se nějaký používaný aplikační server ukáže jako zastaralý. Druhá aplikace analyzuje vazby mezi entitami. Byla vytvořena hlavně k detekci nevhodných vazeb s horlivým načítáním. Díky takovýmto nevhodným vazbám může systém načítat z databáze mnoho zbytečných informací, což nepříjemně zatěžuje jak aplikaci na aplikačním serveru, tak databázi. Manuální vyhledávání nevhodných vazeb u větších systémů je namáhavá a nepříjemná práce vyžadující spoustu času a hodně trpělivosti. Aplikace byla vyvinuta, aby vyhledávání těchto vazeb zjednodušila. Pomocí analýzy zdrojového kódu entit je aplikace pomocí reflexe schopna nalézt všechny vazby a vypsat je do přehledné tabulky. Dále je možné u každé vazby spočítat ceny volání, tedy kolik v průměru vazba načte řádků z tabulky, na kterou odkazuje. Ceny jsou nakonec spojeny do celkové průměrné ceny načtení jedné entity. Z těchto tabulek je nalezení problémových vazeb mnohem jednodušší, než při procházení kódu aplikace. Pomocí aplikace byly nalezeny některé vazby, které způsobovaly problémy a měla by nadále sloužit při vývoji a analýze systémů ve firmě. Aplikace může být integrována do každé aplikace, která využívá Java Persistence API, jelikož ke svému běhu potřebuje jen instancí třídy EntityManager. Je také vhodná k rozšíření, jelikož anotace entit, které jsou k analýze vazeb použity, obsahují mnoho dalších důležitých informací. Například by bylo možné hledat chybějící, či přebývající indexy u atributů nebo vazeb.
46
6 Použitá literatura
[1] JENDROCK, E., EVANS, I., GOLLAPUDI, D., HAASE, K., SRIVATHSA, C., The Java EE 6 Tutorial: Basic Concepts, Fourth Edition. Addison-Wesley Professional, 2010. ISBN: 978-0137081851. [2] ADÁMEK, P., PITNER, T., KUBA, M., Studijní materiály k předmětu PA165 [online]. [cit. 31. 12. 2013]. Datum poslední revize 30. 9. 2012. [3] JENDROCK, E., CERVERA-NAVARO, R., EVANS, I., GOLLAPUDI, D., HAASE, K., MARKITO, W., SRIVATHSA, C., The Java EE 6 Tutorial [online]. [cit. 15. 12. 2013]. Datum poslední revize leden 2013. Dostupné z WWW: < http://docs.oracle.com/javaee/6/tutorial/doc/index.html>. [4] OTTINGER, J., What is an App Server? [online]. [cit. 6. 1. 2014]. Datum poslední revize 1. 9. 2008. Dostupné z WWW: < http://www.theserverside.com/news/1363671/What-is-an-App-Server>. [5] JAMAE, J., JOHNSON, P., JBoss in Action: Configuring the JBoss Application Server. Manning Publications, 2009. ISBN: 978-1933988023. [6] PELEGRI-LLOPART, E., YOSHIDA, Y., MOUSSINE-POUCHKINE, A., The GlassFish Community Delivering a Java EE Application Server [online]. [cit. 8. 2. 2014]. Datum poslední revize září 2007. Dostupné z WWW: < https://glassfish.java.net/faq/v2/GlassFishOverview.pdf>. [7] HAINES, S., Pro Java EE 5 Performance Management & Optimization. Apress Academic, 2006. ISBN: 978-1590596104. [8] HUNT, C., JOHN, B., Java Performance. Addison-Wesley Professional, 2011. ISBN: 978-0137142521. [9] CHARBONNEAU, P. - H., Top 10 Causes of Java EE Enterprise Performance Problems [online]. [cit. 23. 2. 2014]. Datum poslední revize 19. 6. 2012. Dostupné z WWW: . [10] PANKAJ, K., What is JMX? 10 mins Quick Start JMX Tutorial [online]. [cit. 30. 3. 2014]. Datum poslední revize 4. 2. 2013. Dostupné z WWW: . [11] SKEIE, J. H., Effective Java Profiling With Open Source Tools [online]. [cit. 11. 5. 2014]. Datum poslední revize 28. 9. 2011. Dostupné z WWW: . [12] COHEN, Z., JProfiler: Your Java Code Could be Running Faster in Under Two Hours [online]. [cit. 11. 5. 2014]. Datum poslední revize 7. 9. 2018. Dostupné z WWW: . [13] CHAPPELL, D., MONSON-HAEFEL, R., Guaranteed Messaging with JMS (Part2) [online]. [cit. 4. 5. 2014]. Dostupné z WWW: .
47
[14] DAS, A., Understanding JPA, Part2: Relationships the JPA way [online]. [cit. 11. 5. 2014]. Datum poslední revize 24. 1. 2008. Dostupné z WWW: .
48
7 Přílohy
V archivu diplomové práce se nachází následující soubory:
WarehouseModelWildFly-1.0.jar
WarehouseModelGlassFish-1.0.jar
WarehouseModelClient-1.0.jar
EntityAnalyzer-1.0.jar
Soubor WarehouseModelWildFly-1.0.jar obsahuje model skladové aplikace a potřebuje ke svému běhu databázi Oracle a server WildFly 8.0.0. Soubor WarehouseModelGlassFish-1.0.jar obsahuje stejný model skladové aplikace, ale potřebuje ke svému běhu server GlassFish 4.0. Soubor WarehouseModelClient-1.0.jar je jednoduchý klient, který přes webové služby spustí test skladové aplikace běžící na aplikačním serveru. Soubor EntityAnalyzer-1.0.jar obsahuje zjednodušený analyzátor entit a může být vložen do jakékoliv Java EE aplikace.
49