При разработке приложения Ruby on Rails могут возникнуть такие задачи приложения, которые должны выполняться асинхронно. Обработка данных, массовая рассылка электронной почты, взаимодействие с внешними API и другие подобные задачи могут выполняться асинхронно в виде фоновых задач. Использование фоновых задач поможет повысить производительность вашего приложения за счет разгрузки потенциально ресурсоемких задач в фоновую очередь с освобождением первоначального цикла запрос/ответ.
Sidekiq — одна из наиболее широко используемых инфраструктур фоновых задач, которые можно реализовать в приложении Rails. Она основана на системе хранения пар ключ-значение в оперативной памяти Redis, отличающейся гибкостью и высокой производительностью. Sidekiq использует Redis как хранилище для управления задачами, чтобы обрабатывать тысячи задач в секунду.
В этом обучающем руководстве мы добавим Redis и Sidekiq в существующее приложение Rails. Мы создадим набор рабочих классов Sidekiq и методов для обработки:
После завершения работы мы получим демонстрационное приложение, использующее рабочих и задания для асинхронной обработки задач. Это обучающее руководство даст хорошую основу для добавления системы рабочих и заданий в ваше собственное приложение.
Для данного обучающего руководства вам потребуется следующее:
ufw
. Указания по настройке можно найти в обучающем руководстве Начальная настройка сервера Ubuntu 18.04.Нашим первым шагом будет клонирование репозитория rails-bootstrap из учетной записи DigitalOcean Community на GitHub. Этот репозиторий содержит код установки, описанный в обучающем руководстве Добавление Bootstrap в приложение Ruby on Rails, где объясняется процедура добавления Bootstrap в существующий проект Rails 5.
Клонируйте репозиторий в директорию с именем rails-sidekiq
:
- git clone https://github.com/do-community/rails-bootstrap.git rails-sidekiq
Перейдите в директорию rails-sidekiq
:
- cd rails-sidekiq
Для работы с кодом необходимо предварительно установить зависимости проекта, перечисленные в файле Gemfile. Также вам потребуется добавить в проект sidekiq gem для работы с Sidekiq и Redis.
Откройте файл проекта Gemfile для редактирования, используя nano
или другой предпочитаемый редактор:
- nano Gemfile
Добавьте зависимость в любое место в списке основных зависимостей проекта (над зависимостями разработки):
. . .
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false
gem 'sidekiq', '~>6.0.0'
group :development, :test do
. . .
Сохраните и закройте файл после добавления зависимости.
Используйте для установки зависимостей следующую команду:
- bundle install
В результатах вы увидите, что зависимость redis
также установлена как требование для sidekiq
.
Далее мы установим зависимости Yarn. Поскольку данный проект Rails 5 был модифицирован для обслуживания ресурсов с пакетом webpack, его зависимостями JavaScript теперь управляет Yarn. Это означает, что необходимо установить и проверить зависимости, указанные в файле проекта package.json
.
Используйте команду yarn install
для установки этих зависимостей:
- yarn install
Затем проведите миграцию базы данных:
- rails db:migrate
После завершения миграции вы можете протестировать приложение, чтобы убедиться, что оно работает ожидаемым образом. Запустите сервер в контексте локального пакета, используя следующую команду, если вы работаете на локальном компьютере:
- bundle exec rails s
Если вы работаете на сервере разработки, вы можете запустить приложение с помощью следующей команды:
- bundle exec rails s --binding=your_server_ip
Перейдите на адрес localhost:3000
или http://your_server_ip:3000
. Вы увидите следующую начальную страницу:
Чтобы создать новую акулу, нажмите кнопку Get Shark Info, после чего откроется путь sharks/index
:
Чтобы убедиться в работе приложения, добавим в него примеры данных. Нажмите New Shark. Вам будет предложено ввести имя пользователя (sammy) и пароль (shark) в связи с параметрами аутентификации проекта.
На странице New Shark введите Great White в поле Name и Scary в поле Facts:
Нажмите кнопку Create Shark для создания акулы. Когда акула будет создана, вы можете закрыть сервер, нажав CTRL+C
.
Вы установили необходимые зависимости для вашего проекта и протестировали его функционал. Теперь вы можете внести некоторые изменения в приложение Rails для работы с вашими ресурсами по находящимся под угрозой видам акул.
Чтобы работать с нашими ресурсами по находящимся под угрозой видам акул, мы добавим в приложение новую модель и контроллер, который будет определять, как информация по находящимся под угрозой видам акул будет выводиться пользователям. Наша конечная цель — дать пользователям возможность выгружать большие пакеты информации о находящихся под угрозой видах акул без блокировки функционала приложения и удалять эту информацию, когда она им больше не нужна.
Прежде всего, мы создадим модель Endangered
для наших находящихся под угрозой акул. Мы добавим в таблицу нашей базы данных поля строк для названий акул и категорий Международного союза охраны природы (МСОП), определяющих опасность для каждого из видов акул.
Структура нашей модели будет соответствовать столбцам в файле CSV, который мы будем использовать для пакетной выгрузки данных. Этот файл находится в директории db
, и вы можете проверить его содержимое с помощью следующей команды:
- cat db/sharks.csv
Файл содержит список из 73 видов акул, находящихся под угрозой, и их статусов в МСОП: vu — опасность, en — угроза, cr — критическая угроза.
Наша модель Endangered
коррелирует с этими данными, позволяя создавать новые экземпляры Endangered
из этого файла CSV. Создайте модель с помощью следующей команды:
- rails generate model Endangered name:string iucn:string
Сгенерируйте контроллер Endangered
с помощью действия index
:
- rails generate controller endangered index
Это даст нам исходную точку для создания функций нашего приложения, хотя нам понадобится добавить собственные методы в файл контроллера, сгенерированный для нас Rails.
Откройте этот файл:
- nano app/controllers/endangered_controller.rb
Rails предоставляет нам каркас, который мы можем начать заполнять.
Прежде всего нам нужно определить, какие маршруты нам потребуются для работы с нашими данными. Благодаря команде generate controller
у нас имеется метод index
, с которого мы можем начать. Он коррелирует с представлением index
, где мы предоставим пользователям возможность выгрузки акул, находящихся под угрозой.
Однако нам также нужны возможности и для случаев, когда пользователи уже выгрузили списки акул; в этом случае им не потребуется функция выгрузки. Нам потребуется какой-то способ, чтобы оценить количество уже существующих экземпляров класса Endangered
, поскольку наличие нескольких экземпляров означает, что пакетная выгрузка уже выполнена.
Для начала создадим частный
метод set_endangered
, который будет получать каждый экземпляр класса Endangered
из базы данных. Добавьте в файл следующий код:
class EndangeredController < ApplicationController
before_action :set_endangered, only: [:index, :data]
def index
end
private
def set_endangered
@endangered = Endangered.all
end
end
Обратите внимание, что фильтр before_action
обеспечивает установку значения @endangered
только для маршрутов index
и data
, где мы обрабатываем данные по находящимся под угрозой видам акул.
Добавьте следующий код в метод index
, чтобы определить правильный путь для пользователей, посещающих эту часть приложения:
class EndangeredController < ApplicationController
before_action :set_endangered, only: [:index, :data]
def index
if @endangered.length > 0
redirect_to endangered_data_path
else
render 'index'
end
end
. . .
Если существует более 0 экземпляров нашего класса Endangered
, мы перенаправляем пользователей на маршрут data
, где они могут просматривать созданную ими информацию об акулах. В ином случае они перейдут к представлению index
.
Под методом index
добавьте метод data
, который будет коррелровать с представлением data
:
. . .
def index
if @endangered.length > 0
redirect_to endangered_data_path
else
render 'index'
end
end
def data
end
. . .
Затем мы добавим метод для обработки самой процедуры выгрузки данных. Мы назовем этот метод upload
. Он будет вызывать класс рабочего Sidekiq и метод для выполнения выгрузки данных из файла CSV. На следующем шаге мы создадим определение для этого рабочего класса AddEndangeredWorker
.
Пока что добавьте в файл следующий код для вызова рабочего Sidekiq, который будет выполнять выгрузку:
. . .
def data
end
def upload
csv_file = File.join Rails.root, 'db', 'sharks.csv'
AddEndangeredWorker.perform_async(csv_file)
redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
end
. . .
Вызывая метод perform_async
для класса AddEndangeredWorker
с файлом CSV в качестве аргумента, этот код обеспечивает передачу в Redis данных об акулах и задания выгрузки. Заданные нами рабочие Sidekiq будут отслеживать очередь заданий и реагировать при появлении новых заданий.
После вызова perform_async
наш метод upload
выполняет перенаправление на путь data
, где пользователи смогут просматривать выгруженных акул.
Далее мы добавим метод destroy
для уничтожения данных. Добавьте следующий код после метода upload
:
. . .
def upload
csv_file = File.join Rails.root, 'db', 'sharks.csv'
AddEndangeredWorker.perform_async(csv_file)
redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
end
def destroy
RemoveEndangeredWorker.perform_async
redirect_to root_path
end
. . .
Как и метод upload
, наш метод destroy
включает вызов perform_async
для класса RemoveEndangeredWorker
, еще одного создаваемого нами рабочего Sidekiq. После вызова этот метод перенаправляет пользователей в корневую директорию приложения.
Итоговый файл будет выглядеть примерно так:
class EndangeredController < ApplicationController
before_action :set_endangered, only: [:index, :data]
def index
if @endangered.length > 0
redirect_to endangered_data_path
else
render 'index'
end
end
def data
end
def upload
csv_file = File.join Rails.root, 'db', 'sharks.csv'
AddEndangeredWorker.perform_async(csv_file)
redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
end
def destroy
RemoveEndangeredWorker.perform_async
redirect_to root_path
end
private
def set_endangered
@endangered = Endangered.all
end
end
Сохраните и закройте файл после завершения редактирования.
В заключение настройки маршрутов приложения мы изменим код в файле config/routes.rb
, где хранятся декларации наших маршрутов.
Откройте этот файл:
- nano config/routes.rb
Сейчас файл выглядит следующим образом:
Rails.application.routes.draw do
get 'endangered/index'
get 'home/index'
resources :sharks do
resources :posts
end
root 'home#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Нам нужно будет обновить файл, чтобы добавить в него определенные в нашем контроллере маршруты: data
, upload
и destroy
. Маршрут data
будет соответствовать запросу GET на получение данных об акулах, а маршруты upload
и destroy
будут соответствовать запросам POST на выгрузку и уничтожение этих данных.
Добавьте в файл следующий код для определения этих маршрутов:
Rails.application.routes.draw do
get 'endangered/index'
get 'endangered/data', to: 'endangered#data'
post 'endangered/upload', to: 'endangered#upload'
post 'endangered/destroy', to: 'endangered#destroy'
get 'home/index'
resources :sharks do
resources :posts
end
root 'home#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Сохраните и закройте файл после завершения редактирования.
Теперь у нас есть модель Endangered
и контроллер, и мы можем перейти к определению рабочих классов Sidekiq.
Мы вызываем методы perform_async
для рабочих Sidekiq в нашем контроллере, однако нам все еще нужно создать самих рабочих.
Для начала создайте для рабочих директорию workers
:
- mkdir app/workers
Откройте файл для рабочего AddEndangeredWorker
:
- nano app/workers/add_endangered_worker.rb
В этом файле мы добавим код, который позволит нам работать с данными в нашем файле CSV. Вначале добавьте код в файл, который будет создавать класс, включите в него библиотеку Ruby CSV и убедитесь, что этот класс функционирует как рабочий Sidekiq:
class AddEndangeredWorker
require 'csv'
include Sidekiq::Worker
sidekiq_options retry: false
end
Также мы добавим опцию retry: false
, чтобы Sidekiq не пытался повторить выгрузку в случае ошибки.
Далее добавьте код для функции perform
:
class AddEndangeredWorker
require 'csv'
include Sidekiq::Worker
sidekiq_options retry: false
def perform(csv_file)
CSV.foreach(csv_file, headers: true) do |shark|
Endangered.create(name: shark[0], iucn: shark[1])
end
end
end
Метод perform
получает аргументы от метода perform_async
, определенного в контроллере, поэтому очень важно обеспечить согласованность значений аргументов. Здесь мы передаем файл csv_file
и определенную в контроллере переменную, а также используем метод foreach
из библиотеки CSV для чтения значений в файле. Установка headers: true
для этого цикла обеспечивает обработку первой строки в файле как строки заголовков.
Затем блок считывает значения из файла в столбцы, заданные для нашей модели Endangered
: name
и iucn
. При выполнении этого цикла будут созданы экземпляры Endangered
для каждой из записей в нашем файле CSV.
После завершения редактирования сохраните и закройте файл.
Далее мы создадим рабочего для обработки удаления этих данных. Откройте файл для класса RemoveEndangeredWorker
:
- nano app/workers/remove_endangered_worker.rb
Добавьте код для определения класса и убедитесь, что он использует библиотеку CSV и функционирует как рабочий Sidekiq:
class RemoveEndangeredWorker
include Sidekiq::Worker
sidekiq_options retry: false
end
Затем добавьте метод perform
для обработки уничтожения данных о находящихся под угрозой акулах:
class RemoveEndangeredWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform
Endangered.destroy_all
end
end
Метод perform
вызывает destroy_all
для класса Endangered
, в результате чего все экземпляры этого класса удаляются из базы данных.
Сохраните и закройте файл после завершения редактирования.
Мы настроили рабочих и теперь можем перейти к созданию макета для представлений endangered
и шаблонов для представлений index
и data
, чтобы пользователи могли выгружать и просматривать находящихся под угрозой акул.
Чтобы пользователи смогли использовать свою информацию о находящихся под угрозой акулах, нам нужно обеспечить две вещи: макет представлений, определенных в контроллере endangered
, и шаблоны для представлений index
и data
.
Сейчас наше приложение использует общий макет app/views/layouts/application.html.erb
, элемент навигации и макет для представлений sharks
. Макет приложения проверяет наличие блока контента, что позволяет загружать другие макеты в зависимости от того, с какой частью приложения взаимодействует пользователь: для главного
указателя
они увидят один макет, а для представлений отдельных акул — другой.
Мы можем изменть назначение макета sharks
для представлений endangered
, поскольку этот формат также подойдет для вывода массива данных об акулах.
Скопируйте файл макета sharks
для создания макета endangered
:
- cp app/views/layouts/sharks.html.erb app/views/layouts/endangered.html.erb
Далее мы создадим шаблоны для представлений index
и data
.
Откройте шаблон index
:
- nano app/views/endangered/index.html.erb
Удалите базовый код и добавьте вместо него следующий код, который будет выводить пользователям информацию о находящихся под угрозой видах и предоставлять им возможность выгрузки информации о находящихся под угрозой акулах:
<p id="notice"><%= notice %></p>
<h1>Endangered Sharks</h1>
<p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
<br>
<%= form_tag endangered_upload_path do %>
<%= submit_tag "Import Endangered Sharks" %>
<% end %>
<br>
<%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
Тег form_tag
позволяет выгружать данные, указывая действие post для маршрута endangered_upload_path
, заданного нами ранее для выгружаемых файлов. Кнопка отправки, созданная с помощью тега submit_tag
, предлагает пользователям выполнить «Импорт находящихся под угрозой акул»
.
В дополнение к этому коду мы указываем информацию о кодах МСОП, чтобы пользователи могли интерпретировать данные, которые они видят.
Сохраните и закройте файл после завершения редактирования.
Откройте файл для представления data
:
- nano app/views/endangered/data.html.erb
Добавьте следующий код, который добавляет таблицу с данными о находящихся под угрозой видах акул:
<p id="notice"><%= notice %></p>
<h1>Endangered Sharks</h1>
<p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
<div class="table-responsive">
<table class="table table-striped table-dark">
<thead>
<tr>
<th>Name</th>
<th>IUCN Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @endangered.each do |shark| %>
<tr>
<td><%= shark.name %></td>
<td><%= shark.iucn %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<br>
<%= form_tag endangered_destroy_path do %>
<%= submit_tag "Delete Endangered Sharks" %>
<% end %>
<br>
<%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
Этот код содержит коды состояния МСОП и таблицу Bootstrap для выводимых данных. Выполняя цикл с переменной @endangered
, мы выведем в таблицу названия и статусы МСОП для всех акул.
Под таблицей располагается еще один набор тегов form_tags
и submit_tags
, которые используют метод post для пути destroy
, предлагая пользователям возможность «Удалить находящихся под угрозой акул»
.
Сохраните и закройте файл после завершения редактирования.
В заключение мы внесем изменение в представление index
, связанное с нашим контроллером home
. Возможно вы помните, что это представление было задано как корневое для приложения в config/routes.rb
.
Откройте этот файл для редактирования:
- nano app/views/home/index.html.erb
Найдите столбец в строке Sharks are ancient
:
. . .
<div class="col-lg-6">
<h3>Sharks are ancient</h3>
<p>There is evidence to suggest that sharks lived up to 400 million years ago.
</p>
</div>
</div>
</div>
Добавьте в файл следующий код:
. . .
<div class="col-lg-6">
<h3>Sharks are ancient and SOME are in danger</h3>
<p>There is evidence to suggest that sharks lived up to 400 million years ago. Without our help, some could disappear soon.</p>
<p><%= button_to 'Which Sharks Are in Danger?', endangered_index_path, :method => :get, :class => "btn btn-primary btn-sm"%>
</p>
</div>
</div>
</div>
Мы добавили призыв к действию для пользователей, желающих узнать больше о находящихся под угрозой акулах. Вначале мы разместили привлекающее внимание сообщение, а затем добавили помощник button_to
, который отправляет запрос GET на наш маршрут endangered
index
, предоставляя пользователям доступ к этой части приложения. Там они смогут выгружать и просматривать данные о находящихся под угрозой акулах.
Сохраните и закройте файл после завершения редактирования.
Мы разместили код и теперь можем запустить приложение и выгрузить несколько акул!
Перед запуском приложения нам нужно провести миграцию базы данных и запустить Sidekiq для активации наших рабочих. Redis уже должен работать на сервере, но для точности можно это проверить. Когда мы выполним эти задачи, мы будем готовы начинать тестирование приложения.
Проверьте работу Redis:
- systemctl status redis
Результат будет выглядеть следующим образом:
Output● redis-server.service - Advanced key-value store
Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2019-11-12 20:37:13 UTC; 1 weeks 0 days ago
Затем проведите миграцию базы данных:
- rails db:migrate
Теперь вы можете запустить Sidekiq в контексте текущего проекта с помощью команды bundle exec sidekiq
:
- bundle exec sidekiq
Вы увидите следующий вывод, указывающий, что Sidekiq готов к обработке заданий:
Output
m,
`$b
.ss, $$: .,d$
`$$P,d$P' .,md$P"'
,$$$$$b/md$$$P^'
.d$$$$$$/$$$P'
$$^' `"/$$$' ____ _ _ _ _
$: ,$$: / ___|(_) __| | ___| | _(_) __ _
`b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
$$: ___) | | (_| | __/ <| | (_| |
$$ |____/|_|\__,_|\___|_|\_\_|\__, |
.d$$ |_|
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: See LICENSE and the LGPL-3.0 for licensing details.
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Booting Sidekiq 6.0.3 with redis options {:id=>"Sidekiq-server-PID-17621", :url=>nil}
2019-11-19T21:43:00.543Z pid=17621 tid=gpiqiesdl INFO: Starting processing, hit Ctrl-C to stop
Откройте второе окно терминала, перейдите в директорию rails-sidekiq
и запустите свой сервер приложений.
Если вы запускаете приложение на локальном компьютере, используйте следующую команду:
- bundle exec rails s
Если вы работаете с сервером разработки, запустите следующую команду:
- bundle exec rails s --binding=your_server_ip
Откройте в браузере адрес localhost:3000
или http://your_server_ip:3000
. Вы увидите следующую начальную страницу:
Нажмите кнопку Which Sharks Are in Danger?. Поскольку вы еще не выгрузили акул, находящихся под угрозой вымирания, сейчас откроется экран endangered
index
:
Нажмите Import Endangered Sharks для импорта акул. Вы увидите сообщение о состоянии, где будет указано, что акулы были импортированы:
Также вы увидите начало импорта. Обновите страницу, чтобы увидеть таблицу целиком:
Благодаря Sidekiq пакетная выгрузка акул, находящихся под угрозой вымирания, была проведена успешно без блокировки браузера и без помех для работы других приложений.
Нажмите кнопку Home внизу страницы, чтобы вернуться на главную страницу приложения:
Далее нажмите Which Sharks Are in Danger? еще раз. Поскольку вы уже выгрузили акул, после этого откроется представление data
.
Для тестирования функции удаления нажмите кнопку Delete Endangered Sharks под таблицей. Вы снова должны быть перенаправлены на главную страницу приложения. При нажатии Which Sharks Are in Danger? вы вернетесь к представлению index
, где сможете снова выгрузить акул:
Теперь ваше приложение работает с рабочими Sidekiq, которые готовы обрабатывать задания и обеспечивать высокое качество обслуживания для пользователей вашего приложения.
Мы создали работающее приложение Rails с активированным Sidekiq, позволяющее разгружать ресурсоемкие операции в очередь заданий под управлением Sidekiq при поддержке Redis. Это позволит повысить скорость и функциональность разрабатываемого сайта.
Если вы хотите узнать больше о Sidekiq, начните с документации.
Дополнительную информацию о Redis можно найти в нашей библиотеке ресурсов по Redis. Дополнительную информацию о запуске управляемого кластера Redis в инфраструктуре DigitalOcean можно найти в документации по продукту.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!