Akka: Actor model a use cases pro výkonné paralelní systémy

V Etneteře pod taktovkou Karla Smutného, organizátora Czech Scala Enthusiast, proběhly dva workshopy zaměřené podle přání Etneteráků na Akka framework přinášející čerstvý vítr do končin paralelního programování. V tomto článku se podíváme na základní principy aktorů a příklady aplikací, ve kterých se  mohou aktoři uplatnit.

Vítr je čerstvý hlavně v končinách Scaly a Javy, kde lze aktory díky Akka frameworku snadno používat, teoretické základy samotného modelu, který se následně proslavil v jazyce Erlang, byly položeny už v roce 1973 v publikaci pánů Hewitta, Bishopa a Steigera a dále rozvíjeny v univerzitním prostředí ve výzkumných skupinách na Massachusetts Institute of Technology a California Institute of Technology. Dosud nebyla actor modelu věnována patřičná pozornost, jakou si získala jiná mainstreamová témata jako objektově orientované programování.

Actor model

Model aktorů je založen na topologicky uspořádané soustavě uzlů - aktorů, kde každý aktor má svou schránku na přijímání zpráv (mailbox) a vlastní stav, ke kterému má výlučný přístup (neměl by být přímo dostupný ostatním aktorům). Aktoři pak spolu v rámci konkrétní aplikace komunikují výhradně zasíláním zpráv, na základě zpráv mění svůj stav a odesílají další zprávy a tím vykonávají "něco užitečného", například rozsáhlý distribuovaný výpočet, nebo zpracování dat v paralelním tasku, který má k dispozici více výkonných uzlů.

Zprávy se řadí aktorům-adresátům do mailboxů a ti je zpracovávají, typicky v pořadí, v jakém přišly. V jednom časovém okamžiku zpracovává daný aktor maximálně jednu zprávu (ne více zpráv najednou). Tento přístup má jeden velmi důležitý důsledek: V jednom časovém okamžiku (při zpracování jedné zprávy) je potřeba jen jedno vlákno, které může měnit interní stav aktora. Stavové informace aktora tak není potřeba synchronizovat (zamykat) vůči přístupu více vláken najednou, protože taková situace nikdy nenastane.

Navíc zprávy zasílané mezi aktory by měly být výhradně neměnné (immutable), aby informace ve zprávách nemohly být nějakým aktorem pozměněny (a například jeden příjemce zprávy neovlivňoval obsah zprávy čtený i jinými přijemci). Aktoři by měli měnit výhradně jen svůj interní stav.

Snazší vývoj paralelních aplikací

Takto postavený model umožňuje vyvíjet úlohy pro paralelní zpracování dat osvobozujícím způsobem - není vůbec nutné se zabývat náročnou synchronizací dat pomocí nejrůznějších monitorů, zámků, semaforů a bariér (viz. javovské API v balíčku java.util.concurrent). Zdlouhavé "mentální cvičení" s těmito synchronizačními primitivy, ve kterém malá nepozornost může vést k nedostatečné synchronizaci (a vzniku nekonzistencí v datech), nebo přílišné synchronizaci (a vzniku deadlocků), může být frustrující na odladění a otestování požadované funkčnosti a může být zdrojem těžko odhalitelných a opravitelných chyb v produkčních systémech.

V podobě actor modelu dostává programátor naopak do ruky mocný nástroj, ve kterém lze z jasně definovaných stavebních kamenů skládat postupně i velmi složité distribuované systémy, složené z mnoha nezávislých komponent (skupin aktorů/celých systémů aktorů). Uvažování nad zasíláním zpráv v systému aktorů, pomocí kterého se přímo modeluje business logika v aplikaci, je snazší než uvažování nad synchronizací dat a vzájemnou komunikací vláken, které snadno strhává vývoj od podstatné implementace business logiky k detailům na nižší úrovni.

Díky snazšímu uvažování nad posíláním zpráv v actor systému dokáže programátor napnout svojí mentální kapacitu směrem k výstavbě mnohem komplexnějších systémů, usnadnit si méně zajímavou práci a zvednout produktivitu.

Příklad burzovního systému

Na workshopu jsme řešili příklad jednoduchého burzovního systému, ve kterém aktor-burza přijímá zprávy na přihlášení jednotlivých aktorů-obchodníků k obchodování na burze, resp. zprávy na jejich odhlášení, jako další typ zpráv přijímá nabídky na prodej a nákup komodit (určitého množství o určité ceně). Burza tyto nabídky zpracovává - páruje - a zpětně informuje registrované obchodníky o proveditelných obchodech. Přitom obchodníci, kteří podali spárovanou nabídku prodeje/nákupu, dostávají od burzy zprávu vedoucí k uzavření obchodu - vyúčtování na straně obchodníka. Vyúčtování neřeší přímo aktor-obchodník, ale další typ aktora (účetní kniha), pro kterého ochodník "pracuje", a který eviduje dostupný kapitál a transakce snižující/navyšující kapitál uskutečněnými nákupy/prodeji komodit.

Další příklady použití aktorů

Použití aktorů je velmi vhodné právě v uvedeném příkladu burzovního systému, v (distribuovaných) simulačních systémech, obecněji v systémech, které vyžadují paralelní zpracování dotazů (fulltextové vyhledávání, webové servery obsluhující velké počty klientů, cloudové služby s mnoha klienty), událostně orientovaných systémech, při dávkovém zpracování a obecně i v dalších oblastech, ve kterých lze zpracování rozdělit na poměrně malé, dobře definované kompetence (jednotlivé aktory), a následně jednotlivé výsledky zpracování sloučit, nebo na jednotlivé postupně vracené výsledky např. navázat reaktivní chování dalších integrujících se systémů (může se jednat i o realtime zobrazování výsledků ve vrstvě uživatelského rozhraní). Není to nepodobné komunikaci jednotlivých tříd v objektově orientovaném programování, které se vzájemnou komunikací snaží o dosažení určitého výsledku.

V Etneteře jsme aktor model využili při implementaci backendových tasků, u kterých lze počtem současně existujících aktorů-dělníků omezit počet paralelně běžících instancí tasku, a pro rozsáhlý import dat, který se skládá z více zdrojů dat a předzpracování těchto dat (z více kompetencí). Také využíváme Play! framework, webový framework, který má pod kapotou Akka framework zajišťující zpracování požadavků na webovou aplikaci a zpracování "uživatelsky definovaných" asynchronních operací.

Akka framework

Akka framework implementuje model aktorů na produkční úrovni a poskytuje přívětivé API navržené na základě best practices v této oblasti. Práci s aktory umožňuje přímo také standardní knihovna Scaly (balíček scala.actors). Toto API ale není tak odladěné a robustní pro produkční nasazení jako Akka framework a ve Scale 2.11 je označené jako deprecated.

Klíčové vlastnosti Akky

Závěrem si shrneme podstatné vlastnosti Akka frameworku, které naznačují, kam až můžeme při používání Akky zajít:

  • Aktoři "žijí" v kontextu vytvořeného systému aktorů. Aplikace se může skládat z více systémů (hierarchií) aktorů.
  • Ošetření chyb v komponentách distribuované aplikace lze izolovat tak, aby chyba v určité části aplikace neovlivnila jiné části aplikace (výjimky mohou být propagovány hierarchií aktorů).
  • V případě výpadku aktora může nadřízený aktor (supervisor) zajistit znovuobnovení subsystému. Lze implementovat různé strategie supervisorů.
  • Aktoři mohou dynamicky (programově) měnit topologii, přecházet od řešení jednoho typu úkolu k jiným typům úkolů - obsluze jiné množiny zasílaných zpráv (implementovat stavové automaty).
  • Aktoři mohou dynamicky řídit vznik a zánik dalších aktorů (dětí v rámci hierarchie), přemísťovat se do jiných lokalit.
  • Komponentám lze vyhradit samostatné pooly vláken tak, aby žádná z kritických částí aplikace nebyla výkonově ovlivněna jinými částmi.
  • Při práci s aktory se používají transparentní reference, které programátora odstiňují od konkrétního umístění aktora (ten může existovat na stejném stroji jako aplikace nebo na jiném uzlu v síti).
  • Jen změnou konfigurace lze z lokálního systému aktorů vytvořit systém distribuovaný mezi řadu výpočetních uzlů.
  • Počty obslužných vláken pro jednotlivé komponenty složené z aktorů lze rovněž dimenzovat v konfiguraci.
  • Akka nabízí out-of-box způsoby pro uložení stavu aktorů.
  • Kód frameworku je důsledně ošetřen tak, aby nezpůsoboval out-of-memory chyby.
  • Pokud aktor neumí příchozí zprávu zpracovat, může ji ošetřit obecným způsobem (např. přeposlat jinému aktoru), jinak zpráva propadne do dead-letter fronty, která je vyprazdňována, aby nedošlo k neustálému plnění paměti.
  • Existuje framework pro testování vzájemné komunikace aktorů - Akka TestKit. Přitom testovat paralelní aplikace není obecně vůbec jednoduché. Testovat lze:
    • Změnu interního stavu aktorů po přijetí zpráv. Framework k internímu stavu umožní při testování přístup.
    • Obsluhu přijaté testovací zprávy.
    • Přijetí/nepřijetí zprávy po zaslání zprávy jiným aktorem.
    • Pokud nám nezáleží na konkrétním typu spolupracujících aktorů, můžeme využít předpřipravené testovací aktory (TestProbes).

Článek obsahuje 2 komentáře

  • Zdeněk Merta

    1
    Pěkný článek.

    Dá se někde najít kompletní specifikace toho burzovního systému? Šlo by to případně zařídit? Mohlo by být zajímavé zkusit to napsat i pomocí jiných technik než za použití actorů.
  • Radek Beran

    2
    Příklad jednoduchého burzovního systému vznikal na workshopu, který probíhal formou coding dojo, s použitím TDD. Formálně žádnou specifikaci nemáme, byl tvořený ad-hoc pro potřeby školení a detaily se "ladily" za pochodu. Z globálního pohledu je popsaný v článku a podrobnosti lze vyčíst nejlépe přímo ze zdrojových kódů, resp. testovacích suit, které jsou součástí příkladu - viz. https://github.com/vmencik/etn-scw5