Category Archives: Uncategorized

Životní cyklus programátora

Včera jsem narazil na zajímavý článek o životním cyklu programátora. Stejně jako software má svůj životní cyklus, něco podobného existuje i pro schopné programátory. Skládá se z následujících fází:

Euforický – první fáze při nástup na nový projekt. Programátor je stimulován novostí práce a výzvami, které ho čekají. Tato fáze trvá dokud se programátor neseznámí s novým oborem a prostředím.

Produktivní – Jakmile se programátor aklimatizuje, jeho zájem a produktivita dosahuje vrcholu. V této fázi programátor píše nebo přebírá kritickou část systému. Tím roste jeho cena pro zaměstnavatele.

První dvě fáze tvoří takzvané líbánky, následující dvě fáze jsou částí tzv. vrtkavého (volatile) období.

Nenahraditelný – Management přijde na to, že se programátor stal nenahraditelným zbožím. Jeho cena ve firmě je na svém vrcholu. Tím stoupají i jeho odměny a management dělá vše pro to, aby si ho udržel. Programátor se cítí jako mistr světa. Tato fáze netrvá dlouho.

Naštvaný – Management začíná být naštvaný, že jediný jedinec je zodpovědný za úspěch nebo neúspěch projektu. Ze strachu začne jednat tak, jako by programátora vlastnil. Symptomy jsou jednoznačné, programátor musí nosit pager (mobil), pracovat o víkendu, mít doma internet aby mohl pracovat z domova a za žádnou cenu si nesmí brát dovolenou. I programátor začíná být naštvaný na to, že se mu management odvděčuje za jeho práci tím, že mu nakládá stále více práce. Občas tato fáze končí tím, že programátor odejde, ale často také se tato fáze usadí v rovnováze mezi tím jak firma programátora potřebuje a touhou programátora být hvězda.

Znuděný – V této fázi programátor tráví většinu času podporou, schůzkami s managementem a předáváním znalostí ostatním. Protože všechny má výzvy spojené s novým projektem a prostředím zvládnuty, je tato práce málo intelektuálně náročná a přichází nuda. Ta s sebou přináší i výrazný propad produktivity.

Neproduktivní – Je nepravděpodobné, že by se programátor vydržel nudit věčně. Něco se musí zlomit. Klasické symptomy jsou takové, že se programátor prohlíží stránky s nabídkami práce na internetu a management ho považuje za předraženého. Tato fáze končí přechodem na nový projekt nebo k novému zaměstnavateli.

Smutná historie co? Bohužel pravdivá, mám pár kolegů, na které to dost přesně sedí.

Terracotta a distribuované metody

Dnes budu pokračovat v popisu svých experimentů s Terracottou. Povíme si něco o distribuovaném volání metod (Distributed Method Invocation). Motivace je jednoduchá. Představme si, že uživatel změní přes webové rozhraní globální nastavení aplikace. O této události potřebujeme informovat všechny členy clusteru. V EJB ale i bez nich je řešení velmi obtížné. Jediné mě známé řešení je šíření této události pomocí sdílené databáze. (a samozřejmě JMS viz. diskuze (updated 6.12))

Jak nám v tom pomůže Terracotta? Hodně. Umožňuje označit metodu jako distribuovanou. Tzn. pokud mám instanci objektu s takovouto metodou dostupnou z rootu a na jednom z členů clusteru tuto metodu zavolám, zavolá se mi i na ostatních členech. Ukažme si to na příkladu. Mějme následující triviální listener.



1 public class Listener {
2   public void actionPerformed(String action)
3   {
4     System.out.println(Thread.currentThread().getName()+" says \""+action+"\"");
5   }
6 }

Označíme metodu actionPerformed jako distribuovanou (viz. XML updated 28.11.2007) a nasimulujeme si její volání. Nejdříve musíme umístit instanci listeneru do rootu. Použijeme na to skladiště, které máme z minula. Pak už jenom zavoláme metodu listeneru.



1 public class Sender {
2   public static void main(String[] args) {
3     Store store = new Store();
4     Listener listener = new Listener();
5     store.put("listener", listener);
6     listener.actionPerformed("Action!");
7   }
8 }

Pokud vysílač běží na jediném virtuálním stroji spuštěném v clusteru, nic zvláštního se nestane. Jenom se nám vypíše hláška na konzoli. Zajímavé to začne být, až když spustíme další instanci. Přijímající stranu nejjednodušeji nasimulujeme takto.



1 public class Receiver {
2   public static void main(String[] argsthrows InterruptedException {
3       Thread.sleep(60000);
4   }
5 }

To je nejjednodušší příklad který mě napadl. Pokud tento přijímač běží v okamžiku kdy spustím vysílač, vypíše se mi v konzoli přijímače WorkerThread(dmi_stage,0) says "Action!". Cože, kde se mi tam vzalo další vlákno, vždyť ta moje aplikace je evidentně jednovláknová? Není! V okamžiku kdy jakoukoliv aplikaci spustím v Terracottě, musím počítat s tím, že je vícevláknová a chovat se podle toho. Zopakujme si, co znamená distribuované volání metod. Zavolám-li metodu, která je označena jako distribuovaná, na jednom členu clusteru, zavolá se mi i na ostatních. V uvedeném případě mám dva virtuální stroje spojené do clusteru, metoda se mi zavolá na obou. Na jednom normálně, na druhém automagicky

Samozřejmě, uvedený příklad je umělý. V praxi by nám pravděpodobně na všech virtuálních strojích běžela stejná aplikace. Pak by to bylo o něco méně magické. Také bychom listener pravděpodobně instancovali pomocí Springu, na což je Terracotta také připravena.

Co říci závěrem. Terracotta je zajímavý nástroj, který nám umožňuje dělat věci, které by bez ní byly obtížné. Navíc podobné experimentování je neuvěřitelně snadné. Stačí si stáhnout Eclipse plugin a pak všechno jednoduše naklikáte. Zkuste si to.

Zdrojové kódy jsou na stejném místě jako minule.

Hrajeme si s Terracottou

Dnes budu psát o svých pokusech s Terracottou. Předem chci upozornit, že o ní mám jen velmi chabé znalosti, takže to prosím berte jenom jako popis experimentů, které jsem s ní prováděl. Navíc jsem i pár věcí pro přehlednost zanedbal, takže zájemce o přesnou a úplnou informaci odkáži na stránky Terracoty.

Terracotta je nástroj pro clusterování na úrovni virtuálního stroje. Funguje pomocí AOP na úrovni bytecode. Tzn. při startu aplikace upraví bytocode tříd tak, že dostane notifikaci při zápisu a čtení primitivních vlastností (field) objektů. Prostě a jednoduše, když mám třídu, která obsahuje vlastnost typu int, Terracota ví, že ji někdo změnil nebo že se ji někdo pokouší číst. Poté už je snadné distribuovat změny na více JVM. Dokonce tím i dostáváme možnost změny distribuovat až podle potřeby. Tzn. když se pokouším číst hodnotu proměnné změněnou na jiném JVM, Terracotta může změnu promítnout na mém JVM, až když zjistí, že chci tuto proměnnou číst. Princip je to jednoduchý, efektivní a elegantní. Navíc nám to umožňuje používat cosi, co se chová jako distribuovaná heap atp.

Na únorovém CZJUGu o Terracottě mluvil jeden z autorů Jonas Bonér. Tenkrát mě to dost zaujalo, ale k nějakému experimentování s ní jsem se dostal až nedávno. Co pro experimentování potřebujeme? Pokud si chcete hrát přímo s kódem, je dobré stáhnout si Eclipse plugin. Ten obsahuje jak nástroje pro práci s konfigurací v Eclipse, tak i Terracotta server, který celé clusterování řídí. Navíc se nám postará o snadné spouštění aplikací, tak aby byly clusterované.

Je důležité si uvědomit, že Terracotta samozřejmě nedistribuuje celou paměť. Sdílená je jenom část pod tzv. kořenem (rootem). Jak to funguje si ukážeme na příkladu. Mějme následující třídu.



01 public class Store {
02   private Map<String, Object> data = new ConcurrentHashMap<String, Object>();
03   public void put(String key, Object value)
04   {
05     data.put(key, value);
06   }
07   
08   public Object get(String key)
09   {
10     return data.get(key);
11   }
12 }

Poté mohu Terracotě říci, že proměnná data je kořenem a ona mi začne distribuovat všechny změny provedené v této proměnné a všech objektech na které má tato proměnná reference. Tzn. v clusteru se mi bude sdílet všechno, co umístím do této mapy. Ač to podle kódu nevypadá, kořen se chová jako singleton. A to dokonce singleton v rámci clusteru! To je to, po čem uživatelé EJB marně touží.

Konfigurace kořene je pomocí Eclipse plugin jednoduchá, prostě na proměnnou kliknu a řeknu toto je kořen. Plugin vygeneruje příslušnou XML konfiguraci.

Funkčnost můžeme vyzkoušet následovně:



01 public class AdditionTest {
02   public static void main(String[] args) {
03     Store store = new Store();
04     Integer number = (Integerstore.get("number");
05     if (number==null)
06     {
07       number = 0;
08     }
09     store.put("number", ++number);
10     System.out.println("Putting "+number+" to the store.");
11   }
12 }

Kód je jednoduchý, vyrobím novou instanci Store (skladiště), vezmu si z ní číslo, přičtu k němu jedničku a uložím ho zpět. Když tento příklad spustím jako normální aplikaci, vždy začínám s prázdnou instancí skladiště a vždy tam tedy budu ukládat jedničku. Když ale aplikaci spustím jako distribuovanou pomocí Terracotty, bude se to takto chovat jen při prvním spuštění. Při každém dalším už bude skladiště obsahovat hodnoty do něj uložené z předchozích spuštění! To je hodně proti intuici. Na jednom řádku vytvořím novou instanci a na druhém už jsou v ní nějaké hodnoty o kterých netuším jak se tam dostaly. To je ale u distribuovaných aplikací normální, můžeme si představovat, že mi je tam zapsal někdo z jiného virtuálního stroje. Navíc to, že mi data přežila konec virtuálního stroje je přeci jeden z důvodů, proč chci používat cluster. Zvídavější jistě zajímá, kde ta data přežila. Odpověď je jednoduchá, abych mohl Terracotu používat, musím mít spuštěný Terracotta server. Ten se stará o koordinaci clusteru a v našem případě i o uložení dat. Pokud vypnu i tento server (a všechny jeho případné kopie), o data přijdu definitivně.
Každá sranda samozřejmě něco stojí. Když chci clusterovat program, musím ho napsat tak, aby počítal s tím, že v něm poběží více vláken. Musím tedy používat sychnronizace, zámky atp. Terracotta se pak už postará o to, aby se aplikace chovala jako vícevláknová na jednom JVM. Když se podíváte na výše uvedený příklad, uvidíte, že se změnami z více vláken nepočítá. Než budete číst dál, zkuste schválně přijít na to, co tam je špatně. Tak co přišli jste na to? Mapa ve skladišti je typu ConcurrentHashMap, je na běh více vláken připravená. Zkuste to jinde. Pořád nic? Dobře nebudu vás napínat, určitě jste na to přišli. Chyba je v použití skladiště. Já si z něj vytáhnu Integer a o pár řádků dál do něj vložím Integer o jedničku větší. Tím ale mohu přepsat hodnotu, kterou tam mezi tím vložilo jiné vlákno! Nejprve nás napadne vyřešit to synchronizaci. V našem případě by byl problém s tím, jaký objekt použít jako zámek. Navíc existuje elegantnější řešení. Použijeme AtomicInteger, který se nám postará o bezpečné zvyšování hodnoty.



01 public class AdditionTest2 {
02   public static void main(String[] args) {
03     Store store = new Store();
04     AtomicInteger counter = (AtomicIntegerstore.get("counter");
05     if (counter==null)
06     {
07       counter = new AtomicInteger();
08       store.put("counter", counter);
09     }
10     counter.incrementAndGet();
11     System.out.println("Putting "+counter.intValue()+" to the store.");
12   }
13 }

Další cenou za clusterování je razantní snížení výkonu při manipulaci se sdílenými objekty. Terracotta musí na konci každé transakce (opuštění synchronizovaného bloku) přesunout data buď na server nebo na jiného člena clusteru pro případ, že by aktuální virtuální stroj bídně zhynul. Takže pokud dělám hodně změn ve sdílených objektech má s tím hodně práce. Pokud předchozí příklad spustím normálně ve 100 000 iteracích, proběhne za 20ms (na mém obstarožním notebooku). Pokud ho pustím v Terracottě, proběhne přibližně za 86 sekund! Což je sice pořád ještě dost slušných 1000 operací za sekundu, ale už to není ono. Pokud pustím tyto testy dva současně tak, že se mi perou o procesor, doběhnou přibližně za tři minuty. Nicméně si dovoluji tvrdit, že to na sdílení HTTPSession, cache nebo konfigurace musí stačit. Pokud se vám moje mikrobenchmarky nezdají dost vypovídající (oprávněně) podívejte se na tento dokument.

Pro dnešek už toho bylo dost, více možná napíši příště. Zdrojové kódy jsou ke stažení pomocí SVN na adrese https://java-crumbs.svn.sourceforge.net/svnroot/java-crumbs/terracotta-playground.