Статьи

Гигантское убийство с бобовым деревом

beanstalkd Если вы когда-нибудь баловались сервис-ориентированной архитектурой (SOA) или даже читали некоторые интересные статьи об этом, вы, вероятно, сталкивались с термином «очередь сообщений».

Действительно краткое объяснение очереди сообщений, или MQ, состоит в том, что она позволяет сервисам в вашей архитектуре применять подход «запускай и забывай» для взаимодействия с другими сервисами. Помещая очередь в систему, можно выполнять операции, не чувствительные ко времени, на досуге служб, которые заботятся о них, не обращая внимания на технологии или язык программирования.

В качестве примера рассмотрим функцию «отправить другу» в приложении Job Board. После того, как пользователь заполнил форму и нажал «Отправить», действительно ли мы хотим, чтобы отправка электронного письма другу пошла в нашу заявку на работу?

Фоновые задания

Распространенным подходом к этой проблеме является использование фоновых рабочих, таких как Resque или Sidekiq . Для рассматриваемой проблемы это хорошо и несколько более подходит. Единственная проблема, с которой я столкнулся:

  1. Логика отправки электронной почты живет в нашем приложении, которое не обязательно заботится об электронной почте.
  2. Я, вероятно, продублирую процесс связи с моим SMTP-сервером через несколько приложений в архитектуре.
  3. Фоновые работники слишком много знают об их происхождении, то есть, из каких моделей они пришли, к чему они могут получить доступ (весь мой стек приложений).

Если ваша архитектура развивается, возможно, стоит подумать о переносе некоторых фоновых работников в MQ. Для меня MQs просто работают. Вы добавляете некоторые данные, и демон или приложение, которое заботится об этом сообщении, получает его через некоторое время и воздействует на него. В то же время создатель сосредоточился на своем основном бизнесе.

По мере развития архитектуры и добавления новых сервисов некоторым из этих сервисов может потребоваться также отправлять электронную почту. На этом этапе вы установили четкий, надежный метод отправки электронной почты. Вы просто отбрасываете некоторые данные в очередь электронной почты, и она будет отправлена.

Beanstalkd

Надеюсь, теперь вы понимаете, почему MQ такие классные. Есть несколько доступных MQ с открытым исходным кодом, наиболее заметными из которых являются RabbitMQ (есть хорошая статья на RubySource с подробностями ) и мой личный фаворит, и то, что мы будем использовать сегодня Beanstalkd .

Начало работы с Beanstalkd действительно не может быть проще. В OSX вы хотите использовать homebrew ( brew install beanstalkd ) или для Debian linux, вы можете использовать sudo apt-get install beanstalkd . Кажется, это довольно хорошо поддерживается большинством менеджеров пакетов на разных платформах. Вы можете увидеть подробности в документации по загрузке Beanstalkd .

После установки вы можете открыть терминал и выполнить beanstalkd . Это запустит экземпляр Beanstalkd, используя порт по умолчанию 11300 на локальном 11300 на переднем плане. Не всегда идеально для запуска на переднем плане, поэтому моя типичная команда выглядит примерно так:

 beanstalkd -b ~/beanstore & 

Это просто сохраняет данные очереди в хранилище в каталоге ~/beanstore а не просто в памяти, и запускает процесс в фоновом режиме (амперсанд). Для разработки эти настройки хороши. Когда дело доходит до производства, я бы посоветовал вам прочитать документы, относящиеся к инструменту администратора, который поставляется вместе с Beanstalkd.

Beanstalkd Линго

У Beanstalkd есть хороший словарь для описания основных игроков и операций. Давайте пройдемся по ним.

трубы

Трубка — это пространство имен для ваших сообщений. Экземпляр Beantstalkd может иметь несколько трубок. На ванильной загрузке Beanstalkd будет иметь одну трубу с именем default .

Идея состоит в том, что вы хотите, чтобы определенный процесс прослушивал сообщения, поступающие в определенную трубку. Как уже упоминалось, трубки просто действуют как пространства имен для потребителей очереди.

работы

Рабочие места — это то, что мы помещаем в трубу. Для меня характерно помещать JSON в трубу и маршаллировать это на другом конце.

Beanstalkd на самом деле не заботится о содержании работы, поэтому такие вещи, как YAML, простой текст или Thrift будут в порядке.

В нормальной, счастливой работе пути задания имеют 2 состояния:

  1. Готов — Ожидание обработки.
  2. Зарезервировано — обрабатывается

Если все идет хорошо, работа удаляется. Если есть проблема с заданием, скажем, наш SMTP-сервер не работает, задание переводится в состояние «похоронен». Он останется «похороненным» до тех пор, пока трубка не будет «выбита». Это просто переведет задание обратно в состояние «Готово». Итак, с поддержкой SMTP, мы пинаем трубку, и мир продолжает вращаться.

Еще одно состояние, которое мы не рассмотрели, — «Задержка». Это просто означает, что задание не переходит в состояние «Готов», пока не истечет некоторый заранее заданный интервал. Лично я не очень много использовал это состояние, поэтому не буду больше его освещать, кроме как упомяну, что оно существует.

ОМ НОМ НОМ

Теперь у нас работает Beanstalkd на наших блоках разработки, мы хотим получить несколько заданий в очереди. Чтобы достичь этого, мое обычное оружие выбора — драгоценный камень Beaneater . Получить работу в трубе так же просто, как:

 require 'beaneater' require 'json' beanstalk = Beaneater::Pool.new(['localhost:11300']) tube = beanstalkd.tubes['my-tube'] job = {some: 'key', value: 'object'}.to_json tube.put job 

И это все. Теперь мы подошли к интересному, потребляя трубу и все работы, которые там живут.

Я большой поклонник процесса демона, обрабатывающего это. Если трубы начнут переполняться, мы сможем увеличить количество демонов, чтобы помочь избежать отставания в работе. Конечно, мы также можем убить их по мере необходимости.

До сих пор я использовал драгоценный камень Dante для упаковки сценариев в демоны. Это казалось немного легче, чем у Daemon Kit, и мне нравится, чтобы мои демоны не раздувались. Для меня преимущество использования Dante над чем-то вроде ruby script/my_mailer_script.rb — это не что иное, как Dante дает вам возможность генерировать файл идентификатора процесса (PID) из коробки. С этим я могу контролировать демонов с помощью monit .

Beaneater предоставляет действительно хороший API для использования рабочих мест двумя способами. Первый — это пошаговое ручное выполнение процесса резервирования задания, работы с ним, а затем удаление, если оно выполнено правильно, или сохранение в случае возникновения исключения. Это выглядит примерно так:

 beanstalkd.tubes.watch!('my-tube') loop do job = beanstalk.tubes.reserve begin # ... process the job job.delete rescue Exception => e job.bury end end 

Здесь стоит упомянуть пару вещей. Да, я использую бесконечный цикл, и reserve метод на трубе будет фактически сидеть и ждать, пока задание будет «Готово», зарезервировать его и продолжить.

Beaneater предоставляет лучший интерфейс для длительных задач, и вышеперечисленное можно просто сжать в:

 beanstalkd.jobs.register('my-tube') do |job| # ... process the job end beanstalkd.jobs.process! 

Этот метод оборачивает поведение (хотя и в гораздо лучшем виде) предыдущего примера, резервируя, обрабатывая, затем удаляя или хороня в зависимости от результата.

Нет волшебных бобов

Красота Beanstalkd заключается в его абсолютной простоте. На самом деле я бы не хотел углубляться в это как введение. С точки зрения быстрого запуска, это не сложнее, чем любое из фоновых рабочих решений, обсуждавшихся ранее.

Если честно, имеет смысл быть прагматичным в принятии вами MQ. Resque, Sidekiq и т. Д. Имеют свое место и работают очень хорошо, но Beanstalkd решает еще несколько проблем, а именно, взаимодействие между службами, которые могут или не могут быть написаны на Ruby (доступны клиенты .NET для Beanstalkd).

На самом деле, все это совершенно не зависит от языка. Коммуникация с beanstalkd через шею — через собственный протокол по TCP. Gem Beaneater, как вы, вероятно, знаете, абстрагирует весь этот протокольный материал в хорошо упакованный API для нас. Можно с уверенностью сказать, что я буду опираться на драгоценный камень Beaneater при использовании Beanstalkd в течение некоторого времени.

Если бы у меня был какой-либо совет по проектированию / составлению потребителей труб, как можно больше придерживайтесь принципа единой ответственности (SRP) . Придет время, когда вам придется пнуть похороненную работу. Если это задание выполняет запись в базу данных и отправляет электронное письмо, что произойдет, если отправка электронного письма прекратится? Воспроизведение указанного сообщения приведет к дублированию записи базы данных. Разделив обработку задания на мельчайшие разумные обязанности, тем меньше вам придется беспокоиться о выполнении дублирующих действий.

Я действительно призываю вас также обратить внимание на Beanstalkd по мере роста архитектуры вашего приложения. Исходя из личного опыта, я обнаружил, что запустить его легко, просто управлять и обслуживать, а клиент ruby ​​через Beaneater — один из лучших интерфейсов, которые я использовал.