ZeroMQ — библиотека обмена сообщениями

25 апреля 2011 г.

В интернете можно найти много слов о различных NoSQL решениях. Это действительно здорово, что помимо неповоротливых монстров, появляется много легковесных конструкций, из которых можно выбрать ту самую, которая подходит лучше всего.
Это может быть Cassandra, или Lucene, или Tokyo Cabinet.
А потом можно попить пива и похвастаться перед своими коллегами десятками тысяч запросов в секунду, страшной надежностью или чем-нибудь еще.

ZeroMQ логотипВ мире распределенных вычислений ситуация немного другая, там никогда не было одного доминирующего подхода, и с такими монстрами как Corba или AMQP (в разных проявлениях) всегда сосуществовало множество доморощенных решений.

Библиотека ZeroMQ смотрится на фоне своих уважаемых коммуникационных родственников так же ярко и свежо, как Hadoop или Cassandra рядом с PostgreSQL.

ZeroMQ — это хорошая библиотека. Ее исходный код на С и С++ эстетически приятен, она отлично документирована, имеет великолепное и очень отзывчивое комьюнити. У ZeroMQ несколько десятков биндингов для разных языков.

А теперь немного о том, что ZeroMQ умеет.

ZeroMQ обеспечивает взаимодействие между потоками выполнения. Это взаимодействие максимально унифицировано для нитей, процессов, локальных и глобальных сетей. Если Вы написали о отладили приложение на своем ноутбуке, то с минимальным реконфигурированием сможете использовать мощный бокс с десятками ядер или кластер из таких боксов.
Ключевой метод — передача информации с помощью сообщений, вместо обеспечения прямого доступа в ней. Для этого внутри библиотеки ZeroMQ используются неблокирующие алгоритмы и структуры данных en.wikipedia.org/wiki/Non-blocking_algorithm, а прикладной программист всего лишь использует ее элегантный API.

Так выглядят незатейливые сервер и клиент, написанные, соответственно, на С++

#include  #include <zmq.hpp>

zmq::context_t ctx(1);

void*
 f(void* endpoint)
 {
 zmq::socket_t s(ctx, ZMQ_REP);
 s.connect((char*)endpoint);
 const std::string world = ", World!";
 for (;;)
 {
 zmq::message_t req;
 s.recv(&req);

zmq::message_t reply(req.size() + world.size());
 memcpy(reply.data(), req.data(), req.size());
 memcpy(static_cast<char*>(reply.data()) + req.size(), world.data(), world.size());

s.send(reply);
 }
 }

int
 main()
 {
 f((void*)"tcp://localhost:23001");
 }

и на python

import zmq
 context = zmq.Context()
 socket = context.socket(zmq.REQ)
 socket.bind("tcp://lo:23001")

for request in range(1, 10):
 socket.send ("Hello")
 message = socket.recv()
 print "Received: ", message
 

Серверных процессов можно запустить много, и запросы они будут получать от клиента по очереди. Причем добавлять серверные процессы можно на ходу, если они не справляются.
А вот клиент в нашей архитектуре может быть только один просто потому, что bind делается на стороне клиента. Ситуация если и странная, то только до переименования клиента в producer, а сервера в consumer.

Правда, здорово? Количество небольших отличий от BSD sockets невелико, но оно переходит в новое приятное и полезное качество, а легкость, ясность концепции и привычность остаются на своих местах.

А теперь сделаем этот сервер многопоточным. Функция f() останется неизменной, а main станет вот такой

int
 main()
 {
 zmq::socket_t workers (ctx, ZMQ_XREQ);
 workers.bind ("inproc://workers");

// Create an endpoint for client applications to connect to.
 // We are usign XREP socket so that processing of one request
 // won't block other requests.
 zmq::socket_t clients (ctx, ZMQ_XREP);
 clients.connect ("tcp://localhost:23001");

// We'll use 11 application threads. Main thread will be used to run
 // 'dispatcher' (queue). Aside of that there'll be 10 worker threads.
 // Launch 10 worker threads.
 for (int i = 0; i != 10; i++)
 {
 pthread_t worker;
 int rc = pthread_create (&worker, NULL, f, (void*) "inproc://workers");
 assert (rc == 0);
 }

// Use queue device as a dispatcher of messages from clients to worker
 // threads.
 zmq::device (ZMQ_QUEUE, clients, workers);

return 0;
 }
 

Что уж скрывать, есть и другая причина, почему в моем примере connect делается на серверной стороне: для транспорта inproc (в отличие от TCP) bind должен предшествовать вызову connect. Между ZeroMQ и идеалом есть и другие отличия. Автор отличного веб сервера на основе ZeroMQ Mongrel2 причисляет к ним неаккуратную обработку ошибок — ZeroMQ предполагает, что участники взаимодействия соблюдают протокол. На практике это означает, что любой выставленный в Интернет TCP сокет с ZeroMQ делает приложение уязвимым.

Но сильных сторон у ZeroMQ все-таки больше. Помимо уже продемонстрированных сокетов REQ и RES, удобных для взаимодействия запрос/ответ, есть несколько других, в частности Publisher/Subscriber. Выразительные возможности ZeroMQ позволяют компоновать действительно сложные системы.

Создание параллельного сборщика мусора весьма нетривиальная задача. Довольно широко известны проблемы Ruby, отчасти решенные с помощью JVM. А для языка OCaml настоящим спасением стала ZeroMQ — она пропагандируется как основное средство для создания эффективных многонитевых приложений.
Авторы OCaml решили, что не хотят жертвовать эффективностью и разменивать ее на параллельность. Ведь те, кому важна параллельность, могут воспользоваться ZeroMQ. Возможно, Вам тоже нужна ее помощь?

Этот текст написан не только для того, чтобы получить инвайт и не только для того, чтобы рассказать про замечательную библиотеку. Дело в том, что один из ее авторов Martin Sustrik на следующей неделе будет в Москве. Желающие с ним встретиться могут заглянуть в ZeroMQ maillist или связаться со мной. Мартин говорит по-русски.

Но вот к чему я не стремился, так это к тому, чтобы мой рассказ был исчерпывающим. Поэтому знакомство с ZeroMQ лучше продолжить чтением ZGuide zguide.zeromq.org/ .

Теги:
рубрика Python