Chapter1


Chapter 1 - Basics

topprevnext

Fixing the World

topprevnext

어떻게 ZeroMQ를 설명해야 할까? 우리 중의 몇몇은 ZeroMQ가 하는 모든 놀라운 것들을 말함으로써 이를 시작한다. ZeroMQ의 소켓은 스테로이드를 복용한 소켓이다. 라우팅을 하는 우편함과 같으며, 빠르다! 또 다른 이들은 ZeroMQ에 대해 직관적으로 번뜩하고는 기존과 다른 패러다임 전환을 깨우쳐서 모든 것이 명료해졌던 순간을 공유하려고 한다. 상황은 매우 간단해지고, 복잡함은 어느새 멀리 사라진다. 그리고 마음을 열게 된다. 또 어떤 이들은 비교를 통해 설명을 하려고 한다. 매우 작고 단순해 보이지만 친숙하게 보여진다. 지금와서도 나는 개인적으로 우리가 왜 ZeroMQ를 만들었는지에 대한 이유가 당신과 같이 이 글을 읽는 독자들로부터 나온 것들이라고 생각하기 때문에 가능한 그 것들을 모두 기억하고 싶다.

프로그래밍은 예술을 빙자한 과학이다. 우리 대부분이 소프트웨어 물리학에 대해 학습한 적이 있다고 하더라도 그 이해가 부족하기 때문이다. 소프트웨어 물리학은 알고리즘이나 자료 구조, 언어와 관념이 아니다. 이는 단지 우리가 만들고, 사용하고, 버리는 도구들일 뿐이다. 진정한 소프트웨어 물리학은 사람 간의 물리학이다.—특히, 복잡성에서 오는 제약 사항들을 마주했을 때 우리는 큰 문제들을 작은 부분으로 나누어 함께 해결하길 원한다. 사람들이 쉽게 이해하고 사용할 수 있는 빌딩 블록을 만들고 사람들은 거대한 문제들을 해결하기 위해 함께 일하는 것. 이것이 프로그래밍의 과학이다.

우리는 연결된 세상에 살고 있고, 현대 소프트웨어는 이 세상을 항해해야 한다. 그래서 오늘날의 매우 큰 솔루션들을 위한 빌딩 블록들은 연결되어 있고 대규모로 병렬화되어 있다. 이는 더 이상 "강하고 조용한" 코드라기에는 충분하지 않다. 코드는 코드로 통해야 한다. 코드는 수다스럽고, 사교적이고, 잘 연결되어야 한다. 코드는 사람의 두뇌와 같이 중앙 제어가 없는 병렬 네트워크에서 아무런 단일 결함 지점이 없으면서도, 크고 어려운 문제를 해결할 수 있는, 서로가 서로에게 메시지를 전달하는 수조 개의 개별적인 뉴런들과 갈이 움직여야 한다. 그리고 어떤 관점에서는 모든 네트워크의 종착점은 사람의 두뇌이기 때문에 미래의 코드가 이와 갈이 보여지는 것은 전혀 이상한 일이 아니다.

스레드나 프로토콜 혹은 네트워크 작업을 해본 적이 있다면, 이러한 것들이 아름다울 수 없다는 것을 깨닫게 될 것이다. 그건 그냥 꿈이다. 심지어 현실에서 몇몇 프로그램들을 소켓을 통해 연결해서 제어하는 일을 시작할 때조차도 이는 매우 괴로운 일이다. 수조? 그 비용은 상상조차 할 수도 없다. 컴퓨터들을 연결하는 것은 소프트웨어와 서비스들을 연결하는 것이고, 이는 수십억불의 사업이기 때문에 매우 어려운 일이다.

우리는 통신망이 우리가 이를 사용하는 능력보다 몇년은 앞서가 있는 세계에서 살고 있다. 우리에겐 Fred Brooks와 같이 "생산성, 신뢰성, 혹은 단순성 면에서의 한 승수만큼의 향상을 약속하는" "은탄환"은 없다고 생각했던 소프트웨어 엔지니어들이 이끄는 1980년대에 소프트웨어 위기가 있었다.

Brooks는 우리의 지식을 효율적으로 공유함으로써 이러한 위기를 풀어내었던 자유/오픈 소스 소프트웨어를 간과했다. 오늘날 우리는 또 다른 소프트웨어 위기에 직면했지만, 우리는 이에 대해 더 이상 이야기 하지 않는다. 그저 부유한 대기업들이나 이러한 연결된 애플리케이션들을 만들 여유가 있다. 클라우드가 있지만 이는 독점적이다. 우리의 자료와 지식은 우리의 개인 컴퓨터로부터 나타나지 않고 접근할 수도, 우리끼리 경쟁할 수도 없는 클라우드로 들어간다. 누가 우리가 사용하는 사회 연결망을 가지고 있을까? 시대 역설적이게도 그건 바로 메인프레임 컴퓨터다.

우리는 정치적 철학이 담긴 또 다른 책을 남길 수 있다. 핵심은 인터넷이 수많은 연결된 코드의 잠재력을 제공한다는 것이고, 현실은 그 대부분이 우리 손에 닿지 않으며, 이로 인해 코드를 연결할 수 있는 방법이 없고, 그렇게 매우 큰 문제들 (건강, 교육, 환경, 물류 그리고 기타 등등) 을 해결하기 위해 함께 일할 수 있는 두뇌들이 연결될 수 있는 방법이 없기 때문에 이러한 문제들이 해결되지 않는 채 남아있다는 점이다.

연결된 코드들의 도전을 해결하기 위한 많은 시도들이 있어왔다. 그 퍼즐의 각 부분들을 해결하는 수천의 IETF 명세들이 있었다. HTTP는 애플리케이션 개발자에게 있어 이를 위한 충분히 단순한 해결책일 수도 있다. 허나 단언컨데, 이러한 명세는 개발자들과 아키텍트들이 대형 서버와 가볍고 멍청한 클라이언트의 관점에서 생각하도록 장려하기 때문에 문제를 더 악화시킬 뿐이다.

오늘날 사람들은 여전히 raw UDP와 TCP, 독점 프로토콜, HTTP, 그리고 웹소켓을 사용하여 애플리케이션들을 연결하고 있다. 이는 고통스럽고, 느리고, 유연하기 어렵기에 본질적으로 중앙 집중화가 이루어진다. 분산 P2P 아키텍처는 대부분 놀이를 위한 것이지 일하기 위한 것이 아니다. 얼마나 많은 애플리케이션들이 데이터를 교환하기 위해 스카이프나 비트토렌트를 사용하는가?

우리를 프로그래밍의 과학으로 돌아가게 이끄는 것. 세계를 구원하기 위해서, 우리는 2가지가 필요하다. 첫째, "어떻게 모든 코드를 모든 코드에, 모든 곳에서 연결할 것인가"의 일반적인 문제를 해결하기 위한 것. 둘째, 가능한 한 가장 간단하게 사람들이 이해할 수 있고, 사용하기 쉬운 빌딩 블록들을 만드는 것이다.

터무니 없이 간단하게 들리겠지만 아마도 이 말이 핵심일 것이다.

Starting Assumptions

topprevnext

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

Getting the Examples

topprevnext

이 예제들은 공개 GitHub 저장소 에 있다. 모든 예제를 가져오는 가장 간단한 방법은 이 저장소를 복제하는 것이다.

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

다음으로, 이 예제들의 하위 디렉터리를 살펴보자. 당신은 언어별 예제들을 찾을 수 있을 것이다. 당신이 사용하는 언어로 된 예제들이 없다면, 변환된 예제를 제출하는 것을 권한다. 이것이 어떻게 많은 사람들의 작업 덕분에 이 텍스트가 유용하게 되었는가의 이유다. 모든 예제들은 MIT/X11 라이센스 하에 허가된다.

Ask and Ye Shall Receive

topprevnext

이제 몇 가지 코드와 함께 시작해보자. 우리는 Hello World 예제로 코스를 시작한다. 우리는 하나의 클라이언트와 하나의 서버를 만들 것이다. 이 클라이언트는 "Hello"를 서버로 보내고, "World"[figure]를 응답을 받는다. 서버는 C로 되었고, 5555번 포트에 대해 ZeroMQ 소켓을 열여서 요청을 읽은 후에 각 요청에 대해 "World"로 응답한다.


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

그림 2 - 요청-응답

fig2.png

REQ-REP 소켓 쌍은 서로가 발을 맞추어 가는 방식이다. 클라이언트는 한 반복문 내에서 zmq_send()을 호출한 뒤에 zmq_recv()를 호출한다 (혹은 필요하다면 한번만). 다른 시퀀스를 수행하는 것 (예를 들어, 한번에 두 개의 메시지를 보내는 것) 은 send 혹은 recv 호출로부터 -1의 반환 코드를 얻게될 것이다. 이와 유사하게, 서비스는 필요할 때마다 순서대로 zmq_recv(), zmq_send()를 호출한다.

ZeroMQ는 그 레퍼런스 언어로서 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

당신은 C와 C++에서 ZeroMQ API가 유사하다는 것을 볼 수 있다. PHP나 Java와 같은 언어에서, 우리는 코드를 더 읽기 쉽게 만들기 위해서 더 많은 것들을 은닉화할 수 있다.

<?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

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

import org.zeromq.ZMQ;

public class hwserver {

public static void main(String[] args) throws Exception {
ZMQ.Context context = ZMQ.context(1);

// Socket to talk to clients
ZMQ.Socket responder = context.socket(ZMQ.REP);
responder.bind("tcp://*:5555");

while (!Thread.currentThread().isInterrupted()) {
// Wait for next request from the client
byte[] request = responder.recv(0);
System.out.println("Received Hello");

// Do some 'work'
Thread.sleep(1000);

// Send reply back to client
String reply = "World";
responder.send(reply.getBytes(), 0);
}
responder.close();
context.term();
}
}

hwserver.java: Hello World server

다른 언어들에서의 서버 코드:


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

클라이언트 코드:


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

실질적으로 매우 쉽게 보이지만, ZeroMQ 소켓들은 앞서 배운 것과 같이 강력한 힘을 가진다. 수천의 클라이언트들이 이 서버에 한꺼번에 요청해도 빠르고 안정적으로 동작할 것이다. 재미삼아 클라이언트를 먼저 시작하고, 그 후에 서버를 시작해보자. 이제 모두 여전히 동작하는지 지켜본 후에 이게 의미하는 바를 잠깐 생각해보자.

이 두 프로그램이 실제로 어떻게 동작하는지를 간단히 설명해보고자 한다. 이들은 동작을 위한 ZeroMQ 컨텍스트와 소켓을 하나 만든다. 이 말이 무엇을 의미하는지는 곧 알게될 테니 신경쓰지 말자. 서버는 자신의 REP(응답) 소켓을 5555번 포트와 묶는다. 서버는 루프 내에서 요청을 대기하고, 요청이 올 때마다 응답한다. 클라이언트는 요청을 보내고 서버로부터 돌아오는 응답을 읽는다.

서버를 죽이고 (Ctrl-C) 재시작 한다면, 클라이언트는 정상적으로 복구되지 않을 것이다. 깨진 프로세스로부터 복구를 하는 것은 그리 쉬운 일이 아니다. 신뢰성 있는 요청-응답 흐름을 만드는 것은 우리가 [#reliable-request-reply]를 배우기 까지는 다룰 수 없을 정도로 복잡하다.

무대 뒤에서 많은 일들이 일어나지만 프로그래머들이 걱정할 것은 어떻게 짧고 친절한 코드를 만드는지, 그리고 고부하 상태에서 얼마동안 문제가 발생하지 않는지이다. 이것은 요청-응답 패턴이고, 아마 ZeroMQ를 사용하는 가장 쉬운 방법일 것이다. ZeroMQ는 RPC나 고전적인 클라이언트/서버 모델에 해당된다.

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와 같이 당신은 순간 깨달음과 신뢰를 경험하게 될 것입니다.