RPC megoldás wanted

 ( Joejszaka | 2013. január 13., vasárnap - 23:19 )

Olyan Remote Procedure Call megoldást keresek, amiknek megfelelnek a következő kritériumok:
- átlátszó, hogy egy egy hívás process-en belüli, vagy processzek közötti, vagy két különböző gépeken történik TCP/IP-én a hívó szempontjából
- egyszerű API, nem bloated
- támogat C++-t, esetleg python-t (csak C++ is megfelel, esetleg C only ha minden kötél szakad)
- hibatűrő, vagy legalábbis világos a kivételkezelése
- nem akarok TCP/IP kapcsolatok építésével és kivételkezelésével szívni, jó lenne, ha ezt adná a lib.

Mire kell? Egy távoli command que implementáción dolgozok egy hobbi projecten. Kliens küld parancsokat, a szerver végrehajtja őket sorban, és a kliensnek visszaküldi a választ. A filozófia: keep it simple.
Első körben egy ZeroMQ nevű könyvtárat használtam, és Google Protocol buffereken küldtem át az infót.
Csakhogy a ZeroMQ-t nem találom elég robosztusnak. Ötletek?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

A kovetelmenyek alapjan pont 0MQ-t gondoltam javasolni. Mi nem eleg robosztus benne?

--
|8]

Hát, nekem az a bajom, hogy valójában nekem PEER tipusú ZMQ socket-re lenne szükségem, de az nincs kész, és így viszont bonyolult.

A valós problémám a következő (egyszerűsítve): létrehoztam egy REQ-REP tipusú socketet.
Kliens REQ, szerver REP.

A kommunikáció ilyen
1. kliens: send "msg"
2. szerver: recv "msg"
3. szerver: send "reply"
4. kliens: recv "reply"

Namost, ha a szerver meghal a 2. és 3. pont között, akkor a kliens beragad a 4. pontban.
Gondoltam, megheggesztem. Beraktam egy timeout-ot a a recv hívásba (ami korántsem volt triviális).
Ekkor viszont a később zmq context felszabadítása elszállt.

Ráaadásul az asynchron send hívást nem is értem, túl sok kétségem van, hogy mennyire megbízható ez az egész.

A filozófia problémám meg az, hogy nekem egy darab full duplex aszinkron csatorna kéne. Ehelyett kezelnem kell kettőt, mind a kettőt handshaking-gel. Vagyis egy 4x-es az overhead.

Csak rászántam magam és belenéztem a 0mq dokumentációjába. Felteszem, hogy a PUB-SUB működik.

PUB-bal küldd át a kérést, melyben legyen kérés- (jobb esetben: lenyomat-)azonosító.

A választ szintén PUB-bal küldd át, amely tartalmazza a kérésazonosítót. Ennek fogadására külön, független SUB kell a kliensben. A visszatérő választ a korábban feladott kéréssel külön logikával (explicit állapottal, tehát nem függvényhívás stack-jével) tudod összekötni.

A transzport timeout, a válasz transzport timeout-ja, illetve a feldolgozási probléma/timeout nem különböztethető meg. Ez nem baj. A logika olyan legyen, hogy azonos kérés- (jobb esetben: lenyomat-)azonosítót akárhányszor kaphasson meg a szerver. Így nem kell olyan művelet, amellyel függőben lévő események / lenyomatok feldolgozását kérdezed le; kérdés helyett add fel újra az eredeti kérést. Ez elintézi az átmeneti transzport hibákat. (Ha a szerver már kész a feldolgozással, csak a válasz veszett el, akkor újra visszaküldi a már kész választ.)

A tartós szerveroldali hibát megtalálod a szerver logjában, illetve (ha nem párosul átviteli problémával is) a válaszban is szerepelhet.

A vezérelv az, hogy fogalmad sincs, mi van a másik oldalon, illetve közben a hálózaton, ezért rengeteget fogsz faggatózni, újra kérni. Ezeknek az ismételt kéréseknek (kérdéseknek) nem szabad, hogy káros hatása legyen.

Hat, majdnem pont ezt csinaltam, csak nem PUB-SUB-bal, hanem REQ-REP-pel. Na es ott volt a problema, sajnos neha a olyan state-be kerult a requester, amibol a zmq nem tudott kilepni.

Megprobalom a PUB-SUB-ot, igazad van, eszembe se jutott, mert csak peer to peer kapcsolat kell nekem.

átlátszó, hogy egy egy hívás process-en belüli, vagy processzek közötti, vagy két különböző gépeken történik TCP/IP-én a hívó szempontjából

Ne haragudj, hogy a nem a kérdésre válaszolok, de ez így csak egy bizonyos szintig használható. A fentit sokan szokták kérni kényelmes programozáshoz, de ilyen nincs. A processzen belüli hívás gyökeresen máshogy viselkedik, mint a hálózaton (esetleg WAN-on) keresztüli hívás. Erre temérdek példa van, néhányat megpróbálok felsorolni. Az eltérő viselkedés két okból fakad. Az első a latencia (a hívás relatív hossza a program egyéb tevékenységeihez képest), a második pedig az extra hibalehetőség.

A másodikat gyorsan el lehet intézni; arról van szó, hogy helyileg létezik olyan, hogy egy hívás mindig sikerül (mert a program logikája minden körülmények között értelmes eredményt ad vagy mellékhatást eredményez, és mert olyan nincs, hogy maga a függvényhívás mechanizmusa döglene meg (elfajuló eseteket, mint pl. végtelen non-tail recursion-ből fakadó veremtúlcsordulást most hanyagoljunk). Amikor a hívott logika átmegy a hálózaton, ott nincs olyan, hogy "garantáltan sikeres hívás"; mindig lehetséges transzport hiba, vagy (ami még rosszabb, mert fogalmad sincs az eredményről), transzport timeout.

Az első (a latencia) gyorsan ki szokta kényszeríteni az aszinkron vagy kötegelt kódolást; mindkettő elég messze áll a hagyományos függvényhívástól

Kötegelt kérésnél a kéréseket összerakod egy kötegbe, egyben kilövöd a szerver felé, aztán várod a kötegelt választ. Addig vagy csinálsz valamit (aszinkron is), vagy nem (szinkron). A hívás költsége eloszlik a kötegen. A válaszköteg feldolgozása különösen mulatságos, mert lehet részleges hiba (3 egyedi request sikerült, 8 meg nem), így már a kérések kötegelésénél is figyelned kell(het), hogy csak egymástól független kéréseket küldj.

Aszinkron kérésnél feladod a kérést (lehet egyedi vagy köteg), de a választ nem várod be rögtön; a transzport a háttérben megy. Feladhatsz további kéréseket. A beérkező válaszokra elkezdhetsz valamikor blokkolva várni, vagy időnként ránézni az input queue-ra, vagy köthetsz rá callback-et.

Példák:

- NFS. Kliensbeli cache-elés nélkül az NFS write() dögletesen lassú. Helyi cache-eléssel (ami az általános) viszont nem POSIX-konform. Az NFS write() aszinkron (és nem kötegelt), míg a POSIX write szinkron (bármely más read(), amely eléri ugyanazt a filesystem-et, és amelyet a write() visszatérése után indítottak, köteles látni az adatot).

- scatter-gather IO, mind userspace-kernel határon (ld. bármely rendszerhívás, amely iovec struct-tal dolgozik), mind kernel(CPU)-hardver határon (scatter-gather DMA). A motiváció ugyanaz, a rétegek közötti váltás költséges. Ez kötegelt, és szinkron (writev() reguláris file-ra, read()-re nézve) ill. aszinkron is lehet (sendmsg() vagy scatter-gather DMA).

- webservice hívása; a séma szokott olyan lenni, hogy adott complex element-ből végtelen multiplicitást megenged. Kötegelt, lehet szinkron (a válasz jön egyből) vagy aszinkron (tranzakcióazonosító jön vissza, amivel pollozni kell, vagy a másik oldal visszahív az eredménnyel).

- Oracle PL/SQL-ben kötegelt INSERT, UPDATE, DELETE; azért, hogy a PL/SQL és az SQL motor között ritkábban kelljen váltani. (Lásd: FORALL, bulk bind; illetve kurzorból FETCH ... BULK COLLECT INTO). Kötegelt, szinkron.

- scp sok kis file-ra, vs. "tar -c ... | ssh remote | tar -x". Az első szinkron, jó hibakezeléssel, a második aszinkron, pocsék hibakezeléssel.

Mire kell? Egy távoli command que implementáción dolgozok egy hobbi projecten. Kliens küld parancsokat, a szerver végrehajtja őket sorban, és a kliensnek visszaküldi a választ. A filozófia: keep it simple.

Az üzenetsor logikailag aszinkronnak tűnik (az üzleti feldolgozást nem várod meg), de maga a "put to queue" művelet is lehet dögletesen lassú, ha a hívás szinkron, közvetlenül a távoli szerverrel beszélget, és sok kis üzenetet kell feladni. Legalább egy kötegelt interface-re (struct tömb feladására) szükség lehet, de az meg sok memóriát kérhet.

... Ilyesmire sok pénzért árulnak messaging middleware termékeket (nem véletlenül drágák), de azokban szokott lenni helyi queue szerver is (tehát a helyi hívás gyorsaságát és biztonságosságát azon az áron tartják fenn, hogy egy másik helyi komponensre hárítják a rizikót). Hobbiprojektnek szerintem megfelel a zeromq.

Csakhogy a ZeroMQ-t nem találom elég robosztusnak.

Konkrétan mi a probléma? Egyébként pont a robusztusság (sorrendtartás, üzenetvesztés kizárása queue szerver crash vagy hálózati kiesés esetén) és a teljesítmény kombinációja miatt marha drágák ezek a vállalati termékek.

Mindenképpen hasznos idempotens parancsokat kiküldeni, forrásoldali lenyomatszámlálóból bélyegezve. Az idempotens azt jelenti, hogy nem egy adott műveletet (átmenetet) írsz elő a szerver számára, hanem egy adott célállapotot, amibe át akarod rugdalni. Az ilyen üzeneteket ötszázszor is átküldheted, mert ugyanazt írják elő a szerver számára. A konkrét műveletet a szerver határozza meg, a jelenlegi és a kért állapot összehasonlításával. Az üzenetek felcserélődése ellen pedig a forrásoldali lenyomatszámláló véd (amelyet a szerver ment a kért állapot sikeres előállításakor); ha korábbi üzenet (lenyomat) érkezik később, a szerver kukázza.

Nagyjából mint egy keyframe egy video feed-ben (de ehhez abszolút nem értek, szóval rossz hasonlat). Nem számít, mi volt előtte, mert önállóan leírja a teljes képet.

"Aszinkron kérésnél feladod a kérést (lehet egyedi vagy köteg), de a választ nem várod be rögtön; a transzport a háttérben megy. Feladhatsz további kéréseket. A beérkező válaszokra elkezdhetsz valamikor blokkolva várni, vagy időnként ránézni az input queue-ra, vagy köthetsz rá callback-et."

Pont igy csinalom.

Az idempotens otlet is hasznos volt. Lehet, hogy nalam agyuval verebre, meggondolom.