Я сделал RedditLater в прошлом году, чтобы люди могли публиковать сообщения в Reddit в заранее запланированное время. У него скромное использование; несколько сотен посетителей в день, с небольшой долей запланированных постов. В этой статье я напишу о том, как работает RedditLater, и почему я принял некоторые решения, которые я принял.
Архитектура
Я написал проект около года назад. Это размещено на единственном Heroku dyno. Я выбрал Heroku, потому что, эй, бесплатный хостинг. Таким образом, технические решения были приняты с учетом этих ограничений — хотя использование является скромным, среда хостинга довольно ограничена. Приложение должно было быть в состоянии доставлять свои запланированные сообщения (приблизительно) вовремя, и переживать случайный всплеск трафика из-за его публикации на Reddit.
RedditLater работает, раскручивая отдельный рабочий поток для отслеживания очереди сообщений, предназначенных для Reddit, который работает рядом с веб-сервером. Рабочий поток просто просматривает список сообщений в очереди и отправляет их в Reddit всякий раз, когда находит сообщение, где schedule
now
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, подробнее .