Neblokující volání SOAP webových služeb

Představte si situaci, kdy musí vaše webová aplikace při obsluze požadavků od klientů volat nějakou webovou službu. Pokud se z toho nemůžete vyvléct cachováním nebo jiným úhybným manévrem, tak se mnou jistě budete souhlasit, že je to vyhlídka neradostná.

Webové služby můžou mít dosti nestabilní dobu odezvy, která může být i v řádu sekund. Taková věc pak může zasadit smrtelnou ránu škálovatelnosti vaší aplikace. Nemusí to tedy být problém pro nějaký intranetík s pár uživateli, ale pro veřejně dostupný web s ambicí mít zajímavý počet návštěvníků je to zásadní problém.

Jak z toho ven?

V první řadě musíte vaši aplikaci psát v něčem, co podporuje i jiný než klasický model, kdy je pro obsluhu požadavku přiděleno jedno vlákno, které nemůže dělat nic jiného dokud celá obsluha požadavku neskončí.

To v Javě (teď myslím té standardizované) není žádná samozřejmost. S možností asynchronní obsluhy požadavků přišla až specifikace Servlet 3.0. Nicméně nepředpokládám, že vaše aplikace píšete jako čisté servlety, takže ještě potřebujete, aby asynchronní zpracování podporoval váš webový framework.

Od konce loňského roku máte k dispozici například Spring MVC 3.2, které nové možnosti Servlet 3.0 podporuje. Já ale tento problém budu dále řešit v kontextu frameworku Play a Scaly.

JAX-WS asynchronně

Když už se budeme držet těch standardů, tak dnes už přímo v JDK máme API pro práci se SOAP webovými službami v podobě JAX-WS. Tento standard asynchronní volání podporuje.

Funguje to tak, že při generování stub tříd klienta webové služby explicitně řeknete, že chcete kromě klasických synchronních metod vygenerovat do rozhraní služby i metody pro asynchronní volání. Na kompletní ukázku takového rozhraní se můžete podívat zde (všimněte si dvou metod se jmény končícími na “Async”).

Webovou službu pak budete volat asynchronně a při volání předáte metodě callback, kterým budete reagovat na výsledek, až bude znám. Zde také musíte vyřešit napojení na asynchronní možnosti vašeho webového frameworku.

V Play je potřeba vyrobit scala.concurrent.Future nesoucí vaší odpověď klientovi. Jeden možný způsob si můžete prohlédnout zde. Ve Spring MVC byste nejspíš použili DeferredResult, do kterého byste v callbacku nastavili výsledný model a/nebo view.

Asynchronní ještě neznamená neblokující

Máme tedy vyhráno? Ne tak úplně. Teď už skutečně vlákna obsluhující požadavky klienta neblokujeme. Co se ale vlastně děje na pozadí, když na JAX-WS proxy zavoláme jednu z těch asynchronních metod?

To záleží na implementaci JAX-WS, kterou vaše aplikace používá. Typické JRE (např. od Oracle) obsahuje referenční implementaci JAX-WS RI. V něm jsou asynchronní volání řešena pomocí poolu vláken, který spravuje JAX-WS. Ke skutečné HTTP komunikaci s webovou službou pak dochází v těchto vláknech. Referenční implementace JAX-WS ale používá HTTP klienta obsaženého přímo v JDK, který je blokující.

Celým výše uvedeným postupem jsme zatím pouze přesunuli blokující volání z poolu vláken webového frameworku do poolu, který spravuje implementace JAX-WS. Škálovatelnosti jsme tedy zatím moc nepomohli.

Neblokující HTTP klient a CXF

Naštěstí ale existují i jné implementace JAX-WS než ta referenční. Nejlepší zkušenosti mám s Apache CXF. V tomto případě využijeme její schopnost používat neblokujícího HTTP klienta.

Jediné, co musíme udělat, je přidat aplikaci dvě nové závislosti:

  • cxf-rt-frontend-jaxws je samotná implementace JAX-WS pomocí CXF
  • cxf-rt-transports-http-hc je komponenta CXF, která se stará o HTTP komunikaci a využívá při tom neblokujícího HTTP klienta

Jenom přítomnost těchto knihoven v classpath zajistí, že se jako implementace JAX-WS bude používat CXF a že to bude pro asynchronní volání používat neblokujícího HTTP klienta.

Mimochodem ten HTTP klient je z dalšího Apache projektu: HTTP Components. Ve spod používá Java NIO a Rector pattern, který popsal Doug Lea.

Vady na kráse

Nyní už máme řešení, kdy při volání webových služeb nedochází k blokování vláken. Aplikace proto vláken nepotřebuje ani zdaleka tolik a v tomto ohledu škáluje daleko lépe než při blokujícím řešení.

Přesto bych ale ještě upozornil na dva nedostatky, které můj ukázkový projekt neřeší:

JAX-WS je trochu zákeřné a při vytváření Service objektu provede blokující stažení WSDL služby. V prvé řadě bych proto doporučoval mít pro každou službu ten Service objekt globální a rozhodně jej nevytvářet při každém volání. Navíc bych asi raději to WSDL uložil někde lokálně, aby jeho dostupnost byla pod vaší kontrolou (jeho URL můžete předat konstruktoru služby).

Druhá věc je, že použití CXF a neblokujícího HTTP je v podstatě zařízeno jenom sestavením aplikace a projeví se až za běhu. Možná by nebylo od věci ověřit při inicializaci aplikace, že implementací JAX-WS je opravdu CXF a že má k dispozici ten neblokující HTTP transport.

Článek obsahuje 3 komentáře

  • Guido

    1
    Ta úvodní část se mi moc nelíbí - obsahuje tvrzení bez kontextu. Bylo by dobré aspoň zmínit doménu a pár ukotvujících informací. A pak teprve říct, že SOAP WS můžou být problém.

    Většinou je to spíš problém architektury a v pozadí tak nějak čtu, že její návrh nemáte až tak pod kontrolou.

    Od popisu rešení už je článek pěkný a líbí se mi, jak jde do hloubky.

    Obecně, pokud k tomu není důvod, je lepší mít WSDL lokálně, právě kvůli tomu jeho načítání.

    A ještě mě napadá jedna otázka. Celé mi to přijde jako postavené na dokumentaci/specifikaci. Dělali jste nějaké testy (performance, škálovatelnosti apod.), které by to fungování potvrdily?
  • Vlastimil Menčík

    2
    Ten článek (naštěstí) není založen na nějaké konkrétní aplikaci, která by skutečně musela (téměř) pro každý request volat webovou službu. Proto také začíná slovy "představte si". Většinou se tomu dá nějak vyhnout, ale chtěl jsem řešit situaci, kdy by to prostě nebylo možné. Kontextem je tedy hypotetická frontend webová aplikace, která pomocí WS třetí strany přistupuje k datům, které zobrazuje (případně i mění).

    Tím pádem nemám ani změřená konkrétní čísla. Výhody neblokujícího IO při obsluze konkurentních requestů jsem považoval za dostatečně prokázané.

    Srovnáním klasického blokujícího přístupu s neblokujícím se zabývá např. tenhle článek: http://jazzy.id.au/default/2012/11/02/scaling_scala_vs_java.html
  • Juan Manuel Lopez

    3
    Hello, i have soap client create based on wsdl, i need to implement non blocking async, that service are consuming for many users. can you help me?