Chapter1

By Pieter Hintjens <moc.xitami|hp#moc.xitami|hp>, CEO iMatix Corporation.

With thanks to Bill Desmarais, Brian Dorsey, Daniel Lin, Eric Desgranges, Gonzalo Diethelm, Guido Goldstein, Hunter Ford, Kamil Shakirov, Martin Sustrik, Mike Castleman, Naveen Chawla, Nicola Peduzzi, Oliver Smith, Olivier Chamoux, Peter Alexander, Pierre Rouleau, Randy Dryburgh, John Unwin, Alex Thomas, Mihail Minkov, Jeremy Avnet, Michael Compton, Kamil Kisiel, Mark Kharitonov, Guillaume Aubert, Ian Barber, Mike Sheridan, Faruk Akgul, Oleg Sidorov, Lev Givon, Allister MacLeod, Alexander D'Archangel, Andreas Hoelzlwimmer, Han Holl, Robert G. Jakabosky, Felipe Cruz, Marcus McCurdy, Mikhail Kulemin, Dr. Gerg Erdi, Pavel Zhukov, Alexander Else, Giovanni Ruggiero, Rick "Technoweenie", Daniel Lundin, Dave Hoover, Simon Jefford, Benjamin Peterson, Justin Case, Devon Weller, Richard Smith, Alexander Morland, Wadim Grasza, Michael Jakl, Uwe Dauernheim, Sebastian Nowicki, Simone Deponti, Aaron Raddon, Dan Colish, Markus Schirp, Benoit Larroque, Jonathan Palardy, Isaiah Peng, Arkadiusz Orzechowski, Umut Aydin, Matthew Horsfall, Jeremy W. Sherman, Eric Pugh, Tyler Sellon, John E. Vincent, Pavel Mitin, Min RK, and Zed Shaw for their contributions, and to Stathis Sideris for Ditaa.

모든 설명 및 에러정보는 'issue tracker'참고하시길 바랍니다.
본 문서는 2011.12.6일 발표된 최신버전의 ØMQ를 기준으로 합니다.

가이드 소스는 주로 'C'로 되어 있으며 그 외 'PHP', ' Lua', ‘Haxe’를 사용 했습니다.

Chapter One - Basic Stuff

top prev next

Fixing the World

top prev next

ØMQ를 어떻게 설명 할 수 있을까요? 이런 미사어구를 사용할 수 있습니다. 소켓과는 차원이 다르다. 라우팅 가능한 메일박스다. 빠르다. 다른 사람들은 ØMQ에 익숙해져 가면 zap-pow-kaboom satori 패러다임과 같이 깨달아질 때의 순간을 공유하고 싶어 합니다. 일이 단순화 되고, 복잡함은 사라지며, 마음이 가벼워 집니다!. 다른 사람들은 ØMQ는 작고, 간단하지만, 친밀하게 느껴진다고 설명합니다. 개인적으로 왜 ØMQ 를 만들었는지 기억되었으면 좋겠습니다.

프로그래밍은 예술적인 과학입니다. 왜냐하면 우리 대부분이 소프트웨어 물리학을 이해하지 못하고, 그것을 가르치는 곳도 거의 없습니다. 소프트웨어 물리학은 algorithms, data structures, languages and abstractions은 아닙니다. 단지, 우리가 만들고 사용하고 버리는 도구입니다. 소프트웨어의 진정한 물리학은 사람입니다.

특히, 어려움이 닥쳤을 때 우리는 한계에 도달하게 되고, 큰 문제를 함께 풀기를 원합니다. 사람들이 쉽게 이해하고 사용할 수 있도록 빌딩블록을 만들고, 큰 문제를 해결하기 위해서 함께 일하는 것이 프로그래밍의 과학입니다.

우리는 서로 연관된 세계에 살고 있고 현대 소프트웨어는 이 세계를 탐색할 수 있습니다. 그래서 내일의 큰 솔루션을 위한 빌딩블록은 연결되어 있고 대규모 병렬화되어 있습니다. 코드는 더 이상 강한 침묵이 되어서는 안됩니다. 코드는 풀 수 있어야 되고, 서로 잘 연결되어야 합니다. 코드는 서로간의 메시지를 전달하는 수 백만개의 뉴런과 같이 중앙제어가 없어야 합니다. 또한 실패의 단일점만을 갖지 않는 거대한 병렬 네트워크로서 다른 문제도 풀 수 있는 인간의 두뇌처럼 처리해야 합니다. 몇몇 관점에서 보면 모든 네트워크의 종점은 인간 두뇌이기 때문에 코드의 미래는 인간의 두뇌에 달려 있습니다.

당신이 스레드, 프로토콜 또는 네트워크를 사용하는 모든 작업을 완료했다면, 꽤 많은 것이 불가능했다는 것을 깨닫게 될 것 입이다. 실제 상황에 적용할 몇몇 프로그램을 소켓으로 연결하는 작업도 쉽지 않고 비용 또한 상상 그 이상이기 때문입니다. 컴퓨터들을 연결하는 것은 수십억 달러 사업이기 때문에 쉬운 일이 아닙니다.

그래서 우리는 몇 년 앞서 그것을 사용할 수 있는 세계에 살고 있습니다. Fred Brook와 같이 'Silver Bullet은 없다'라고 믿었던 80년대에 소프트웨어 위기가 있었습니다. 무료 및 오픈소스 소프트웨어로 인해 효율적으로 지식을 공유할 수 있었기 때문에 그 위기를 해결했었습니다. 오늘 우리는 또 다른 소프트웨어의 위기에 직면하고 있지만 그것에 대해 많이 이야기 하지 않고 있습니다. 규모가 크고, 부유한 회사만이 연결된 응용 프로그램을 개발 할 수 있습니다. 클라우드가 있지만 독점되어 있는 실정입니다. 개인이 액세스 할 수 없고 경쟁 할 수 없는 클라우드 환경에서 데이터와 지식은 우리의 개인 컴퓨터에서 사라질 것입니다. 누가 사회적 네트워크를 소유하겠습니까? 이것은 mainframe-PC의 혁명과는 반대로 가는 길입니다.

우리는 또 다른 책에 정치 철학을 남길 수 있습니다. 요점은 “인터넷이 대규모로 연결된 코드의 가능성을 제공하는 반면, 현실은 우리 대부분이 도달하지 못하고 그래서 더 큰 흥미로운 문제(건강, 교육, 경제, 교통 등)가 발생되지만, 이런 문제를 해결하는 방법이 없고 이러한 문제를 해결하기 위해 함께 일할 수 있는 두뇌를 연결하는 방법이 없기 때문에 미해결로 남는다” 입니다.

연결 software는 많은 시도를 해 왔습니다. 수 천개의 IETF이 있고, 퍼즐의 각 해결 부분이 있습니다. 응용 프로그램 개발자를 위해 HTTP는 충분한 정보를 제공하는 한 솔루션입니다. 하지만 그것은 틀림없이 큰 서버와 얇은, 바보 같은 고객의 관점에서 생각하는 개발자 및 아키텍트를 장려함으로써 더 나쁜 문제를 만들 수 있습니다.

그래서 오늘 사람들은 여전히 원시 UDP 및 TCP, 사설 프로토콜, HTTP, WebSockets를 사용하여 응용 프로그램을 연결하고 있습니다. 그것은 어렵고, 느리고, 규모 산정이 어렵고, 본질적으로 중앙 집중되어 집니다. 분산 P2P 아키텍처는 거의 작업 없이 동작합니다. 얼마나 많은 응용 프로그램이 데이터 교환을 위해 Skype나 Bittorrent를 사용하나요?

프로그래밍 과학은 우리에게 제공해주는 것이 있습니다. 문제를 해결하기 위해, 우리는 두 가지를 할 필요가 있습니다. 첫 번째는, "어디서나, 어떤 코드에 코드를 연결하는 방법"의 일반적인 문제를 해결하는 것이며, 둘째로는 사람들이 이해하고 쉽게 사용할 수 있도록 간단하고 가능한 빌딩 블록으로 포장하는 것입니다.

이것은 말도 안되게 간단한 소리이지만, 이 글의 요점 입니다.

ØMQ in a Hundred Words

top prev next

ØMQ (ZeroMQ, 0MQ, zmq)는 임베디드 네트워킹 라이브러리 이지만, 동시성 프레임 워크와 같은 역할을 합니다.
이것은 in-process, inter-process, TCP, and multicast 처럼 다양한 방식으로 메시지를 전송하는 소켓을 제공합니다. 당신은 fanout, pub-sub, task distribution, and request-reply와 같은 패턴으로 N-to-N 소켓을 연결할 수 있습니다. 이것은 클러스터 구조에서 충분한 속도를 제공합니다. 비동기 I/O 모델은 비동기 메시지 처리를 제공하는 확장 멀티 코어 애플리케이션을 제공합니다. 이것은 language API를 제공하며 대부분의 운영 체제에서 실행됩니다. ØMQ는 iMatix에서 만들어 졌으며, LGPL 오픈소스 소프트웨어입니다.

Some Assumptions

top prev next

우리는 당신이 ØMQ의 최신 안정된 버전을 사용하고 있으며, 리눅스 또는 유사한 무언가를 사용한다고 가정합니다. 우리는 당신이 예제의 기본 언어인 C 코드를 알고 있다고 가정합니다. 우리가 PUSH, SUBSCRIBE와 같은 상수를 사용할 때 실제적으로는 ZMQ_PUSH , ZMQ_SUBSCRIBE가 사용 될 것이라고 이해 할 수 있다고 가정합니다.

Getting the Examples

top prev next

가이드 예제는 가이드의 git repository에 있습니다. 모든 예제를 얻을 수 있는 가장 간단한 방법은 이 저장소에서 복사하는 것입니다 :

git clone git://github.com/imatix/zguide.git

그리고 예제는 하위 디렉토리를 탐색하는 것입니다.
당신은 당신의 언어로 예제를 확인할 수 있습니다. 만일 당신이 사용하는 언어로 누락된 예제가 있다면, 당신은 번역물을 제출해 주시기 바랍니다.
이것은 가이드를 매우 유용하게 하는 방법이며, 많은 사람의 작품 덕분입니다.

소스 코드에 명시되어 있지 않더라도 모든 예제는 MIT/X11에 라이선스가 있습니다.

Ask and Ye Shall Receive

top prev next

몇 가지 코드로 시작하겠습니다. ‘Hello World’로 시작해 봅시다. 우리는 클라이언트와 서버를 만들 것이며, 클라이언트는 서버에 "Hello"를 보내면 ‘World’로 응답을 받습니다. 서버는 ‘C’로 되어 있고 포트 5555로 ØMQ 소켓을 열고 각 요청을 읽고 각각 요청에 대해 "World"로 응답합니다. :

//
// Hello World server
// Binds REP socket to tcp://*:5555
// Expects "Hello" from client, replies with "World"
//

#include <zmq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main (void)
{
void *context = zmq_init (1);

// Socket to talk to clients
void *responder = zmq_socket (context, ZMQ_REP);
zmq_bind (responder, "tcp://*:5555");

while (1) {
// Wait for next request from client
zmq_msg_t request;
zmq_msg_init (&request);
zmq_recv (responder, &request, 0);
printf ("Received Hello\n");
zmq_msg_close (&request);

// Do some 'work'
sleep (1);

// Send reply back to client
zmq_msg_t reply;
zmq_msg_init_size (&reply, 5);
memcpy (zmq_msg_data (&reply), "World", 5);
zmq_send (responder, &reply, 0);
zmq_msg_close (&reply);
}
// We never get here but if we did, this would be how we end
zmq_close (responder);
zmq_term (context);
return 0;
}

hwserver.c: Hello World server

fig1.png

REQ-REP는 소켓 쌍으로 개발한다. 클라이언트는 zmq_send(3) 다음에 zmq_recv(3)을 수행해야지 순서를 바꾸면 에러가 납니다. 유사하게 서버는 zmq_recv(3) 다음에 zmq_send(3) 순서로 합니다.

ØMQ는 이 글에서 C언어를 사용하며 이것은 우리가 예제에 사용할 기본 언어입니다. 당신이 온라인에서 읽는다면, 예를 들어 아래의 링크는 다른 프로그래밍 언어로 번역된 곳으로 이동합니다. C++로 된 같은 서버와 비교해 보세요.

//
// Hello World server in C++
// Binds REP socket to tcp://*:5555
// Expects "Hello" from client, replies with "World"
//

#include <zmq.hpp>
#include <string>
#include <iostream>
#include <unistd.h>

int main () {
// Prepare our context and socket
zmq::context_t context (1);
zmq::socket_t socket (context, ZMQ_REP);
socket.bind ("tcp://*:5555");

while (true) {
zmq::message_t request;

// Wait for next request from client
socket.recv (&request);
std::cout << "Received Hello" << std::endl;

// Do some 'work'
sleep (1);

// Send reply back to client
zmq::message_t reply (5);
memcpy ((void *) reply.data (), "World", 5);
socket.send (reply);
}
return 0;
}

hwserver.cpp: Hello World server

ØMQ API는 C와 C++이 유사함을 알 수 있습니다. PHP경우에는 좀더 간결하고 코드 읽기가 쉽습니다. :

<?php
/*
* Hello World server
* Binds REP socket to tcp://*:5555
* Expects "Hello" from client, replies with "World"
* @author Ian Barber <ian(dot)barber(at)gmail(dot)com>
*/

$context = new ZMQContext(1);

// Socket to talk to clients
$responder = new ZMQSocket($context, ZMQ::SOCKET_REP);
$responder->bind("tcp://*:5555");

while(true) {
// Wait for next request from client
$request = $responder->recv();
printf ("Received request: [%s]\n", $request);

// Do some 'work'
sleep (1);

// Send reply back to client
$responder->send("World");
}

hwserver.php: Hello World server

여기에 클라이언트 코드가 있습니다. (좋아하는 프로그래밍 언어로 번역에 기여하기 위해 혹은 소스 코드를 보기 위해 아래 링크를 클릭하세요.) :


C++ | C# | Clojure | CL | Erlang | F# | Go | Haskell | Java | Lua | Node.js | Objective-C | Perl | PHP | Python | Ruby | Scala | Ada | Basic | Haxe | ooc
지금 이 현실이 너무 간단해 보이지만, ØMQ socket은 많은 기능을 담고 있습니다.
ØMQ 소켓은 네트워크시대의 세계를 구원할 슈퍼 영웅 입니다.
fig2.png

당신은 말 그대로 한번에, 한 서버에서 수천개의 클라이언트에게 메시지를 보낼 수도 있으며, 행복하고 신속하게 작업을 계속 할 수 있습니다. 재미로, 클라이언트를 시작하고 서버를 시작하면, 그 모든 것이 어떻게 작동되는지를 볼 수 있습니다. 이것이 무엇을 의미하는지 잠시 생각해 봅시다.

간략하게 이 두 프로그램이 실제로 어떻게 작동하는지 설명해 보겠습니다. 먼저 처리할 ØMQ context와 소켓을 만듭니다. 무슨 말인지 몰라도 걱정하지 마십시오. 곧 알게 될 것입니다. 서버는 5555번 응답 소켓을 열고, 루프 안에서 요청을 기다립니다. 그리고 매번 응답처리 합니다. 클라이언트는 요청을 보내고 서버로부터 보내온 회신을 읽습니다.

프로그래머에게 관심이 되는 코드가 얼마나 짧고 쉬운지, 얼마나 자주 충돌이 발생하는지, 때로는 무거운 짐이 될 수 있다는 것을 제외하고는 내부적으로 많은 일이 발생합니다. 이것은 request-reply 패턴이며 ØMQ를 사용하는 가장 간단한 방법입니다. 이것은 RFP나 고전적인 client-server모델에 해당됩니다.

A Minor Note on Strings

top prev next

ØMQ은 전송하려는 데이터의 바이트 크기를 제외하고 관여하지 않습니다. 이것은 어플리케이션이 그것을 다시 읽을 수 있도록 안전하게 포맷팅 해야 할 책임은 여러분에게 있다는 것을 의미합니다. 객체와 복잡한 데이터 유형을 위한 것은 프로토콜 버퍼와 같은 전문 라이브러리 작업에 해당됩니다. 그래서 문자열에 대해서 신경을 써야 합니다.

C와 다른 언어에서 문자열은 NULL byte로 종료됩니다. 우리는 "HELLO"와 추가 NULL byte를 같이 문자열로 보낼 수 있습니다.:

zmq_msg_init_data (&request, "Hello", 6, NULL, NULL);

당신이 다른 언어에서 문자열을 보낼 경우, 아마도 그 NULL 바이트를 포함하지 않을 것입니다. 예를 들어, 우리는 Python에서 동일한 문자열을 보낼 때, 아래와 같이 합니다. :

socket.send ("Hello")

이것은 아래와 같이 표현이 됩니다. :

fig3.png

그리고 이것을 C 프로그램에서 읽으면, 문자열 같이 보이는 무엇인가를 얻게 됩니다. 이것은 적절한 문자열이 아니면 문제가 발생될 수 있습니다. (만약 5bytes다음에 NULL이 따라온 다면 다행입니다.). 이것은 클라이언트와 서버가 문자열 형식이 일치하지 않으면 이상한 결과를 얻을 수 있다는 것을 의미 합니다.

ØMQ에서 문자열 데이터를 수신할 때, C에서는 문자열이 안전하게 종료되었는지 신임할 수 없습니다. 문자열은 읽을 때마다 매번 여분의 byte를 위한 충분한 새로운 버퍼를 할당하고, 문자열을 복사하고, 적당하게 종료문자 NULL을 넣어야 합니다.

그러면, ØMQ 문자열을 길이와 종료문자 NULL없이 보내봅시다. 가장 간단한 경우에는 (예제에서 이것을 해 볼 것입니다.) ØMQ 문자열은 위의 그림에서 보이는 것처럼 길이와 문자열로 된 ØMQ 메시지 프레임으로 되어 있습니다.

C언어에서는 ØMQ 문자열을 받고, 어플리케이션이 가용한 C 문자열을 받기 위해서는 아래와 같이 할 필요가 있습니다.:

// Receive 0MQ string from socket and convert into C string
static char *
s_recv (void *socket) {
zmq_msg_t message;
zmq_msg_init (&message);
zmq_recv (socket, &message, 0);
int size = zmq_msg_size (&message);
char *string = malloc (size + 1);
memcpy (string, zmq_msg_data (&message), size);
zmq_msg_close (&message);
string [size] = 0;
return (string);
}

이것으로 함수를 만들면 좋습니다. 올바른 ØMQ 포맷 문자열을 보내는 ‘s_send’와 유사한 이름으로 함수를 만들고 재사용할 수 있도록 헤더파일을 만들므로 재사용 할 수 있습니다.

C언어로 ØMQ 어플리케이션을 만드는 것이 좀더 쉽고 짧게 되는 것이 zhelpers.h 때문입니다.
이것은 상당히 긴 소스라서 개발자들은 여유를 가지고 재미있게 읽기를 바랍니다.

Version Reporting

top prev next

ØMQ는 꽤 자주 여러 번의 버전을 거쳐왔으며, 만약 문제가 발생되면 이후 버전에서 해결되었습니다. 아래 ØMQ의 버전을 알 수 있는 짧은 프로그램이 있습니다.:


C++ | C# | CL | Erlang | F# | Java | Lua | Objective-C | PHP | Python | Ruby | Ada | Basic | Clojure | Go | Haskell | Haxe | Node.js | ooc | Perl | Scala

Getting the Message Out

top prev next

두 번째 고전적인 패턴은 서버가 클라이언트들에게 정보를 PUSH하는 단방향 데이터 분산 입니다. 우편 번호, 온도 및 상대 습도의 날씨 정보를 업데이트하는 것이 아래 예제입니다. 실제 날씨처럼random값을 생성할 것입니다.

이것이 서버이며, 포트 5556를 사용합니다.:


C++ | C# | Clojure | CL | Erlang | F# | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C | Perl | PHP | Python | Ruby | Scala | Ada | Basic | ooc

업데이트 정보의 시작과 끝이 없습니다. 이것은 끝이 없는 broadcast 와 같습니다.

fig4.png

여기에서 클라이언트 프로그램은 원하는 zipcode(어떤 모험을 시작하기에 좋은 장소이기 때문에 기본 값은 New York City로 하자)에 대한 정보를 가져옵니다.:


C++ | C# | Clojure | CL | Erlang | F# | Go | Haskell | Haxe | Java | Lua | Node.js | Objective-C | Perl | PHP | Python | Ruby | Scala | Ada | Basic | ooc

SUB 소켓을 사용하는 경우, 반드시 zmq_setsockopt(3)를 사용하여 subscription을 설정해야 합니다. 만약 subscription을 설정하지 않으면 어떤 메시지도 받을 수 없습니다. 이것은 초보자들이 많이 실수하는 것입니다. Subscriber는 많은 subscription을 설정할 수 있습니다. 즉, 어떤 subscription에 매칭이 되면 subscriber는 메시지를 수신합니다. Subcriber는 특정 subscription을 수신하지 않을 수 있습니다. Subscription은 length-specified blobs입니다. 상세한 내용은 zmq_setsockopt(3)을 참조하십시오.

PUB?SUB 소켓 한 쌍은 비동기입니다. 클라이언트는 루프 (또는 한번)에서, zmq_recv(3)를 사용합니다. SUB 소켓에서 메시지를 보내려고 하면 오류가 발생합니다. 마찬가지로 서버는 필요한 만큼 zmq_send(3)을 사용하며 PUB 소켓에서는 zmq_recv(3)을 사용하면 안 됩니다.

ØMQ에서는 이론상으로, 이것을 어느곳에 연결(connect)하든, 어느곳에 바인드(bind)하든 문제가 되지 않습니다. 그러나 만약 당신이 PUB-SUB소켓에서 SUB소켓에 바인드하고 PUB소켓에 연결을 한다면, SUB소켓은 오래된 메시지를 받을 수 있습니다. 즉, SUB이 시작되기 전에 메시지를 보낸 것입니다. 이것은 바인딩하고 연결하는 한개의 아티팩트 입니다. 그러나 가능하면 PUB은 바인드(bind)를하고 SUB은 연결(connect)를하는 것이 가장 좋습니다.

PUB-SUB 소켓에서 알아야 될 중요한 한 가지가 있습니다 :
Subscriber는 메시지를 가져오기 시작하는 시간을 정확히 모릅니다. 심지어 subscriber가 시작되어 기다리고 있고, publisher가 작동하고 있어도 그렇습니다. Subscriber는 publisher가 보낸 첫 번째 메시지를 항상 잃을 수도 있습니다. 왜냐하면 subscriber는 publisher(접속시간은 짧지만, 없지는 않다.)에 접속을 해야 하며, 그 동안 publisher가 이미 메시지를 보냈을 수도 있기 때문입니다.

"slow joiner"현상은 상세히 이것을 설명하기에 충분합니다. ØMQ는 백그라운드로 비동기 처리한다는 것을 기억하시기 바랍니다. 이 순서로 이렇게 처리하는 두 노드를 가집니다.:

  • Subscriber는 endpoint에 연결 후 수신하고 메시지를 셉니다.
  • Publisher는 endpoint에 바인딩한 후 즉시 1000개의 메시지를 보냅니다.

그러면 subscriber는 대부분 아무것도 받을 수 없습니다. 당신은 왜 그런지 모른체 올바른 필터를 설정했는지 확인하고 다시 시도해도 아무것도 받을 수 없습니다.

TCP에 연결하기 위해서는 네트워크나 Peers사이의 hop수에 따라 몇 milliseconds가 걸리는 핸드쉐이킹을 합니다. 그 때 ØMQ는 매우 많은 메시지를 보낼 수 있습니다. 예를들면, 연결하기 위해 5msecs가 소요되고, 그 연결로 초당 1M 메시지를 처리할 수 있다고 가정해 봅시다. Subscriber가 publisher에 연결하는 5 msecs 동안, publisher는 1K 메시지를 보내는데 단 1msec가 걸립니다.

2장에서는 subscriber가 연결하고 준비되기까지 데이터를 발송하지 않도록 publisher와 subscriber를 동기화 하는 방법에 대해서 설명할 것입니다. Publisher를 sleep하여 대기하는 것은 간단하지만 옳지 못한 방법이기에 실제 응용 프로그램은 이렇게 작업을 수행하지 않을 것입니다. 그것은 너무 세련되지 못하고 느립니다. 무슨 일이 발생할지 sleep를 사용해 볼 수 있지만, 어떤 방법이 있는지 2장에서 보도록 합시다.

동기화 대안은 단순히 게시된 데이터 스트림이 무한이며 시작도없고 끝도 없다고 가정하는 것입니다. 이것은 우리가 기상 클라이언트 예제를 만드는 방법에서 다룰 것입니다.

클라이언트는 선택 되어진 우편코드만 subscribe하고 천 개의 최신 우편번호를 수집합니다. 이것은 우편번호를 무작위로 배포할 수 있는 서버로부터 약 10만개 최신정보를 받았다는 것을 의미합니다. 클라이언트가 시작되고 그 다음 서버가 시작된다면 클라이언트는 기다릴 것입니다. 서버는 때로는 여러 번 재 구동 하지만 클라이언트는 기다릴 것입니다. 클라이언트는 천 개의 최신정보를 수집했을 때 평균을 계산해 출력하고 종료됩니다.

Publish-subscribe패턴의 몇 가지 특징 :

  • Subscriber는 사실 ‘connect’를 매번 호출을 하는 방식으로 한 개 이상의 publisher에 연결할 수 있다. 메시지는 번갈아 가면서 각 publisher로부터 도착될 것입니다.
  • Subscriber가 없다면 모든 Publisher의 메시지는 유실됩니다.
  • TCP를 사용하고, subscriber가 느리다면 메시지는 publisher의 큐에 쌓일 것이다. 나중에 "high-water mark"를 사용하면서 publisher를 보호하는 방법에 대해 살펴보겠습니다.
  • ØMQ의 현재 버전에서 필터링은 subscriber쪽에서 하며 publisher쪽에서는 하지 않습니다. 이것은 TCP상에서 publisher는 모든 메시지를 모든subscriber에게 보내지만, subscriber는 원하는 메시지만 받습니다.

이것은 인텔 4 코어 Q8300에서 10MB 메시지를 필터하여 받는데 얼마나 걸리는지 보여주고 있으며, 빠르지만 특별한 것은 없습니다. :

ph@ws200901:~/work/git/ØMQGuide/examples/c$ time wuclient
Collecting updates from weather server...
Average temperature for zipcode '10001 ' was 18F

real    0m5.939s
user    0m1.590s
sys     0m2.290s

Divide and Conquer

top prev next

마지막 예제에서는 (여러분이 확실히 juicy code의 피곤과 비교 추상 표준에 대한 언어학적 논의로 다시 탐구하기를 원합니다) 작은 슈퍼컴퓨팅을 해봅시다. 그런 다음 커피한잔 합시다. 우리의 슈퍼 컴퓨팅 응용 프로그램은 상당히 전형적인 병렬처리 모델입니다 :

  • 우리는 생산공정을 병렬로 처리할 수 있는 ventilator 를 가지고 있습니다.
  • 우리는 프로세스 공정 수행 Workers를 가지고 있습니다.
  • 우리는 worker 프로세스에서 다시 결과를 수집하는 Sink가 있습니다.

사실, Workers는 어려운 수학을 연산하는GPU(그래픽 처리 장치)를 사용하는 superfast 상자를 실행합니다. 여기 ventilator가 있습니다. 이것은 100타스크를 생성하며 각각은 수 milliseconds동안 sleep을 하는 worker에 전달하는 메시지 입니다. :


C++ | C# | Clojure | CL | Erlang | F# | Haskell | Haxe | Java | Lua | Node.js | Objective-C | Perl | PHP | Python | Ruby | Scala | Ada | Basic | Go | ooc
fig5.png

여기 worker 응용 프로그램입니다. 그것은 메시지를 받고 몇 초 동안 sleep을 한 다음 완료 신호를 받습니다. :


C++ | C# | Clojure | CL | Erlang | F# | Haskell | Haxe | Java | Lua | Node.js | Objective-C | Perl | PHP | Python | Ruby | Scala | Ada | Basic | Go | ooc

여기 sink어플리케이션이 있습니다. 이것은 100개의 작업을 수집한 후 전체 처리시간이 얼마인지 계산을 합니다. 그래서 worker가 여러 개 라면 실제 병렬로 처리되는 것을 확인할 수 있습니다 :


C++ | C# | Clojure | CL | Erlang | F# | Haskell | Haxe | Java | Lua | Node.js | Objective-C | Perl | PHP | Python | Ruby | Scala | Ada | Basic | Go | ooc

배치의 평균 소요시간은 5 초입니다. 우리는1, 2, 4 작업자가 시작할 때 sink로부터 아래와 같은 결과를 얻을 수 있습니다. :

#   1 worker
Total elapsed time: 5034 msec
#   2 workers
Total elapsed time: 2421 msec
#   4 workers
Total elapsed time: 1018 msec

좀더 자세하게 코드의 몇 가지 측면을 살펴보겠습니다. :

  • Worker는 ventilator에 위로 연결되어 있고, sink와는 아래로 연결되어 있습니다. 이것은 worker를 임의로 추가 할 수 있다는 것을 의미 합니다. worker가 그것들의 종점에 바인딩되어 있다면 worker가 추가할 때마다 매번 ventilator와 sink가 변경하기 위해서 더 많은 종점이 필요하게 됩니다. 이 구조에서 ventilator와 sink는 stable part이며 작업자는 dynamic part라고 부릅니다.
  • 모든 worker는 시작을 동기화하여 실행되어야 합니다. 이것은 ØMQ에서는 일반적인 것이지만 쉬운 솔루션은 아닙니다. 연결하는 데는 특정한 시간이 걸립니다. 그래서 worker들이 ventilator에 접속할 때 처음 연결에 성공한 작업자는 다른 작업자가 연결하는 짧은 시간 동안 전체메시지를 받게 됩니다. 어떻게든 시작을 동기화하지 않으면 시스템은 병렬로 실행되지 않습니다. 기다림을 제거하는 것을 해 봅시다.
  • ventilator의 PUSH 소켓은 균등하게 근로자 (시작하기 전에 작업자가 모두 연결되어 있다고 가정한다.)에 작업을 분배합니다. 이것은 load-balancing 이라고 하며, 이것이 무엇인지 자세히 다시 보게 될 것입니다.
  • Sink의 PULL소켓은 균등하게 노동자로부터 결과를 수집합니다. 이것은 fair-queuing이라고 합니다 :
fig6.png

pipeline패턴은 또한 PUSH소켓이 적당하게 load-balancing되지 않는 ‘slow joiner’가 발생됩니다. 당신이 PUSH와 PULL을 사용한다면 작업자중에 한명은 다른 작업자보다 더 많은 메시지를 얻게 될 것입니다. 이것은 그 PULL소켓이 다른 것보다 빨리 연결되어 다른 것들이 연결을 하는 동안 더 많은 메시지를 받기 때문입니다.

Programming with ØMQ

top prev next

몇 가지 예제를 보겠습니다. 당신은 몇몇 어플리케이션에서 ØMQ를 사용하길 원할 것입니다. 그전에 심호흡과 진정을 하고 스트레스와 혼돈을 피하기 위해 몇 가지 기본적인 조언을 하겠습니다.

  • 단계별로 ØMQ를 배워가십시요. 이것은 간단한 API지만 많은 가능성들이 숨겨져 있습니다. 천천히 가능한 것을 배워가면서 각각 하나씩 마스터 하시길 바랍니다.
  • 좋은 코드를 작성하세요. 보기 좋지 않은 코드는 문제를 숨기고 다른 사람의 도움도 어렵게 만듭니다. 의미 없는 변수는 사용해도 되지만 사람들이 당신의 코드를 읽는 것을 어렵게 합니다. 이 변수가 실제 무슨 역할을 하는지 너무 조심성 있게 정하기 보다는 무슨 의미인지 실제 단어를 사용하여 명명 하세요. 일관된 들여쓰기와 깨끗한 레이아웃을 사용하세요. 좋은 코드를 작성하는 것은 당신을 좀더 편안하게 할 것입니다.
  • 당신이 만든 코드를 테스트하세요. 프로그램이 동작하지 않을 때 5라인이 문제인 것을 알 것입니다. 이것은 당신이 코딩한 처음은 동작하지 않는다는 ØMQ 마법이 사실이라는 것을 증명합니다.
  • 기대한 대로 동작하지 않을 때 코딩을 멈추고 문제가 있는 부분을 테스트 하세요. ØMQ는 본질적으로 모듈화 코드를 만들 수 있도록 되어 있어서 자신에게 유리하게 사용할 수 있습니다.
  • 적절하게 클래스, 메소드 등을 만드세요. 만약 너무 많은 코드를 복사/붙여 넣기 하면 복사/붙여 넣기에서 에러가 발생할 수 있습니다.

예를 들어 이것은 누군가 나에게 교정을 요청했던 코드의 일부입니다. :

//  NOTE: do NOT reuse this example code!
static char *topic_str = "msg.x|";

void* pub_worker(void* arg){
    void *ctx = arg;
    assert(ctx);

    void *qskt = zmq_socket(ctx, ZMQ_REP);
    assert(qskt);

    int rc = zmq_connect(qskt, "inproc://querys");
    assert(rc == 0);

    void *pubskt = zmq_socket(ctx, ZMQ_PUB);
    assert(pubskt);

    rc = zmq_bind(pubskt, "inproc://publish");
    assert(rc == 0);

    uint8_t cmd;
    uint32_t nb;
    zmq_msg_t topic_msg, cmd_msg, nb_msg, resp_msg;

    zmq_msg_init_data(&topic_msg, topic_str, strlen(topic_str) , NULL, NULL);

    fprintf(stdout,"WORKER: ready to receive messages\n");
    //  NOTE: do NOT reuse this example code, It's broken.
    //  e.g. topic_msg will be invalid the second time through
    while (1){
    zmq_send(pubskt, &topic_msg, ZMQ_SNDMORE);

    zmq_msg_init(&cmd_msg);
    zmq_recv(qskt, &cmd_msg, 0);
    memcpy(&cmd, zmq_msg_data(&cmd_msg), sizeof(uint8_t));
    zmq_send(pubskt, &cmd_msg, ZMQ_SNDMORE);
    zmq_msg_close(&cmd_msg);

    fprintf(stdout, "received cmd %u\n", cmd);

    zmq_msg_init(&nb_msg);
    zmq_recv(qskt, &nb_msg, 0);
    memcpy(&nb, zmq_msg_data(&nb_msg), sizeof(uint32_t));
    zmq_send(pubskt, &nb_msg, 0);
    zmq_msg_close(&nb_msg);

    fprintf(stdout, "received nb %u\n", nb);

    zmq_msg_init_size(&resp_msg, sizeof(uint8_t));
    memset(zmq_msg_data(&resp_msg), 0, sizeof(uint8_t));
    zmq_send(qskt, &resp_msg, 0);
    zmq_msg_close(&resp_msg);

    }
    return NULL;
}

이것은 제가 버그를 찾은 부분으로 재 작성 한 것입니다. :

static void *
worker_thread (void *arg) {
void *context = arg;
void *worker = zmq_socket (context, ZMQ_REP);
assert (worker);
int rc;
rc = zmq_connect (worker, "ipc://worker");
assert (rc == 0);

void *broadcast = zmq_socket (context, ZMQ_PUB);
assert (broadcast);
rc = zmq_bind (broadcast, "ipc://publish");
assert (rc == 0);

while (1) {
char *part1 = s_recv (worker);
char *part2 = s_recv (worker);
printf ("Worker got [%s][%s]\n", part1, part2);
s_sendmore (broadcast, "msg");
s_sendmore (broadcast, part1);
s_send (broadcast, part2);
free (part1);
free (part2);

s_send (worker, "OK");
}
return NULL;
}

결국, 문제는 어플리케이션이 기묘하게 충돌하는 스레드 사이에 소켓을 사용 했기에 비롯되었습니다. 이것은 ØMQ/2.1에서 수정되었지만, 위험하기에 다시 그렇게 하지 않도록 충고하는 것입니다.

ØMQ/2.1

top prev next

MQ/2.0시작은 low-latency분산 메시징의 어려움을 겪다가, buzzworlds 및 기업용어의 무거운 코트를 벗어 마구 흔들고, 마치 아무런 제한이 없다는 듯이 최고조에 도달했을 때입니다. 우리는 2010년 8월의 뜨거운 여름날 ØMQ/2.0.8을 양산한 이후에 안정적인 버전으로 사용하게 되었습니다

그러나 시대가 변화되어 2010년에 멋진 것들이 2011년에는 더 이상 유행하지 않았습니다. ØMQ 개발자 및 커뮤니티는 세련된 메시징으로 바꾸기 위해서 미친듯이 작업해서, 새로운 안정적인 버전 2.1이 되었습니다.

아래 가이드는 2.1.x버전과 기존 2.0의 차이를 나열한 것입니다. :

  • 2.0에서는 zmq_close (3)zmq_term (3)에서 전송중인 메시지가 삭제되었습니다.. 그래서 소켓을 close하고, 메시지를 전송한 후 곧바로 종료시키는 것은 안정적이지 않았습니다. 2.1에서는 이 API는 safe합니다:zmq_term은 보내려고 기다리고 있는 것을 flush합니다. 2.0에서는 이문제로 해결하려고 sleep(1)을 추가했지만, 2.1에서는 이것이 필요하지 않습니다.
  • 반대로, 2.0에서 만약 소켓이 오픈되어 있다면 zmq_term(3) 을 호출하는 것이 safe합니다. 2.1에서 이것은 safe하지 않으며 이것은 zmq_term이 블락킹 되는 원인이 될 수 있습니다. 그래서 2.1에서는 종료하기 전에 모든 소켓을 닫아야 합니다. 보낼 메시지가 있거나 소켓이 연결을 대기하고 있다면 기본적으로 2.1에서는 이것을 전달하기 위해 영원히 노력하고 기다릴 것입니다. 당신은 zmq_term을 호출하기 전에 아직 활동중인 모든 소켓에 ‘LINGER’ 소켓옵션을 설정해야 합니다. :

int zero = 0;
zmq_setsockopt (mysocket, ZMQ_LINGER, &zero, sizeof (zero));

  • 2.0에서 zmq_poll(3)은 불 특정하게 반환이 될 것입니다. 그래서 당신은 타이머로써 이것을 사용할 수 없습니다. 2.1에서 zmq_poll은 이벤트가 있을 때까지 기다립니다.

*2.0에서, ØMQ는 중단(interrupted) 시스템 호출을 무시 합니다. 동작 중에 이런 신호를 받으면 EINTR을 반환 하지 않습니다. 이것은 특히 런타임에 SIGINT (Ctrl - C를 처리)와 같은 신호를 손실하는 문제가 발생됩니다. 2.1에서는, 중단신호가 발생되면 zmq_recv(3)와 같은 차단 호출 EINTR 반환합니다.

Getting the Context Right

top prev next

ØMQ 응용 프로그램은 항상 context를 만드는 것에서 시작하고, 다음은 소켓을 생성하기 위해서 사용합니다. C에서 이것을 위해 zmq_init(3)을 호출합니다. 당신은 당신의 프로세스에서 정확하게 한 개의 context를 생성하고 사용해야 합니다. 기술적으로, ,context는 단일 프로세스에서 모든 소켓을 위한 container이며, inproc 소켓(한 프로세스에서 스레드들을 연결하기 위한 가장 빠른 방법)을 통해 전송됩니다. 만약 런타임에 한 프로세스가 2개의 context를 가지고 있다면 이것은 구분된 ØMQ instances일 것입니다. 이것이 당신이 원하는 것이라면 상관없지만, 여하튼 이것만은 기억하세요. :

당신의 메인 코드의 시작에는zmq_init(3) , 그리고 마지막에는 zmq_term(3)을 사용하세요.

만약 fork() 시스템 콜을 사용한다면, 각각의 프로세스는 자신의 context를 필요로 합니다. 만약 fork()를 호출하기 전에 메인 프로세스에서zmq_init(3)을 사용한다면, 자식 프로세스는 그들 자신의 context를 얻을 것입니다. 일반적으로 자식 프로세스는 중요한 일을 수행하고 부모 프로세스는 이것을 관리 합니다.

Making a Clean Exit

top prev next

품위 있는 프로그래머는 고상한 암살단과 같습니다 - 작업이 끝 났을 때 항상 깨끗이 정리합니다. 당신이 Python와 같은 언어로 ØMQ를 사용할 때 자동으로 객체를 free해 줍니다.. 그러나 C를 사용할 때는 작업이 끝났을 때 주의 깊게 객체를 free해야 합니다.

아니면 메모리 누수(leak)나 불안정한 어플리케이션, 나쁜 Karma에 빠질 수 있습니다. 메모리 누수(leak)은 한가지 이지만, ØMQ는 당신이 어플리케이션을 어떻게 종료하는지에 꼼꼼하게 신경을 씁니다. 그 이유는 만약 어떤 소켓을 열고 있다면 zmq_term(3) 함수는 영원히 끊기지 않으며, 심지어 모든 소켓을 닫았다고 해도, zmq_term(3) 은 연결되어 있거나 보내는 것이 있다면 영원히 기다릴 것입니다. 이들 소켓을 종료하기 전에 LINGER을 zero로 설정하지 않았어도 말입니다.

우리가 걱정해야 하는 ØMQ 개체는 메시지, 소켓, 그리고 context입니다. 다행히 그것은 적어도 간단한 프로그램에서는 매우 간단 합니다. :

  • 항상 zmq_msg_close(3)을 사용하여, 메시지를 사용한 후 즉시 닫습니다.
  • 많은 소켓을 열고 닫고 한다면 당신의 어플리케이션을 재설계 해야 합니다.
  • 프로그램을 종료할 때 소켓을 닫고 zmq_term(3) 을 호출하면 context는 사라집니다.

당신이 다중 스레드 작업을 하고 있다면 이것보다 좀더 복잡할 것입니다. 다음 장은 다중 스레딩에 대해서 다룰 것입니다. 경고에도 불구하고 다수의 사람들은 안정적으로 걷기 전에 달려가려고 할 것입니다. 아래 다중 스레드 ØMQ 어플리케이션에서 깔끔하게 종료하기 위한 빠르고 조잡한 가이드가 있기 때문입니다.

첫째, 멀티스레드에서 같은 소켓을 사용하지 말기 바랍니다. 매우 재미있을 것이라는 여러분의 생각을 얘기하지 마세요. 단지 그렇게 하지 말기 바랍니다. 둘째, relingerfy와 모든 소켓을 닫고 메인 스레드의 context를 종료하면 결국, 이것은 에러를 리턴하기 위한 스레드(즉, 같은 context를 공유하는 것)에서 receive/poll/send blocking의 원인이 됩니다. 이렇게 되면 relingerize 그리고, 그 스레드에서 소켓이 닫고 종료됩니다. 두번째 같이 context를 종료하지 마세요. 모든 소켓을 안전하게 닫혀질 때까지 메인 스레드에서 zmq_term은 기다릴 것입니다.

짜잔!, 이것은 복잡하고 고통스러운 일입니다. 그래서 언어를 개발한 개발자가 자동으로 이렇게 동작하도록 하고 즉시 소켓을 닫게 만들 것입니다.

Why We Needed ØMQ

top prev next

이제 ØMQ을 보았으니까, 왜 필요한지로 돌아갑니다.

요즘 대부분의 어플리케이션은 여러 종류의 네트워크, LAN이나 Internet을 통하는 컴포넌트로 구성합니다. 그래서 많은 어플리케이션 개발자들은 메시징을 처리하게 됩니다. TCP나 UDP를 사용할 시간이 없는 몇몇 개발자들은 메시지 큐잉 제품을 사용합니다. 이들 프로토콜은 사용하기 쉽습니다. 그러나 A에서 B로 문자열을 전송하는 것과 신뢰 할 수 있는 방식으로 메시징하는 것에는 큰 차이가 있습니다.

우리가 raw TCP를 사용하여 connection할 때 직면하게 되는 전형적인 문제를 살펴 보겠습니다. 재사용 가능한 메시징은 모두 또는 대부분 이를 해결해야 합니다. :

  • 우리는 I/O를 어떻게 처리합니까? 우리의 어플리케이션을 blocking합니까? 아니면 백그라운드에서 I/O를 처리합니까? 이것은 디자인 결정의 핵심입니다. I/O Blocking하는 것은 스케일이 좋지 않는 아키텍처가 됩니다. 그러나 I/O를 백그라운드로 처리하는 것은 잘 동작하게 하기 위해 매우 어려울 수 있습니다.
  • 일시적으로 사용하는 다이나믹 컴포넌트는 어떻게 다룹니까? 보통 ‘클라이언트’와 ‘서버’ 컴포넌트로 구분하며, 서버는 항상 실행되어 있습니다. 그 다음에 우리는 서버에서 서버를 연결하려면 어떻게 해야 합니까? 몇 초마다 연결하려고 합니까?
  • 어떻게 메시지를 표현 합니까? buffer overflow로 부터 안전하고 작은 메시지에 효과적이고 파티모자를 쓰고 춤을 추는 고양이가 나오는 큰 동영상에도 적합하게 쉽게 쓰고 읽을 수 있기 위해서 어떻게 데이터를 프레임 합니까?
  • 즉시 제공할 수 없는 메시지를 어떻게 처리합니까? 특히, 우리가 온라인으로 돌아올 컴포넌트를 위해 기다리고 있다면? 우리는 메시지를 버리고 그것들을 데이터베이스나 메모리 큐에 넣겠습니까?
  • 어디에 메시지 큐를 저장합니까? 큐로부터 읽고 있는 컴포넌트가 너무 느리고, 큐를 만드는데 발생되는 것이 무엇입니까? 그 다음 우리의 전략은 무엇입니까?
  • 어떻게 메시지 유실을 처리합니까? 다음 메시지를 기다립니까? 재전송을 요청합니까? 아니면 메시지를 잃어 버리지 않도록 보장하는 신뢰 가능한 레이어 같은 것을 만들어야 합니까? 만약 그 레이어 자체가 깨지면 어찌 됩니까?
  • 서로 다른 네트워크간의 전송이 필요할 때 어떻게 합니까? 이를테면 TCP unicast대신에 multicast나, IPv6경우 어플리케이션을 다시 만들어야 됩니까? 아니면 일부 레이어의 추상화된 전송부분을 수정해야 합니까?
  • 어떻게 메시지 경로를 줍니까? 같은 메시지를 여러 peers에 보낼 수 있습니까? 요청자에게 reply를 보낼 수 있습니까?
  • 우리가 다른 언어에 대한 API를 작성하려면 어떻게 해야 합니까? 우리는 다시 wire-level 프로토콜을 다시 구현하거나, 라이브러리를 다시 패키지 해야 합니까? 전자의 경우 어떻게 효율적이고 안정적인 stacks을 보장합니까? 후자의 경우 우리가 어떻게 상호 운용성을 보장할 수 있습니까?
  • 다른 아키텍쳐 사이에 메시지를 읽을 수 있도록 하기 위해 어떻게 데이터를 표시합니까? 우리는 데이터 유형에 대한 특정 인코딩을 시행합니까? 높은 레이어 작업보다 오히려 메시징 시스템의 작업이 얼마나 걸립니까?
  • 우리가 어떻게 네트워크 오류를 처리합니까? 우리가 기다리고 재시도하고, 자동으로 무시하거나, 중지합니까?

Hadoop Zookeeper와 같은 전형적인 오픈소스 프로젝트에서 얻은 C API 코드 src/c/src/zookeeper.c를 읽어 보면, client-server 네트워크 통신 프로토콜로 문서화되지 않은 신비로운 3200라인이 있습니다. 개인적으로 select()대신에 poll()을 사용하기 때문에 그것이 효과적이라고 생각하지만 사실, Zookeeper는 일반적인 메시징 레이어와 명시적으로 문서화된 wire레벨 프로토콜을 사용해야 합니다. 이것은 반복되는 특정 모듈를 만드는 팀에게는 매우 소모적인 작업입니다.

fig7.png

그러나 재사용 가능한 메시징 레이어는 어떻게 만들까요?. 그렇게 많은 프로젝트들이 이 기술을 필요로 할 때 여전히 그들의 코드에서 TCP 소켓을 사용하고, 반복적으로 오래된 목록에서 그 문제를 해결하는 어려운 방법으로 진행합니까?

이것은 재사용 가능한 메시징 시스템을 구축하는 것은 몇몇 FOSS프로젝트에서 시도를 해 봤던 적이 있지만 정말 어렵고, 왜 상용 메시징 제품은 복잡하고 비싸고, 유연성이 떨어지고, 불안전한지를 증명하는 것입니다. 2006년, iMatrix는 FOSS개발자들이 메시징 시스템에 아마도 처음으로 재사용성을 제공하도록 AMQP를 설계했습니다. AMQP는 많은 다른 디자인보다 잘 동작하지만 http://www.imatix.com/articles:whats-wrong-with-amqp 상대적으로 비싸고, 복잡하고, 불안전 합니다.] 이것은 사용법을 배우는 데는 수 주일이 걸리며, 여러 상황에서도 안정된 아키텍쳐로 만드는 데는 수개월이 걸립니다.

재사용 가능한 방법으로 문제되는 긴 목록을 해결하려고 AMQP와 같은 대부분의 메시징 프로젝트는 addressing, routing, queuing하는 “broker”라는 새로운 개념을 발명해 왔습니다. 이렇게, 어플리케이션이 broker와 통신할 수 있는 client-server 프로토콜 이나 몇몇 문서화되지 않은 프로토콜 API집합이 생겨났습니다. Broker는 대형 네트워크의 복잡성을 줄일 수 있는 훌륭한 일을 하고 있습니다. 그러나 Zookeeper와 같은 제품에 broker기반 메시징을 추가하는 것은 더 나빠지는 것이며, 그럴만한 장점도 가치도 없습니다. 이것은 단지 커지는 것이며, 한 결함이 더해질 수 있습니다. Broker는 빠르게 병목과 관리의 새로운 위험이 됩니다. 소프트웨어가 이것을 지원한다면 2,3,4 broker를 추가하고 몇 가지 fail-over 구조를 만들어야 될 수 있습니다. 이렇게 하게 되면 변경이 많아지고, 더 복잡하고, 위험요소들이 많이 생깁니다.

그리고 broker주심의 환경은 자체 운영팀이 필요합니다. 당신은 말 그대로 broker를 밤낮으로 감시해야 하고, 그것이 오동작할 때 조치를 해야 합니다. 그런 시스템이 필요하고, 백업 시스템이 필요합니다. 그리고 이들 시스템을 관리하는 사람이 필요합니다. 이것은 단지 몇 년 동안 여러 팀에 의해 만들어지는 거대한 어플리케이션을 위한 가치일 뿐입니다.

그래서 몇몇 중간 어플리케이션 개발자들은 네트워크 프로그램을 피하고 규모가 작은 단일 어플리케이션을 만듭니다. 아니면 그들은 네트워크 프로그램을 건너 뛰고, 불안전하고 복잡한 관리하기 어려운 어플리케이션을 만듭니다. 아니면 메시징 제품을 구입하여 비싸고 불안정한 기술로 뒤 덮인 확장 어플리케이션으로 끝나게 됩니다. 메시징은 지난 세기에 크게 정착하거나 마음을 움직일 만한 좋은 선택이 못 되어 왔습니다. 단지, 사용자에게는 부정적이고, 지원과 라이선스를 판매하는 사람들에게는 기쁨일 뿐이었습니다.

fig8.png

우리가 필요한 것은 제로 비용에 가깝게 어떤 어플리케이션에서도 동작할 수 있는 간단하고 큰 노력 없이 메시징 처리를 할 수 있는 것이다. 이것은 어떤 다른 종속성 없이 단지 참조하는 LIB이어야 됩니다. 추가 변동되는 부분이 없기 때문에 추가적인 위험도 없고, 이것은 어떤 프로그램 언어에서 동작하고 모든 OS에서 실행되어야 합니다.

바로 이것이 ØMQ입니다 : 많은 비용 없이, 네트워크 연결에 유연성이 필요한 어플리케이션의 대부분 문제를 풀 수 있는 효율적인 임베디드 라이브러리입니다.

특징 :

  • 이것은 백그라운드에서 비동기 I/O를 처리 합니다. 이것은 lock-free 데이터 구조를 사용하는 어플리케이션 스레드로 통신합니다. 그래서 ØMQ 응용 프로그램은 락(lock), 세마포(semaphores), 다른 대기상태가 필요하지 않습니다.
  • 컴포넌트들은 동적으로 오고,갈수도 있으며, ØMQ는 자동으로 재 연결이 됩니다. 이것은 어떤 순서로 구성 요소를 시작할 수있다는 것을 의미합니다. 이 서비스는 언제든지 네트워크에 참여하고 떠날 수있는 "서비스 지향 아키텍처를"(SOAs)를 만들 수 있습니다.
  • 이것은 필요 시 자동으로 메시지를 대기 시킵니다. 메시지가 대기하기 전에 지능적으로 수신자에 가능한 가깝게 메시지를 밀어 넣는 작업을 수행합니다.
  • 이것은 over-full 큐를 처리하는 방법을 제공합니다.(‘high water mark라고 함). 큐가 가득 차면, ØMQ는 당신이 하려고 하는 메시징의 성격에 따라(패턴[pattern]이라 함) 자동적으로 발신자를 차단하거나, 메시지를 버립니다.
  • 이것은 어플리케이션이 임의의 전송 레이어와 서로 대화하도록 허락합니다 : TCP, multicast, in-process, inter-process. 당신은 다른 전송 레이어를 사용하도록 코드를 변경할 필요가 없습니다.
  • 이것은 메시징 패턴에 따라 서로 다른 전략을 사용하여 안전하게 수신을 늦게 혹은 차단하도록 처리 합니다.
  • 이것은 request-reply, publish-subscribe와 같은 다양한 패턴을 사용하여 메시지 라우팅을 제공합니다. 이러한 패턴은 어떻게 당신이 토폴로지, 네트워크의 구조를 구성하느냐에 달려 있습니다.
  • 이것은 네트워크에서 많은 부분이 상호 연결하는 복잡도를 줄이기 위해 확장패턴을 위한 “devices”(small brokers)를 둘 수 있습니다.
  • 이것은 간단한 프레임을 사용하여 정확하게 전체 메시지를 전달합니다. 10k 메시지를 보내며 10k 메시지를 받을 것입니다.
  • 이것은 특정 메시지 형식이 없습니다. 이것은 0에서 gigabytes의 큰 blob입니다. 데이터를 표현하고 싶을 때 구글의 protocol buffer, XDR, 기타 다른 것과 같은 제품을 선택하면 됩니다.
  • 이것은 지능적으로 네트워크 오류를 처리합니다. 때로 재시도하고, 때로 실패로 처리 합니다.
  • 이것은 carbon footprint를 줄여 줍니다. 적은 CPU로 많은 일을 수행하면 적은 전력을 사용한다는 의미입니다. 그리고 더 오랫동안 노후 시스템을 유지할 수 있습니다. Al Gore는 ØMQ을 사랑합니다.

사실 ØMQ의 장점은 더 많이 있습니다. 이것은 당신이 네크워크 프로그램을 개발하는데 혁신적인 효과를 제공합니다. 표면적으로 이것은 단지 당신이 zmq_recv (3)zmq_send (3) API를 사용하는 것이지만, 메시지 처리는 빠르게 처리되어, 당신의 어플리케이션은 곧 모든 메시지를 처리하고 종료 될 것입니다. 이것은 우아하고 자연스러운 것입니다. 그리고 이들 각각의 작업은 node로 연결합니다. 이 node는 임의의 전송 레이어를 통해 서로 연결 됩니다.(코드 변경없이 node는 한 프로세스에 있는 두 스레드 일 수 있으며, 한 시스템에 있는 두 프로세스 일 수 있으며, 네트워크상에 있는 두 시스템 일 수 있습니다.)

Socket Scalability

top prev next

다음은 ØMQ의 확장성에 대한 얘기입니다. 여기 날씨 서버가 시작된 후, 병렬로 클라이언트가 분기되는 쉘 스크립트가 있습니다. :

wuserver &
wuclient 12345 &
wuclient 23456 &
wuclient 34567 &
wuclient 45678 &
wuclient 56789 &

클라이언트가 실행 된 후 ‘top’을 사용해서 실행중인 프로세스를 보세요. :

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 7136 ph        20   0 1040m 959m 1156 R  157 12.0  16:25.47 wuserver
 7966 ph        20   0 98608 1804 1372 S   33  0.0   0:03.94 wuclient
 7963 ph        20   0 33116 1748 1372 S   14  0.0   0:00.76 wuclient
 7965 ph        20   0 33116 1784 1372 S    6  0.0   0:00.47 wuclient
 7964 ph        20   0 33116 1788 1372 S    5  0.0   0:00.25 wuclient
 7967 ph        20   0 33072 1740 1372 S    5  0.0   0:00.35 wuclient

여기 무슨 일이 일어나고 있는지에 대해 잠시 생각해 봅시다. 날씨 서버는 하나의 소켓을 가지고 있으며, 병렬로 5개의 클라이언트에게 데이터를 보내려고 하고 있습니다. 우리는 수천의 동시 클라이언트가 있을 수 있습니다. 서버 어플리케이션은 클라이언트를 보거나 직접 얘기 할 수 없습니다.

Missing Message Problem Solver

top prev next

당신이 ØMQ로 프로그램을 시작할 때 한번 이상 이 한 문제에 직면할 것입니다. : 당신이 받을 것으로 기대한 메시지를 잃게 되는 것입니다. 이것은 가장 일반적인 원인을 통해 해결하는 기초적인 문제해결 방법입니다.. 전문용어 중 일부가 아직 익숙하지 않더라도 걱정하지 마세요, 그것은 다음 장에서 명확하게 알게 될 것입니다.

fig9.png

만약 오류 비용이 큰 환경에서 ØMQ를 사용하는 경우, 당신은 적당한 테스트 계획을 세우길 원할 것입니다. 첫째, 당신이 디자인한 서로 다른 측면을 테스트할 Prototype을 구축하고, 당신이 설계한 것이 얼마나 견고하고 정확한지 검증을 위해 죽을 때까지 부하를 줍니다. 둘째, 테스트에 투자합니다. 이것은 충분한 컴퓨터 자원으로 운영환경과 같은 테스트 환경을 만드는 것을 의미 하며, 시간을 점점 늘리거나 심각할 정도로 실제적인 테스트가 되도록 도움을 줍니다. 이상적으로 한 팀은 코드를 만들고, 두번째 팀은 에러가 나도록 노력합니다. 마지막으로 정말 제대로 작동할 수 있도록 도울 수 있는 방법을 논의하기 위해 iMatrix에 연락하는 것이며, 당신은 신속하게 문제를 해결 할 수 있을 것입니다.

즉, 실 환경에서 동작하는지 테스트하고 증명되지 않았다면, 최악의 가능한 순간에 문제가 발생 할 것입니다.

Warning - Unstable Paradigms!

top prev next

전통적인 네트워크 프로그래밍은 한 소켓이 한 connection으로 한 대상과 통신한다고 가정합니다. 좀 다르지만 멀티캐스트 프로토콜 이란 것도 있기는 합니다. 우리는 "한 소켓 = 하나의 연결"이라 가정했을 때 다른 방식으로 우리의 아키텍처를 확장할 수 있습니다. 여러 스레드가 한 소켓으로 처리하도록 하는 로직을 만들 수 있습니다. 우리는 이들의 스레드에 정보와 상태를 설정합니다.

ØMQ에서 소켓은 자동으로 연결 전체 집합을 관리하는 영리한 멀티스레드 어플리케이션입니다. 당신은 이들 연결이 작동되고, 열고, 닫고, 추가적인 상태를 알수 없습니다. 당신이 send/receive/poll을 차단하고 당신이 제어 할 수 있는 것이 소켓일지라도, 당신이 관리하는 것은 connection이 아닙니다. Connection은 개인적이고 보이지 않는 것이며, ØMQ 확장성의 핵심입니다.

당신은 코드 변경 없이 네트워크 프로토콜이 무엇이든지 간에 connection수를 제어할 수 있습니다. ØMQ의 메시지 패턴은 당신의 어플리케이션 메시지 패턴보다 더 싸게 스케일 할 수 있습니다.

그래서 일반적인 가정으로는 더 이상 적용되지 않습니다. 예제코드를 참고로 당신의 뇌는 알고 있는 것을 구상하여 그리려고 합니다. 당신은 “socket”을 “ah, that represents a connection to another node”로 생각합니다. 이것은 잘못된 것입니다. 당신이 “thread”를 봤을 때 당신의 뇌는 "ah, a thread represents a connection to another node"라고 다시 생각합니다. 이것 또한 잘못된 것입니다.

당신이 처음 이 가이드를 읽는다면, 특히 간단한 ØMQ 어플리케이션을 구상하고 하루 이틀(그리고 아마 3,4일)에 ØMQ코드를 작성 할 때까지는 혼란을 느낄 수도 있을 것이라는 것을 깨닫게 될 것입니다. 그리고, 당신이 ØMQ에 일반적인 가정을 둔다면 그것은 작동하지 않을 것입니다. 이 모든 것이 확실하게 될 때 zap-pow-kaboom satori paradigm-shift와 같이 당신은 순간 깨달음과 신뢰를 경험하게 될 것입니다.