Статьи

Анатомия веб-приложения: как я создал RedditLater в Clojure

Я сделал RedditLater в прошлом году, чтобы люди могли публиковать сообщения в Reddit в заранее запланированное время. У него скромное использование; несколько сотен посетителей в день, с небольшой долей запланированных постов. В этой статье я напишу о том, как работает RedditLater, и почему я принял некоторые решения, которые я принял.

Архитектура

Я написал проект около года назад. Это размещено на единственном Heroku dyno. Я выбрал Heroku, потому что, эй, бесплатный хостинг. Таким образом, технические решения были приняты с учетом этих ограничений — хотя использование является скромным, среда хостинга довольно ограничена. Приложение должно было быть в состоянии доставлять свои запланированные сообщения (приблизительно) вовремя, и переживать случайный всплеск трафика из-за его публикации на Reddit.

RedditLater работает, раскручивая отдельный рабочий поток для отслеживания очереди сообщений, предназначенных для Reddit, который работает рядом с веб-сервером. Рабочий поток просто просматривает список сообщений в очереди и отправляет их в Reddit всякий раз, когда находит сообщение, где schedulenow

RedditLater был написан с использованием Clojure . Я выбрал Clojure главным образом потому, что в то время был в Clojure . Я до сих пор, но я был тогда тоже. Оглядываясь назад, я могу сказать, что это было хорошее решение. Clojure — это простой, функциональный язык с первоклассной поддержкой параллелизма. Это не совсем мейнстрим, но он достаточно популярен, чтобы иметь первоклассную поддержку на Heroku . RedditLater опирается на параллелизм, который Clojure делает настолько доступным для запуска параллельных задач на одном экземпляре Heroku, особенно в библиотеке Lamina и ее превосходных структурах очередей.

Для обеспечения сохранности сообщения и данные для входа в систему пользователя хранятся в базе данных Mongo, размещенной в MongoHQ , которая прекрасно работает с Heroku. Я использовал Mongo, потому что приложение не интенсивно использует базу данных, и потому, что Mongo прост в использовании, особенно из языка с литералом хэш-карты, как у Clojure.

(Я начал думать о Mongo как о хранилище данных, которое вы используете, пока вам не нужно принять решение о том, какое хранилище данных использовать.)

обзор

Приложение требует нескольких функций, которые можно разделить на независимые модули. Основную функциональность можно разделить между обработчиком запросов и рабочим. Обработчик запросов — это пользовательский интерфейс и интерфейс приложения, принимающий пользовательский ввод. Работник позаботится о том, чтобы отправлять вещи в Reddit.

Обработчик запросов

Вот обзор задач, которые веб-работник выполняет в обычном рабочем процессе, когда пользователь входит в систему и планирует публикацию:

  • Служить отрендеренному шаблону, перенаправленному по URL
  • Аутентифицировать пользователя с помощью Reddit через поддержку OAuth в Reddit
  • Храните учетные данные пользователя для будущих вызовов Reddit API в Монго
  • Сохраните нужный пост (включая запланированное время) в монго
  • Поместите сообщение в очередь работника для публикации

Обработчик запросов написан с использованием библиотеки Ring , фактического стандарта для веб-приложений Clojure, с маршрутизацией Compojure , а Enlive занимается рендерингом шаблонов. Я также использовал Middleman для макета пользовательского интерфейса и создания HTML-шаблонов, которые будут использоваться сive .

Здесь нет ничего слишком интересного, только эквиваленты Clojure некоторых действительно обыденных задач, с которыми может иметь дело любой разработчик. Более интересная часть — почтовая очередь. Одним из бонусных аспектов использования Clojure является то, что запускать механизм обработки запросов из вашего приложения легко и удобно. Это связано с тем, что параллелизм Clojure основан на потоках (в процессе), а Python, Ruby, PHP и т. Д. Используют многопроцессный параллелизм.

работник

С рабочей стороны ситуация намного проще:

  • Возьмите пост из очереди.
  • Получить последнюю версию этого поста из Монго.
  • Проверьте, пришло ли время отправить этот пост.
    • Если нет, поместите сообщение в конец очереди.
    • Если это так, попробуйте опубликовать. Если попытка не удалась, добавьте сообщение обратно в очередь.
  • Повторение.

Вот аннотированный пример того, как все это выглядит:

(Синтаксический праймер: myfunction(x, y)(myfunction x y)

 ;; Define a post queue
(def upcoming-post-queue (lamina/queue))

(defn enqueue-post
  "Enqueue a post in the post queue."
  [post]
  (lamina/enqueue upcoming-post-queue post))

(defn time-to-post?
  "Is schedule < now?"
  [post]
  (>= (get post :schedule) (helpers/now)))

(defn process-post
  "Grab a post from the queue. If it's time to post it, post it. If not, requeue."
  []
  (let [post @upcoming-post-queue] ; Blocks until a post is in the queue
    (if (time-to-post? post)
      (reddit-api/submit post) ; If so, submit the post with the reddit-api module
      (enqueue-post post)))    ; Otherwise, add the post to the queue
  (Thread/sleep 1000.)) ; Sleep for a second


;; Called by main on startup
(defn start-worker []
  (doall (repeatedly process-post)))

Если вы можете преодолеть скобки, вы увидите, как этот процесс упрощается по сравнению с решениями на таких языках, как Python ( Celery ) или Ruby ( Resque ). Оба из них требуют, чтобы вы запускали и управляли другим процессом (еще на 30 долларов в месяц на Heroku), и ни один из них не имеет столь простого API.

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

Вывод

И это все, что нужно сделать! Используя только эти простые инструменты, RedditLater успешно и непрерывно работает более года (со случайным исправлением ошибок). Конечно, есть много других способов разработки такого приложения, но я надеюсь, что вы сегодня немного узнали из дизайна и инструментов, которые я выбрал. Подробнее о том, как работает Redditlater, подробнее .