Обмен сообщениями между сервисами с ZEROMQ и NODE.JS

Thank you for reading this post, don't forget to subscribe!

ZeroMQ это неболь­шая и шуст­рая биб­лио­те­ка для обме­на сооб­ще­ни­я­ми, кото­рая оди­на­ко­во хоро­шо рабо­та­ет как меж­ду про­цес­са­ми на одном хосте, так и по сети. Хотя она напи­са­на на C++, очень доб­рые люди созда­ли адап­те­ры для все­го: хоть для Node.js, хоть для Haskell.

Рабо­та с ZeroMQ напо­ми­на­ет рабо­ту с TCP/UDP соке­та­ми. Один про­цесс созда­ёт сокет и при­вя­зы­ва­ет его к адре­су, вто­рой — под­клю­ча­ет­ся к пер­во­му, и понес­лась, род­ная. Прав­да, соке­ты в ZMQ весь­ма необыч­ные. Но, навер­ное, сто­ит начать с при­ме­ров, и по ходу дела уже вда­вать­ся в детали.

Суще­ству­ет три обыч­ных пат­тер­на рабо­ты с оче­ре­дя­ми сообщений:

  • Отпра­вить-и-забыть
  • Запрос-ответ
  • Publish-subscribe

Поче­му бы не попро­бо­вать сде­лать их на Node.JS и ZeroMQ?

Установка

Луч­ше все­го опи­са­на в офи­ци­аль­ной доку­мен­та­ции, но вооб­ще-то в NPM есть пакет  zmq , кото­рый нам и нужен. Если в Mac OS он у меня завёл­ся сра­зу, то на Debian при­шлось сна­ча­ла уста­нав­ли­вать  libzmq-dev , а потом уже раз­го­ва­ри­вать с NPM. Таким обра­зом, если вокруг сто­ит Debian/Ubuntu/­по­ло­ви­на-обра­зов-Docker, то нуж­но исполь­зо­вать такое заклинание:

А на маке  npm install ...  долж­но хва­тить. Как все­гда, когда с уста­нов­кой ста­но­вит­ся совсем непо­нят­но, есть доку­мен­та­ция.

Паттерн «отправить-и-забить»

Ока­зы­ва­ет­ся, ZeroMQ в кур­се суще­ство­ва­ния пат­тер­нов, и идёт с ком­плек­тов соке­тов, зато­чен­ных под рабо­ту имен­но с ними. Напри­мер, в ZMQ есть пат­терн push-pull, кото­рый прак­ти­че­ски иден­ти­чен «отпра­вить-и-забыть», и для него созда­ны кон­крет­ные соке­ты PUSH и PULL.

Пред­ста­вим, что в мире Hello-World при­ло­же­ний для пол­но­го сча­стья ста­ло не хва­тать сер­ви­са, кото­рый раз в две секун­ды отправ­ля­ет сооб­ще­ние «Ping #1,2,3..»:

А так как луч­ше одно­го бес­смыс­лен­но­го при­ло­же­ния — толь­ко два бес­смыс­лен­ных при­ло­же­ния, напи­шем для него и кли­ен­та в нагрузку:

И если запу­стить это счастье:

То полу­чит­ся доста­точ­но пред­ска­зу­е­мый резуль­тат. Но даже с таким про­стым при­ме­ром мож­но доволь­но дол­го играть­ся и откры­вать для себя инте­рес­ные шту­ки. Например:

  1. Совсем без раз­ни­цы, кто запу­стил­ся пер­вым — кли­ент или сер­вер. Соке­ты всё рав­но созда­дут­ся, и как толь­ко оба сер­ви­са появят­ся в сети — через миро­вой эфир поле­тят сообщения.
  2. Даже если убить сер­вер, кли­ент оста­нет­ся жить. Как толь­ко сер­вер вер­нёт­ся, кли­ент пере­под­клю­чит­ся автоматически.
  3. Если при­бить кли­ен­та, то сер­вер будет накап­ли­вать неот­прав­лен­ные сооб­ще­ния до тех пор, поку­да не при­дёт кто-то, кто их забе­рёт. Напри­мер, тот же клиент.
  4. Мож­но запу­стить сра­зу два кли­ен­та, но они тут же нач­нут сорев­но­вать­ся за сооб­ще­ния. Пер­вый, напри­мер, полу­чит чёт­ные, а вто­рой — нечет­ные. Или наобо­рот. Важ­но то, что у каж­до­го сооб­ще­ния будет толь­ко один получатель.
  5. Если при­бить сер­вер, пока у него есть неот­прав­лен­ные сооб­ще­ния — они про­па­дут навсе­гда. Дру­ги­ми сло­ва­ми, ZeroMQ ни разу не durable.

Что­бы полу­чить эти фичи обыч­ны­ми соке­та­ми, при­шлось бы поста­рать­ся. А тут бес­плат­но, и с мини­маль­ным коли­че­ством кода.

Паттерн Запрос-ответ

В отли­чие от преды­ду­ще­го това­ри­ща, этот пат­терн под­ра­зу­ме­ва­ет реак­цию на исхо­дя­щие сооб­ще­ния. Как и в про­шлый раз, у ZMQ есть спе­ци­аль­ные соке­ты для это­го: REQ и REP. Итак, сер­вер идёт первым:

При­мер всё еще про­стой. Кли­ен­та я сде­лал чуть-чуть по-объ­ём­нее, но его логи­ка сво­дит­ся к «отправь сооб­ще­ние, полу­чи ответ, повто­ряй раз в две секунды»:

И если запу­стить кли­ен­та и сер­ве­ра, они запо­ло­нят кон­соль зага­доч­ны­ми сооб­ще­ни­я­ми, кото­рые так любят заказчики:

С этим при­ме­ром тоже полез­но поиг­рать и, напри­мер, заме­тить, что ZeroMQ-соке­ты нель­зя исполь­зо­вать «не по-пат­тер­ну». Напри­мер, на одно вхо­дя­щее сооб­ще­ние REP-сокет может отве­тить толь­ко один раз. Если попы­тать­ся вызвать send два раза, то ZMQ при­па­сёт вто­рое сооб­ще­ние для сле­ду­ю­ще­го запро­са. Похо­жие пра­ви­ла рас­про­стра­ня­ют­ся на все соке­ты. PUSH сокет может толь­ко отправ­лять, но не может полу­чать, PULL — стро­го наобо­рот, и т. д.

Паттерн Publish-subscribe

Два пер­вых пат­тер­на под­ра­зу­ме­ва­ли, что одно сооб­ще­ние отпра­вит­ся толь­ко одно­му полу­ча­те­лю. Ино­гда это­го мало. Поэто­му в ZeroMQ есть PUB/SUB соке­ты, а так­же кон­цеп­ция топи­ков, на кото­рые могут под­пи­сать­ся все, кому не лень, и полу­чать свои копии сообщений.

По тра­ди­ции, сер­вер идёт первым:

А за ним — клиент:

Раз в две секун­ды сер­вер гене­ри­ру­ет сооб­ще­ние heartbeat, а кли­ент на него под­пи­сы­ва­ет­ся. Всё просто.