Pull to refresh

Фоновая обработка видео в Ruby on Rails

Reading time4 min
Views2.7K
Допустим у вас есть сайт, на котором пользователи загружают видео, и это видео надо конвертировать в flv формат.
Делать это непосредственно после загрузки, в текущем рабочем потоке не хорошо, т.к. потоков этих ограниченное число, и при большой нагрузке сайт будет недоступен.
Будет гораздо лучше, если обработкой этих видео-файлов займется демон.

Логика работы демона может быть следующей — демон выбирает необработанный объект из БД (например, используя флаг state, и сортируя по дате), после конвертирует соответствующий видео файл и обновляет запись в бд, выставляя статус в «обработано» (или «ошибка обработки»).
Для написания собственного демона можно воспользоваться библиотекой daemons (как ни странно). Демон будет состоять из 2-х частей: скрипт для управления фоновым процессом, и скрипт с логикой демона. В нашем случае это будут файлы scripts/video_converter_control и lib/video_converter.

scripts/video_converter_control:
require 'rubygems'
require 'daemons'

options = {:app_name => "video_converter",
           :ARGV => ARGV,
           :dir_mode => :script,
           :dir => '../tmp/pids',
           :log_output => false,
           :multiple => false,
           :ontop => false,
           :mode => :load,
           :backtrace => false,
           :monitor => false
# daemons предлагает возможность мониторить состояние процесса (опция monitor), но я предпочитаю использовать monit
}

Daemons.run(File.join(File.dirname(__FILE__), '../lib/video_converter.rb'), options)


lib/video_converter:
require File.dirname(__FILE__) + '/../config/boot'
require RAILS_ROOT + "/config/environment"

SLEEP_TIME = 1

logger = RAILS_DEFAULT_LOGGER

loop do
  video = Video.find(:first, :conditions => {:state => Video::PENDING_STATE}, :order => "id ASC")
  success = video.process
  if success.empty?
    video.update_attribute(:state, Video::CONVERTED_STATE)
  else
    logger.info "VideoConverter: video #{video.id} converting failure"
    video.update_attribute(:state, Video::FAILURE_STATE)
  end
  sleep(SLEEP_TIME)
end


Работать с демоном можно следующим образом:
estarter@ny $ ./script/video_converter_control status
video_converter: no instances running
estarter@ny $ ./script/video_converter_control start
estarter@ny $ ./script/video_converter_control status
video_converter: running [pid 5957]
estarter@ny $ ./script/video_converter_control stop
estarter@ny $ ./script/video_converter_control status
video_converter: no instances running


Теперь нарастим «мощь» конвертера, т.к. можно и нужно обрабатывать несколько видео-файлов одновременно. Самый простой вариант — запускать несколько демонов, однако при этом встает проблема синхронизации, т.к. нельзя допустить, что бы несколько процессов одновременно обрабатывали один файл. Это можно решить например «блокируя» запись в бд, выставляя соответствующий флаг (Video::PROCESSING_STATE).
Но если обработка происходит только на одном сервере, можно поступить умнее — делать потоке в основном демоне.
Для этих целей подходит плагин spawn, который позволяет просто создавать и работать с потоками. Теперь демон будет выбирать за раз несколько объектов, и обрабатывать их в отдельных потоках.

lib/video_converter:
require File.dirname(__FILE__) + '/../config/boot'
require RAILS_ROOT + "/config/environment"
include Spawn

THREAD_COUNT = 5 # максимальное количество одновременно обрабатываемых объектов, максимальное количество потоков
SLEEP_TIME = 1

logger = RAILS_DEFAULT_LOGGER
spawn_ids = []

loop do
  videos = Video.find(:all, :conditions => {:state => Video::PENDING_STATE}, :order => "id ASC", :limit => THREAD_COUNT)
  videos.each_with_index do |video, idx|
    logger.info "VideoConverter: convert #{videos.size} videos"
    spawn_ids[idx] = spawn do
      success = video.process
      if success.empty?
        video.update_attribute(:state, Video::CONVERTED_STATE)
      else
        logger.info "VideoConverter: video #{video.id} converting failure"
        video.update_attribute(:state, Video::FAILURE_STATE)
      end
    end
  end
  wait(spawn_ids)
  sleep(SLEEP_TIME)
end


Данных подход можно использовать для целого ряда ресурсоемких задач.

Оригинал — в моем блоге.
Tags:
Hubs:
+14
Comments17

Articles