|
|
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Для удобства в дальнейшей работе создадим пару функций, пересылающих и принимающих строки: Код: pascal 1. 2. Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.10.2014, 20:44 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Обмен сигналами между потоками. Пора рассмотреть сокеты типа PAIR . Пример: Приложение в "основном потоке" создает поток 2 и ждет сигнала о выполнении какой-то полезной работы. Поток 2 создает поток 3 и ждет сигнала о выполнении какой-то полезной его работы, затем посылает сигнал "основному" потоку. Поток 3 выполняет некоторую полезную работу и посылает сигнал потоку 2. Отправка - прием сигналов будет выполняться с помощью сокетов типа PAIR по inproc протоколу. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.10.2014, 21:04 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
"Сигнал" в данном случае - это просто сообщение, строка "READY". На приемной стороне содержамое строки игнорируется, важен сам факт передачи-приема. ... Что-то я с названиями напутал. :( Step 3 - это "основной" поток, который создает Step 2. Последний создает Step 1. Ну и Step 3 ждет сигнала от Step 2, а Step 2 ждет сигнала от Step 1. Итак, исходник: Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.10.2014, 21:45 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Итак, это был образец классического многопоточного приложения ZeroMQ: - Два потока взаимодействуют через InProc, используя общий контекст. - Родительский поток создает один сокет, связывает его с конкретной конечной точкой по InProc, а затем запускает дочерний поток, передавая ему контекст. - Дочерний поток создает второй сокет, соединяет его с той же конкретной конечной точкой по InProc и по готовности сигнализирует родительскому потоку. Обращаем внимание, что многопоточный код, используемый в данной схеме, не масштабируется за пределы процесса: если используется протокол InProc и сокеты типа PAIR, значит, строится сильносвязная система, в которой есть взаимозависымые структуры. Такие вещи следует делать, когда нужна высокая скорость взаимодействия между компонентами системы. Если использовать схему с протоколом TCP и использовать собственный контекст в каждом потоке, система будет менее связной и позволит в будущем легко масштабироваться методом вычленения узлов в отдельные процессы. ~~~~~~~~~~~~~ Почему были использованы сокеты типа PAIR? -Потому, что использование сокетов других типов имеет нежелательные побочные эффекты: - Можно использовать рассмотренные ранее PUSH для отправителя и PULL для приемника. Это выглядит просто и будет работать, однако следует помнить, что PUSH будет распределять сообщения по всем доступным приемникам. Если вы случайно запустили два приемника (например, создали еще один поток с такой же процедурой потока), то вы будете "потеряете" половину ваших сигналов. Преимущество сокетов PAIR в том, что они не позволят создать больше одного соединения; пара сокетов типа PAIR - является эксклюзивной. - Вы можете использовать DEALER для отправителя и ROUTER для приемника. ROUTER, однако, упаковывает сообщение в "конверт", т.обр. ваш сигнал нулевого размера превращается в составное сообщение. Это несущественно, если вы не заботитесь о самих данных, а посылаете только сигнал. Однако, если понадобится отправить реальные данные, то обнаружится, что ROUTER прислал вам "неправильные" сообщения. Кроме того, DEALER точно так же как и PUSH, распределяет исходящие сообщения между всеми приемниками , т.е. тут такой же риск потери сообщений, как и при использовании PUSH. - Вы можете использовать PUB для отправителя и SUB для приемника. Эта схема будет правильно доставлять сообщения, и PUB не разбросает их по приемникам, как DEALER или PUSH. Тем неменее, вам придется все время настривать приемник на подписку, что утомительно. По этим причинам, пара сокетов типа PAIR - лучший выбор для пересылки сигналов координации между парами потоков в приложении. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.10.2014, 22:42 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Согласование работы между узлами сети. Если нужно согласовывать работы набора узлов в сети, то сокеты типа PAIR уже не так хороши. Это как раз те области, где стратегии использования потоков и узлов различаются. В большинстве случаев узлы приходят и уходят "сами по себе", а потоки обычно статичны. Сокеты типа PAIR не выполнят автоматическое переподключение, если удаленный узел сети уйдет, а потом появится снова. Другим существенным различием в применении узлов и применением потоков является то, что обычно мы имеем фиксированное число потоков, в то время как число рабочих узлов сети меняется. ... ~~~~~~~~~~~~~ Рассмотрим рассмотреть предыдущий сценарий (с метостанцией-издателем и кучей клиентов-подписчиков) и попробуем координировать узлы так, чтобы быть уверенными в том, что при запуске подписчики-клиенты не потеряют данные. Схема работы приложения: - Издатель (сервис метеостанции) заранее знает, сколько будет подписчиков. То есть, это просто волшебное число, которое он откуда-то получает. - Издатель запускается и ждет, пока подключатся все подписчики. Эта часть и есть процесс согласования. Каждый подписчик подписывается, а затем через другой сокет сообщает издателю, что он готов. - Когда к издателю подключатся все подписчики, он начинает публиковать данные. В данном случае для согласования действий между подписчиком и издателем мы будем использовать пары сокетов REQ-REP. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 00:04 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Поехали. Кодируем проект "Метео" (исходная задача - 16583825 ). Сервис начинает публиковать данные только после коннекта 10 клиентов. Пакеты данных публикуются 10 000 раз, после чего публикуется сообщение 'END'. Клиенты при запуске сообщают сервису о своем появлении и подписываются на данные сервиса. Полученные по подписке данные выводятся в консоль клиента, число полученных пакетов подсчитывается, . Рабочий цикл клиента прерывается при получении сообщения 'END'. Код сервиса: Сервис "метео" с синхронизацией запуска. Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. Код клиента: Клиент "метео" с синхронизацией запуска. Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. Мы не можем быть уверены, что коннект SUB будет завершен к тому времени, когда завершится диалог REQ/REP. Вообще нет никакой гарантии того, что исходящие соединения завершатся в том или ином порядке, если вы используете любой транспорт за исключением InProc. Ну, в примере мы воткнули ожидание ( sleep(1) ) после подпиской и синхропосылками REQ/REP. Что как бы работает, но тоже в общем случае не гарантирует. Более надежная схема могла бы выглядеть так: - Издатель открывает PUB - сокет и начинает передавать сообщения "Hello"(без данных). - Подписчик подключает SUB - сокет и, когда тот принимает сообщение "Hello", то сообщает об этом издателю через пару сокетов REQ/REP. - Когда издатель получит необходимое число подтверждений от коннекте от подписчиков, он начинает публиковать реальные данные. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 01:57 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Нуль - копия (Zero-Copy ). API ZeroMQ позволяет отправлять и принимать сообщения напрямую, используя буфера данных приложения, без копирования данных. Эта технология называется нуль - копия , и её применение позволяет увеличить производительность в некоторых приложениях. О нуль-копии следует вспоминать в случае, когда вы отправляете большие блоки памяти (тысячи байт) с высокой частотой. Для коротких сообщений, или для сообщений, отравляемых редко, использование нуль-копии сделает код грязнее и сложнее без заметной пользы. Как и все прочие оптимизации, использовать нуль-копии следует, когда вы точно знаете, что это это помогает, и были выполнены измерения производительности до и после применения. Делаем нуль-копию. С помощью zmq_msg_init_data() создается сообщение, которое ссылается на уже существующий блок данных вашего приложения, которое затем передается в zmq_msg_send() . Когда создается сообщение, вы также передаете параметр - функцию, чтобы ZeroMQ смогла вызвать её для освобождения блока данных после завершения передачи сообщения. Пример такой функции, предполагающий, что буфер представляет собой блок длиной, скажем, в 1000 байт, выделенный в куче: Код: pascal 1. 2. 3. 4. PS: в модуле ZMQ.PAS Тип zmq_free_fn определен как Код: pascal 1. 2. 3. без модификатор cdecl. Конечно, следует исправить: Код: pascal 1. Пример применения: Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. PPS: насчет параметра hint: Код: pascal 1. Значение его просто дублируется из параметра hint функции Код: pascal 1. 2. Судя по всему, он введен для особых случаев - например, когда требуется передать блок дополнительных данных в процедуру ffn: free_fn. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 03:58 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
чччД Нуль - копия (Zero-Copy ). Следует еще раз отметить, что вызывать zmq_msg_close() после отправки сообщения не нужно - libzmq выполнит вызов автоматически, когда сообщение будет отправлено. Способа сделать нуль-копию на приеме - нет. ZeroMQ предоставит вам буфер, который вы можете использовать сколько угодно, но не записывает данные напрямую в буферы вашего приложения. При записи составных сообщений ZeroMQ отлично работает с нуль-копией. Для обычных сообщений вам понадобилось бы слить несколько сообщений в один буфер, а только потом отправлять. То есть, понадобилось бы выполнить копирование данных. А с ZMQ можно отправить несколько разных буферов, пришедших от разных источников, как отдельные кадры сообщения. Каждое поле отправляется как кадр, отделенный значением длины (префиксом). В приложении это выглядит как последовательность вызовов отправлений или приема. Однако, внутри ядра ZMQ, составное сообщение отправляется и принимается одним системным вызовом, что очень эффективно. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 04:12 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Упаковка сообщений для схемы "Издатель-Подписчик" Вернемся к схеме "Издатель - Подписчик". (Приложение "Метостанция"). Вспомним, что при подписке данные можно фильтровать: 16590914 . Однако, фильтрация по самим данным не всегда удобно. Куда удобнее фильтровать по значению ключевого поля, связанному с данными. Например: T - Температура P - Давление W - Скорость ветра Это гораздо удобнее. Например, ключу W можно сопоставить не только скорость ветра, но и направление ветра. Или данные о скорости ветра разбить на две подгруппы: WS - ветер до 5 м/с; WF - ветер 5 м/с и более. Это реализуется очень просто: на сервере нужно формировать составное сообщение: Код: pascal 1. 2. 3. 4. А клиент, при оформлении подписки, должен указать фильтр: Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. При этом сообщение будет фильтроваться по первому кадру, а приходить отфильтрованное сообщение будет полностью. Сервис. Код сервиса "Метео" с составным пакетом Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. Клиент: Клиент "Метео", составной пакет Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. Разбиение сообщения на части удобно, в том числе, например, для логического разделения составных данных.Например: Ключ - Адрес - Основное сообщение. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 05:16 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
High-Water Marks (Высокая вода). Когда вы наконец сможете отправлять сообщения с огромной скоростью от процесса к процессу, вы вскоре онаружите, что память имеет свойство заканчиваться. Несколько секунд задержки в каком-то процессе приводит к общему дикому ухудшению реакции системы. В общем, в проблему нужно вникнуть и принять меры предосторожности. Проблема заключается в следующем: вообразите, что есть процесс A, с высокой частотой отправляющий сообщения процессу B, который их обрабатывает. Иногда процесс B оказывается недоступен (сборка мусора, перегрузка CPU, что угодно), и не может обработать сообщения за короткий период. Если такие задержки составляют несколько секунд или даже больше - это может стать серьезной проблемой. Что произойдет с сообщениями, которые процесс A все еще старается отправлять? Некоторые из них попадут в сетевые буферы процесса B. Некоторые будут все еще в процессе передачи по Ethernet. Некоторые будут в буферах сети процесса A. А остальные будут накапливаться в памяти процесса A с той скоростью, с которой процесс A их отправляет. Если не принять мер предосторожности, можно легко получить out of memory и крах. Это - классическая проблема систем обмена сообщениями. Причем, чаще всего, процесс В - это приложение, написанное пользователем, и процесс А его никак не контролирует. Что делать? Один из вариантов решения - управлять входным потоком. Процесс A получает сообщение откуда-то еще. Говорим ему "Stop!". И так далее - тормозим всех, кто меня торопит. Это называется управление потоком (flow control - обмен сигналами, при котором каждое устройство оповещает о готовности послать или принять данные). Такое решение выглядит правдоподобно, но что если вам пришло сообщение из Твиттера? Вы скажете всему миру подождать, пока вы в процессе B делается что-то важное? Flow control работает в некоторых случаях, но не работает в других. Транспортный уровень не может сообщить уровню приложения "stop". Это как если метрополитен скажет большему бизнесу: "пожалуйста, держите работников еще полчаса, я слишком занят". Решением для системы обмена сообщениями является назначение пределов размеров буферов, а по достижению этих границ - выполнение некоторых разумных действий. В некоторых случаях (ОК, не для метрополитена), будет отказ в обслуживании (сообщения отбрасываются), в других лучшей стратегией будет ожидание. ZeroMQ использует концепцию HWM (high-water mark) для определения емкости своих внутренних трубопроводов. Каждое соединение от сокета к сокету имеет собственный трубопровод, и значение HWM для отправки и/или для приема , в зависимости от типа сокета. Некоторые сокеты (PUB, PUSH) имеют только буферы для отправления сообщений. Некоторые (SUB, PULL, REQ, REP) - только для приема. Некоторые (DEALER, ROUTER, PAIR) имеют оба типа буферов. В ZeroMQ v2.x значение HWM было бесконечным по умолчанию. Это было легко для использования, но и, как правило, оказывалось смертельным для сокетов - издателей с большим объемом сообщений. В ZeroMQ v3.x оно установлено в 1000 по умолчанию, которое является более разумным. Если вы все еще используете ZeroMQ v2.x, вы всегда должны установить HWM на ваших сокетах, например - 1000, чтобы соответствовать ZeroMQ v3.x или другое значение, которое посчитаете правильным. Когда сокет достигает своего HWM, он либо блокирует данные, либо отбрасывает сообщения, в зависимости от типа скета. Сокеты типа PUB и ROUTER будут отбрасывать данные, в то время как другие будут блокировать. Для транспорта inproc передающий и принимающий сокеты используют общие буферы, поэтому реальное значение HWM будет суммой HWM, установленных для обоих сторон. Наконец, последнее. Значения HWMs не являются точными величинами. Если вы установите 1,000 сообщений (по умолчанию), то реальная величина буфера будет меньше, чем половина, так как libzmq реализует еще и собственные очереди. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 06:11 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Потери сообщений. Сообщения атомарны, это хорошо. То есть, если вы что-то получаете - то получаете это полностью. Плохо то, что если что-то теряется, то вы не получаете вообще ничего. Далее - универсальный решатель проблем потерь сообщений. - Для сокетов SUB, всегда устанавливать подписку с помощью zmq_setsockopt() и ZMQ_SUBSCRIBE , или не получите ни одного сообщения. Так как вы подписываетесь на сообщения с заданным префиксом, для приема всех сообщений следует указать префикс "" (пустая строка). - Если вы стартуете сокет SUB (то есть, соединяетесь с сокетом PUB) после того, как сокет PUB начал рассылать данные , вы потеряете все, что было разослано до вашего подключения. Если это является проблемой, поменяйте архитектуру так, чтобы сокет SUB стартовал первым, а лишь затем начинайте публикацию данных с сокетом PUB. - Даже если сокеты SUB и PUB синхронизированы, сообщения могут все равно теряться . Это из-за того, что внутренние очереди могут быть еще не созданы до момента фактического коннекта. Если вы можете переключить направление операций bind/connect так, чтобы для сокета SUB выполнялся bind, а для сокета PUB - connect, возможно, вы получите более рабочий вариант. - Если используются сокеты REP и REQ, но при этом не соблюдается синхронный порядок операций запрос/ответ/запрос/ответ, ZeroMQ будет сообщать об ошибках, которые вы, возможно, будете игнорировать. Это также будет выглядеть как потеря сообщений. При использовании сокетов REQ или REP следует строго соблюдать последовательность запросов и ответов на обеих сторонах соединения. И всегда в реальном коде следует проверять коды ошибок после вызовов ZeroMQ. - Если используются сокеты PUSH, может получиться, что первые подключившиеся сокеты PULL получат несоразмерно большое количество сообщений . Равномерное распределение сообщений возможно лишь в случае, когда все сокеты PULL успешкно подключились, что может занять несколько миллисекунд. В качестве альтернативы PUSH/PULL, для если нагрузка невелика, следует рассмотреть пару ROUTER/DEALER и шаблон балансировки нагрузки. - Разделение сокетов между потоками приведет к неустойчивому поведению и авариям . - Если используется транспорт inproc, убедитесь, что оба сокета имеют общий контекст . Иначе будет отказ на стороне коннекта. Также, сначала делайте bind, а лишь затем - connect. Транаспорт inproc не отключаемый, в отличии от tcp. - При использовании сокетов ROUTER очень легко потерять сообщения, если случайно отправлены неверные кадры идентификации (или если вообще забыть про кадры идентификации). Отличной идеей будет установка для таких сокетов ROUTER отции ZMQ_ROUTER_MANDATORY, но все равно не следует забывать проверять код завершения после каждого вызова ZMQ . Последнее. Если вы все сделали правильно, но ничего не работает, сделайте крошечное тестовое приложение, воспроизводящее проблему и запросите помощь у сообщества ZeroMQ. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 20:11 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
чччД... - Если используется транспорт inproc... .Также, сначала делайте bind, а лишь затем - connect. ... PS: В ZeroMQ версии 4.* эта проблема решена, bind и connect для inproc может быть выполнен в любой последовательности. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 02.10.2014, 20:14 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Шаблон "Запрос - Ответ" - подробности. Ранее были немного упомянуты составные сообщения. Так вот, использование составных сообщений дает возможность оформлять сообщения в форме "конвертов", когда адрес отделен от тела сообщения. Так вот, сообщения в SUB/PUB сокетах как раз пересылаются в таких конвертах. Наличие адресов позволяет легко организовать двусторонний обмен с помощью средств общего назначения - таких, как ZPI ZMQ и прокси, которые на лету создают, читают и удаляют адреса, не затрагивая "полезные" данные. Так вот, в шаблоне "Запрос - Ответ" конверт содержит обратный адрес для ответа. Наличие обратного адреса позволяет получить ответ на запрос. При использовании сокетов REQ и REP нет никакой нужды создавать конверты самостоятельно; это автоматически делают сами сокеты. Для понимания интересно разобрать, как такие конверты используются с сокетом ROUTER. Простая форма конверта для ответа REPLY . Обмен данными "запрос-ответ" состоит из сообщения-запроса и, в конечном итоге, из сообщении-ответа. В простой схеме "запрос-ответ" на один запроса приходится один ответ. В более сложных схемах запросы и ответы могут пересылаться асинхронно. Однако, ответный "конверт" всегда одинаковый. В общем виде, ответный конверт ZMQ всегда состоит из нуля или более обратных адресов, следующих после пустого кадра. (разделителя конвертов). За ними следует тело сообщения - ноль или более кадров. Конверт создается множеством сокетов, работающих в одной цепочке. Например, в сокет REQ отправляем запрос "Hello". Сокет REQ создает простейший конверт для ответа без адреса, просто пустой кадр и кадр сообщения, содержащий строку "Hello". Т.обр., сообщение из двух кадров: № кадраДлинаСодержание1025Hello Сокет REP, получив конверт, "вскрывает" его: удаляет разделитель конвертов (первый кадр) и передает сообщение приложению. Если перехватить сетевые данные приложения, мы увидим, что каждый запрос и каждый ответ состоит из двух кадров: пустой кадр, а затем кадр с полезными данными. Для простого случая "REQ-REP" это выглядит не очень полезным. Однако, это важно для сокетов типа ROUTER и DEALER. Расширенная форма конвертов для ответа Рассмотрим, как работают пары сокетов REQ-REP через прокси (который использует сокеты ROUTER-DEALER), и как это влияет на форма конвертов для ответа. См. приложение из 16630307 В общем, совершенно никакой разницы, сколько прокси - ноль, один, два или больше: Код: plaintext 1. 2. 3. Вот псевдокод того, что делает прокси: Код: sql 1. 2. 3. 4. 5. 6. 7. 8. 9. Просто код (не всевдо). Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. Так вот. Сокет ROUTER, в отличии от всех остальных, отслеживает каждое входящее соединение и сообщает об этом вызывающей стороне. Вызывающая сторона получает уведомление, что она должна проверять идентификатор каждого входящего сообщения, который будет следовать перед сообщением. Идентификатор, который иногда называют адресом, представляет собой всего-навсего двоичную строку, несущую единственную нагрузку: "Это - уникальный дескриптор связи". После этого, когда приложение отправляет сообщение через сокет ROUTER, первым отправляется кадр идентификации. Вот что сказано об этом в документации по zmq_socket() : ...из документации по zmq_socket() Перед передачей в приложение все сообщения, принимаемые сокетом ZMQ_ROUTER , должны предваряться частью собщения, идентифицирующей вызывающего корреспондента. Сообщения принимаются от всех подключенных корреспондентов в соответствии с алгоритмом справедливой очереди (fair-queued) . При отправке сообщения, сокет ZMQ_ROUTER должен удалить первую часть сообщения и использовать её для идентификации корреспондента, которому должно быть доставлено сообщение. В качестве идентификаторов ZMQ v2.* использовала UUID, а начиная с V3.0 используются короткие целые. Изменения улучшили производительность сети, правда, только в случае использования множества прокси - ретрансляторов (что бывает крайне редко). Сокет ROUTER для каждого соединения генерирует случайное число. То есть, когда к сокету ROUTER в прокси подключается три клиента сокетами REQ, генерируется три случайных числа, по одному на каждый сокет REQ. Итак, начнем разрабатывать поясняющий пример. Предположим, у сокета REQ трехбатовый идентификатор "ABC". Это подразумевает, что внутренние механизмы сокета ROUTER содержат хэш-таблицу, в которой ищется строка "ABC" и соответствующее данному сокету REQ соединение TCP. При получении сообщения от сокета ROUTER socket, мы получаем три кадра: Запрос с одним адресомНомер кадра Длина Содержание Описание13ABCИдентификатор соединения20Пустой разделяющий кадр35HelloКадр с данными Ядро прокси в цикле просто читает данные из одного сокета и передает в другой, то есть буквально эти три кадра и попадают на вход сокета DEALER. Прослушка сетевого трафика показала бы, что эти три фрейма пролетают из сокета DEALER в REP. Сокет REP делает все так же, как было описано ранее: срезает с конверта все, включая новый обратный адрес и передает "Hello" абоненту - получателю. Следует отметить, что сокет REP одновременно может работать только одним циклом запрос - ответ. Поэтому, если вы попытаетесь прочитать несколько запросов или оправить несколько ответов, строго не придерживаясь цикла "Запрос-Ответ", сокет вернет ошибку. Теперь вполне понятен и обратный путь сообщения. Когда сервис возвращает ответ, сокет REP заворачивает его в "сохраненный" (при "вскрытии") конверт, и отправляет ответ из трех кадров через сокет DEALER. Ответ с одним адресомНомер кадра Длина Содержание Описание13ABCИдентификатор соединения20Пустой разделяющий кадр35WorldКадр с данными Далее сокет DEALER читает эти три фрейма и отправляет через сокет ROUTER. Сокет ROUTER берет первый кадр сообщения с идентификатором "ABC" и находит коннект, связанный с ним. Ели коннект найден, наружу перекачивается следующие два кадра - уже знакомый минимальный конверт № кадраДлинаСодержание1025World Сокет REQ принимает сообщение, проверяет, что первым кадром идет пустой разделитель, отбрасывает кадр и передает в приложение "World"... Все просто. Ну и что? Следует признать, что вот такие простые схемы "Запрос - Ответ" или даже "Запрос - Ответ с брокером" не так уж часто применяются. Кроме того, нет простого способа восстановления системы после таких вещей как падение сервера (например, из-за бажного кода). Методы построения надежных схем Запрос -Ответ будут рассмотрены позже. Сейчас разберемся, как отважная четверка сокетов (REQ-REP-ROUTER-DEALER) борется с конвертами. Это позволит делать всякие полезные вещи. Итак, мы поняли, что сокет ROUTER использует конверты для обратной пересылки, чтобы определить, какому из клиентских сокетов REQ следует направить обратный ответ. Или, иными словами: - Каждый раз сокет ROUTER принимает сообщение, оно с помощью идентификатора сообщает вам, какой корреспондент это сообщение прислал. - Вы можете воспользоваться хэш- таблицей (когда идентификатор - ключевая строка), чтобы отследить вновь подключенного абонента. - Сокет ROUTER будет асинхронно, циклически обрабатывать всех корреспондентов, подключившихся к нему, если префикс - идентификатор идентичен первому кадру сообщения. Сокеты ROUTER не обрабатывают конверт полностью. Они ничего не знают о пустых разделителях. Все ,что они делают - это работа с кадром идентификации, который позволяет выяснить, в какой из коннектов следует дальше отправить сообщение. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 05.10.2014, 02:40 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Что мы знаем про сокеты из схемы "Запрос - Ответ" - Сокеты REQ перед сообщением отправляют в сеть пустой фрагмент-разделитель. Сокеты REQ - синхронные. Сокеты REQ всегда посылают один запрос и всегда ждут одного ответа. Сокеты REQ одновременно общаются только с одним корреспондентом. Если вы подключаете сокет REQ к некольким абонентам, запросы будет отправлены, а ответы получены по одному корреспонденту в каждом цикле запрос - ответ. - Сокет REP читает и сохраняет все кадры идентификации до пустого разделительного кадра включительно, затем передает следующий кадр или кадры абоненту. Сокеты REP - синхронные и одновременно общаются только с одним абонентом. Если вы подключаете сокет REP ко множеству абонентов, запросы читаются от корреспондентов последовательно по кругу (in fair fashion), а ответы всегда будут отправляться тому же самому корреспонденту, от которого был последний запрос. - Сокет DEALER не обращает на конверт возврата никакого внимания и обрабатывает его как любое составное сообщение. Сокеты DEALER - асинхронные и похожи на комбинацию сокетов PUSH и PULL. Они распределяют отправляемые сообщения по всем соединениям, и принимают сообщения от всех соединений по алгоритму справедливой очереди. - Сокет ROUTER не обращает внимания на конверт возврата, как и сокет DEALER. Он создает идентификаторы своих соединений, и передают эти идентификаторы корреспондентам в виде первого кадра каждого входящего соединения. И наоборот, когда корреспондент отправляет сообщение, он использует первый кадр как идентификатор, чтобы найти соединение для отправки. Сокет ROUTERS - асинхронный. Допустимые комбинации сокетов От (connect) Направление К(bind) REQ-->REP DEALER--> REP REQ-->ROUTER DEALER-->ROUTER DEALER-->DEALER ROUTER-->ROUTER Недопустимые комбинации: От (connect) Направление К(bind) REQ-->REQ REQ-->DEALER REP-->REP REP-->ROUTER ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 05.10.2014, 03:13 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
"Запрос - Ответ", рабочие комбинации сокетов. Некоторые особенности. REQ->REP Мы уже рассматривали, как клиент REQ общается с сервером REP. Одно замечание: клиент REQ должен быть инициатором потока сообщений. Сервер REP не может начать общение с клиентом REQ, пока тот не пришлет запрос. DEALER->REP Далее. Меняем в клиенте сокет REQ на сокет DEALER. Это дает асинхронного клиента, который может общаться с множеством серверов REP. Если перепишем клиента "Hello World", используя сокет DEALER, мы сможет отправлять любое число запросов "Hello" без ожидания ответов. Когда мы используем сокет DEALER для общения с сокетом REP, мы должны тщательно эмулировать формирвоание конверта, который должен был посылать сокет REQ, или же сокет REP будет отбрасывать сообщение как неправильное. Итак, чтобы отправить сообщение: - Отправляем пустой кадр с установленным флагом "MORE". - Затем отправляем тело сообщения. Ну, а когда принимаем сообщение, то: - принимаем первый кадр и, если он не пуст - отбрасываем все сообщение; - принимаем следующий кадр и передаем его в приложение. REQ -> ROUTER Можем заменить не только REQ на DEALER, но и REP на ROUTER. Это дает нам асинхронный сервер, который может общаться с множеством REQ клиентов одновременно. Если мы сервер перепишем "Hello World", используя сокет ROUTER, мы сможем обрабатывать параллельно любое число запросом "Hello". Мы это уже делали. Есть два различных способа использования сокетов ROUTER:: - как прокси, который переключает сообщения между сокетами frontend и backend. - как приложение, которое читает сообщения и реагирует на них. В первом случае сокет ROUTER просто читает все кадры, включая искусственный кадр с идентификатором, а затем вслепую передает их Во втором случае сокет ROUTER должен знать формат конверта возврата, который он отправляет... Так как второй сокет в паре типа REQ то сокет ROUTER получает кадр идентификации, пустой кадр-разделитель и затем - кадр данных. DEALER -> ROUTER Теперь заменим сразу оба сокета - и REQ, и REP на DEALER и ROUTER. Получам самую мощную комбинацию сокетов, в которой DEALER общается с ROUTER. Это дает нам асинхронного клиента, общающегося с асинхронным сервером, когда обе стороны имеют полный контроль над форматом сообщений. Так как и DEALER и ROUTER могут работать с сообщениями любого фората, вам придется "немного" поработать в качестве проектировщика протокола. Как минимум, вы должны решить - будете ли вы эмулировать конверты возврата REQ/REP. Все зависит от того, нужно ли вам в действительности отправлять ответы или нет. DEALER -> DEALER Вы можете поменять не только REP на ROUTER, но и REP на DEALER, если DEALER общается с одним и только одним корреспондентом. Когда вы заменяете REP на DEALER, ваше приложение становится полностью асинхронным и сможет посылать и принимать любое число запросов и ответов. За это придется заплатить необходимостью управлять формированием конвертами ответа самостоятельно, и получать их либо правильно - иначе вообще ничего работать не будет. Пока отметим, что пара сокетов DEALER -> DEALER - одна из самых хитрых, и хорошо, что в действительности она бывает нужна нечасто. ROUTER -> ROUTER Название пары звучит так, как будто она идеальна для организации соединений N-to-N, но в действительности это наиболее сложная для использования комбинация. В руководстве рекомендуют избегать её, пока не станете докой в ZeroMQ. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 05.10.2014, 04:28 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Для дальнейшей работы понадобится инструмент для более детального просмотра сообщений, поступающих в сокет. Создадим процедуру s_dump(), которая будет читать все части сообщения из сокета и выводить их в консоль: Код: pascal 1. ...и добавим её в наш вспомогательны модуль ZMQ_utils: ZMQ_utils.pas Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. Пригодится, когда начнем разбирать "конверты". ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 01:03 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Сокеты ROUTER , более подробно. Концепция идентификаторов и адресов - Идентификация сообщений в ZMQ касается только сокетов ROUTER. - В широком смысле идентификатор представляет собой обратный адрес для ответа в составе конверта сообщений. В большинстве случаев, идентификаторы являются случайным значением, и они локальны в рамках одного сокета ROUTER. Идентификатор - ключевое значение для поиска в хэш-таблице. - Независимо от значений идентификаторов, соединения могут иметь физические адреса ("tcp://192.168.55.117:5670"), или логические (UUID или email адрес или любое уникальное ключевое значение). Приложение, которое общается с абонентами с помощью сокета ROUTER, может преобразовывать логические адреса в идентификаторы с помощью встроенных хэш-таблиц. Так как в момент передачи сообщения сокет ROUTER задает идентификацию соединения для конкретного корреспондента-отправителя, вы можете ответить лишь этому отправителю, а не произвольному абоненту сокета. Пример крошечного приложения, в котором сообщения пересылаются по inproc протоколу от сокетов REQ к сокету ROUTER: Пример идентификации Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. Создается сокет - приемник ROUTER, к которому коннектятся два сокета REQ. Второму сокету перед коннектом назначется идентификатор 'PEER2'. Смотрим, что же получилось на выходе приемника. Видно, что каждое сообщение состоит из трех кадров: идентификатор, пустой кадр - разделитель, и кадр с данными: ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 01:29 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Обработка ошибок сокета ROUTER . Ну, не то чтобы ошибок. Если сокет ROUTER не может определить, куда отправить сообщения, он просто удаляет их. Для реальных приложений это, наверное, правильно (клиент отвалился - что тут поделаешь?), но это затрудняет отладку - особенно если обратный конверт для сокета ROUTER формируется "ручками". Начиная с ZeroMQ v3.2, у сокетов появилась опция для перехвата ошибок: ZMQ_ROUTER_MANDATORY. Устанавливаем ей для сокета ROUTER получаем возможность отловить ситуацию, когда индентификации недостаточно для работы сокета ROUTER, сокет будет сигнализировать ошибкой EHOSTUNREACH . ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 02:34 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Раз уж выяснилось ( 16666035 ), что Write/Writeln небезопасны в потоках, добавим безопасную функцию вывода строки в консоль. Снова расширим ZMQ_Utils.pas. Код: pascal 1. 2. Ну и для работы с текущей темой добавим еще пару функций: Код: pascal 1. 2. 3. 4. 5. ZMQ_Utils.pas Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 17:07 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Шаблон Балансировка Нагрузки Рассмотрим простой код (см. ниже). Видно, как сокет ROUTER коннектится к сокету REQ, а затем к сокету DEALER. Эти два примера следуют одной и той же логике, соотвествующей шаблону "Балансировка Нагрузки" . Этот шаблон был использован, когда мы впервые применили сокет ROUTER для маршрутизации сообщений, а не просто как средство для ответа на запрос. Шаблон "Балансировка Нагрузки" чрезвычайно популярен. Он решает главную проблему, когда простые алгоритмы последовательной круговой(round robin) маршртизации (которые обеспечивают PUSH и DEALER) становятся неэффективными. Такое случается, когда для выполнения задач, решаемых рабочими процессами, требуется разное время. Рассмотрим аналогию с почтовым офисом. Если у нас есть общая очередь клиентов перед прилавком, и некоторые покупают почтовые марки (быстрая, простая транзакция), а некоторые открывают новые счета (очень медленная, долгая транзакция), то становися очевидно, что покупатели почтовых марок застряли неоправданно долго. Если ваша архитектура обмена сообщениями окажется неудачной, ваши клиенты будут раздражены, точно так же, как в почтовом офисе. Для почтового офиса решением будет использование одной очереди так, чтобы даже когда один или два служащих застрянут на медленном задании, прочие клиенты будут обслуживаться другими служащими по принципу "первый пришел - первый обслужен". Одной из причин использования упрощенного подхода в сокетах PUSH и DEALER является производительность. Если вы прибываете в любой из главных аэропортов США, вы увидите несколько длинных очередей людей, ожидающих оформления процедуры иммиграции. Пограничники заранее отправляют людей к очереди конкретного служащего, а не заставляют всех стоять в одной общей очереди. Заставив людей заранее пройти полсотни метров, реально экономится 1-2 минут на обслуживание каждого пассажира. А так как проверка каждого паспорт занимает примерно одинаковое время, это обеспечивает что-то вроде справедливого обслуживания. Это и есть стратегия для сокетов PUSH и DEALER: распределить нагрузку заранее так, чтобы уменьшить расстояние для перемещения сообщения. То есть, метод обслуживания в аэропорту отличается от метода в почтовом офисе. Рассмотрим сценарий, когда рабочий процесс (сокетом DEALER или REQ) подключается к брокеру (к сокету ROUTER). Брокер знает, когда рабочий процесс готов к обслуживанию, и хранит список рабочих процессов. Поэтому он может всегда определить, какой из рабочих процессов наиболее редко использовался. Решение задачи очень просто: рабочий посылает сообщение "готов" после того, как стартует, а также всякий раз после выполнения очередной задачи. Брокер читает сообщения последовательно, одно за другим. Понятно, что всякий раз, когда сообщение прочитано, оно принято от рабочего процесса, использованного последним . И так как используется сокет ROUTER, в начале конверта сообщения содержится идентификатор, который позволяет оправить задачу рабочему процессу обратно. Цикл запрос - ответ необходим, так как задание отправляется с ответом, а любой ответ на задание отправляется как новый запрос. Следующий пример понятно демонстрирует описанное: Основной поток приложения создает "слушающий" сокет ROUTER. Затем создается 10 рабочих потоков, каждый поток создает сокет REQ и коннектится к сокету ROUTER основного потока. Каждый рабочий поток в цикле посылает сообщение "Я готов!", потом получает задание и выполняет его, пока не получит сообщение "Свободен!". Основной поток в цикле читает конверт сообщения из сокета ROUTER. Первый кадр сообщения - идентификатор потока, который его прислал. Значит, этот поток готов к выполнению работы, формируется обратный конверт и сообщение отправляется обратно. Если задача выполнялась больше 5 секунд, основной поток отправляет сообщение "Fire!". Код приложения, поясняющего идею балансировки нагрузки Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. На выходе сокета ROUTER формируется конверт со следующей структурой: № кадраДлинаСодержаниеОписание11002947ws5fw Идентификатор отправителя20Пустой кадр - разделитель37Hi Boss Сигнал готовности рабочего потока Видно, что кадры ответного сообщения формируется и отправляются параллельно со считыванием кадров входящего сообщения. Это сделано, чтобы лишний раз напомнить: сокет ROUTER - асинхронный. ИМХО, сообщения лучше читать кадр за кадром, логически отделяя процесс приема от процесса передачи. Вот так, например: Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. ... После запуска приложение усиленно что-то делает 5 секунд, потом каждый поток отчитывается о количестве задач, которые успел выполнит за эти 5 секунд Вывод программы: ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 17:36 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Брокер на сокете ROUTER , рабочие процессы - на сокете DEALER Везде, где можно использовать сокет REQ, можно использовать DEALER. При этом следует учесть отличия: - Сокет REQ всегда отправляет пустой кадр - разделитель конверта перед любыми кадрами с данными. Сокет DEALER так не делает. - Сокет REQ всегда отправляет одно сообщение перед тем, как принимает ответ. Сокет DEALER - полностью асинхронный. В нашем примере нет никакой разницы - синхронная работа или асинхронная, так как придерживаемся строгой последовательности "запрос - ответ". Это станет важно позднее, когда мы займемся вопросами восстановления после сбоев. Рассмотрим такой же пример, как и предыдущий, но заменим сокет REQ на сокет DEALER: Брокер на сокете ROUTER, рабочий процесс - на сокете Dealer Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. Этот код почти такой же, как предыдущий, за исключением того, что рабочие процессы используют сокет DEALER и читают и пишут пустой кадр перед кадром данных. Таким образом, сохранена совместимость с вариантом рабочих процессов на сокетах REQ. Следует помнить, что пустой кадр - разделитель конверта необходим для того, чтобы позволить в последовательной цепочке запросов использовать завершающий сокет REQ, который использует данный разделитель для отсечения обратного адреса конверта так, чтобы можно было в приложении обрабатывать кадры данных. Если сообщение никогда не будет передано к сокету REP, то для простоты мы можем с обоих сторон просто отбросить пустой кадр - разделитель. Что, в общем, часто и делается. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 18:44 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Брокер сообщений с балансировкой нагрузки Предыдущий пример был завершен лишь наполовину. Брокер может управлять множеством рабочих процессов с фиктивными запросами и ответами, но не может общаться с клиентами. Если мы добавим еще один frontend ROUTER сокет, который принимает запросы клиентов, и превратим наш пример в настоящий прокси, который может передавать сообщения от frontend к backend, мы получим полезный и готовый для практического многократного использования крошечный брокер сообщений с балансировкой нагрузки. Этот брокер делает следующее: - принимает соединения от множества клиентов; - принимает соединения от множества рабочих процессов; - принимает запросы от клиентов и хранит их в общей очереди; - отсылает эти запросы рабочим процессам в соответствии со схемой «Балансировка нагрузки»; - обратно принимает ответы от рабочих процессов; Исходник получается длинноват, но в нем стоит разобраться. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 06.10.2014, 21:22 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Вот он: Брокер с балансировкой нагрузки Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. Исходник избыточно комментирован, поэтому добавлю только пару замечаний. 1. Обратить внимание на цикл рабочего потока worker_thread_proc(): Код: pascal 1. 2. 3. 4. 5. 6. 7. Так как для связи между потоками используется протокол inproc, необходимо использовать общий контекст, который передается как параметр в процедуру потока. Так вот, когда основной поток завершается, выполняется закрытие контекста ZMQ: Код: pascal 1. Следовательно, обращение к сокетам контекста вызовет ошибку ETERM, что в данном случае считается признаком необходимости завершения основного цикла процедуры потока. 2. Основным источником задач в примере является процедура потока клиентов client_thread_proc(). Следовательно, при завершении приложения есть смысл дождаться завершения потоков клиентского слоя. Для этого при создании потоков слоя клиентов запоминаются дескрипторы потоков, а при завершении процедуры выполняется ожидание завершения всех потоков клиентов: Код: pascal 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 09.10.2014, 13:01 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
Немного поясню. В алгоритме две сложные вещи: а) упаковка сообщений в конверты при каждом чтении и записи; б) сам алгоритм балансировки нагрузки Рассмотрим весь путь сообщения схемы "Запрос - от клиента до рабочего процесса и обратно. В коде есть вызовы Код: pascal 1. 2. 3. 4. - это установка идентичности сокетов в форме набора случайных символов в строках. Такая форма используется исключительно для облегчения процесса отслеживания сообщений. В реальности, можно не задавать идентификацию самому, а позволить это сделать сокету ROUTER. Предположим, что идентификатором клиента будет строка "CLIENT", а рабочего - "WORKER". Предположим, что клиентское приложение посылает один кадр, содержащий "Hello". Сообщение, отсылаемое клиентом: № кадраДлинаСодержаниеОписание15Hello Кадр с данными Сокет REQ сам добавляет в начало пустой кадр - разделитель. Далее, сокет ROUTER добавляет идентификатор соединения. В итоге прокси читает уже адрес (идентификатор) клиента, пустой кадр и данные: Сообщение, которое читает прокси с фронтэнд сокета ROUTER: № кадраДлинаСодержаниеОписание15ClientИдентификатор клиента20Пустой кадр - разделитель35Hello Кадр с данными Брокер отправляет все это рабочему, предварив двумя кадрами: идентификаторо рабочего и разделителем. Сообщение, которое брокер отправляет в бэкэнд сокета ROUTER: № кадраДлинаСодержаниеОписание16WorkerИдентификатор рабочего20Пустой кадр - разделитель35ClientИдентификатор клиента40Пустой кадр - разделитель55Hello Кадр с данными Этот конверт распаковывается сначала бэкенд - сокетом ROUTER, который удаляет первый кадр и отправляет все сокету REQ рабочего. Потом сокет REQ рабочего удаляет пустой кадр, а остаток передает в приложение рабочего потока: Сообщение, которое получает рабочий: № кадраДлинаСодержаниеОписание15ClientИдентификатор клиента20Пустой кадр - разделитель35Hello Кадр с данными Рабочий сохраняет все части сообщения, что-то делает с данными и возвращает результат. На обратном пути сообщение проходит те же самые этапы, то есть, например, на бэкэнд сокет брокеру передается сообщение из пяти частей, а брокер отправляет в фронтенд сокет сообщение из трех частей, в итоге клиент получает сообщение из одной части. ~~~~~~~~~~~~~~~~~ Теперь по поводу алгоритма балансировки нагрузки. Со стороны и клиента, и рабочего используются сокеты REQ. Рабочий должен корректно получить, запомнить и правильно вернуть получаемые конверты . Алгоритм: - создается пул сокетов. Пул всегда содержит бэкэнд сокет (сторона рабочего), и, если есть хоть один свободный рабочий - фронтэнд сокет (сторона клиента). - пул активируется с бесконечным таймаутом; - если есть активность со стороны бэкэнда (рабочего), мы читаем либо сообщение "READY", либо идентификатор клиента, для которого рабочий отправил сообщение. В обоих случаях мы помещаем адрес рабочего (первый кадр конверта) в конец очереди свободных рабочих, а оставшуюся часть (если это ответ клиенту) отправляем через фронтенд сокет клиенту. - если есть активность со стороны фронтэнда (клиент), мы берем запрос клиента, извлекаем из очереди адрес очередного рабочего (который использовался последним), и отправляем запрос в бэкэнд (рабочему). В конверте теперь первый кадр - адрес рабочего, потом разделитель, потом все три части клиентского запроса. Данная схема легко расширяется. Например, в процессе работы работники могли бы запускать счетчики производительности для оценки собственного быстродействия и отчитываться брокеру о результатах. А брокер мог бы выбирать самого быстрого из работников, использованных последними. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 09.10.2014, 14:20 |
|
||
|
ZeroMQ - сокеты на стероидах, часть 3 (а для чего?).
|
|||
|---|---|---|---|
|
#18+
ZeroMQ, переходим на последнюю версию: 4.0.4 Здесь: 16562018 было сказано, что биндинг реализован для версий 2.* и 3.*. Хотя уже год как есть 4я версия. В которой реализованы такие интересные вещи, как новый протокол уровня передачи, могут использоваться криптографические библиотеки и аутентификация коннекта а также добавлен новый тип сокета - ZMQ_STREAM (для работы в качестве TCP клиента или сервера). Вот файлик, реализующий интерфейсы библиотеки libzmq.dll (используем его в uses вместо старого zmq.pas) : ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 13.10.2014, 23:45 |
|
||
|
|

start [/forum/topic.php?fid=58&startmsg=38763975&tid=2039957]: |
0ms |
get settings: |
5ms |
get forum list: |
17ms |
check forum access: |
3ms |
check topic access: |
3ms |
track hit: |
159ms |
get topic data: |
9ms |
get forum data: |
2ms |
get page messages: |
84ms |
get tp. blocked users: |
1ms |
| others: | 201ms |
| total: | 484ms |

| 0 / 0 |
