Чому вчені не довіряють атомам? Тому що вони складають все!
Ви знайомі з тим відчуттям, коли ви розробляєте додаток на Rails, він виглядає блискуче і новенько, ви надзвичайно раді... і ось ви починаєте додавати нові функції. Можливо, ви хочете відправляти електронні листи, отримати дані з API або здійснити обробку зображень. І раптом ваш додаток починає працювати повільно. Дуже повільно. Ваші користувачі, ймовірно, вже нетерпляче тупають ногами, а ваш сервер починає потіти.
Не переживайте, друже, ви не одні. Це трапляється з усіма. Але не бійтеся! Є секретна зброя, яка може врятувати ситуацію: асинхронна обробка.
Що таке цей “Async”?
Уявіть, що ви в автозаправці. Ви замовляєте бургер, і замість того, щоб чекати, поки його приготують прямо там, ви платите і їдете до наступного вікна. Коли ви доїжджаєте туди, бах! Ваш бургер вже готовий. Ось що таке async — робити все одночасно, а не чекати, поки кожен крок завершиться, перш ніж рухатися далі.
Чому це важливо?
- Швидкість: Async дозволяє вашому додатку обробляти інші запити, поки він займається довготривалими завданнями. Це означає, що ваші користувачі отримують свої запити швидше, і вони будуть щасливіші. Щасливі користувачі = щасливий ви (а може, ще й підвищення зарплати!).
- Навантаження на ресурси: Ті довгі завдання можуть сильно навантажувати ресурси вашого сервера. Async допомагає зменшити це навантаження, щоб ваш сервер не зламався.
- Масштабованість: З async ваш додаток може обробляти більше запитів, не ламаючись. Це дуже важливо, особливо якщо ваш додаток стає популярним.
Давайте заглянемо в код!
Добре, давайте побачимо це в дії. Ми будемо отримувати погодні дані для кількох міст.
Старий спосіб (Sync):
Ruby
class CreateArticles < ActiveRecord::Migration[7.0]
def change
create_table :articles do |t|
t.string :title
t.text :body
t.references :author, null: false, foreign_key: { to_table: :users }
t.timestamps
end
end
end
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.text :body
t.references :article, null: false, foreign_key: true
t.references :user, null: false, foreign_key: { to_table: :users }
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :author
has_many :comments
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
end
# app/models/user.rb
class User < ApplicationRecord
has_many :articles
has_many :comments
end
class ArticlesController < ApplicationController
def index
start_time = Time.now
@articles = Article.includes(:author, :comments).order(created_at: :desc).limit(10)
begin
response = Faraday.get("https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY")
weather_data = JSON.parse(response.body)
@temperature = weather_data["main"]["temp"]
rescue Faraday::Error => e
Rails.logger.error "Error fetching weather data: #{e.message}"
@temperature = nil
end
end_time = Time.now
@elapsed_time_ms = ((end_time - start_time) * 1000).to_f.round(2)
render json: { articles: @articles, temperature: @temperature, elapsed_time_ms: @elapsed_time_ms }
end
end
Цей код отримує погоду для кожного міста один за одним. Це як стояти в черзі до одного касира, коли є 10 відкритих кас.
Не дуже ефективно, правда?
Асинхронний спосіб (набагато краще):
Ruby
class ArticlesController < ApplicationController
def index
start_time = Time.now
articles_task = Async do
Article.includes(:author, :comments).order(created_at: :desc).limit(10)
end
weather_task = Async do
begin
response = Faraday.get("https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY")
JSON.parse(response.body)["main"]["temp"]
rescue Faraday::Error => e
Rails.logger.error "Error fetching weather data: #{e.message}"
nil
end
end
@articles = articles_task.wait
@temperature = weather_task.wait
end_time = Time.now
@elapsed_time_ms = ((end_time - start_time) * 1000).to_f.round(2)
render json: { articles: @articles, temperature: @temperature, elapsed_time_ms: @elapsed_time_ms }
end
end
Давайте розберемо це:
Асинхронний I/O дозволяє програмі продовжувати виконання інших завдань, поки операції введення/виведення (I/O) тривають. Це досягається за допомогою не блокуючих системних викликів і використання циклів подій (event loops) для ефективного керування подіями введення/виведення.
Тепер давайте розглянемо, як працює цей асинхронний гем за лаштунками.
Ключові особливості гему async
- Фібри (Fibers): Гем
async
активно використовує фібри Ruby — легковажні потоки, які можуть бути призупинені та відновлені ефективно. Фібри дозволяють кооперативний багатозадачність (cooperative multitasking), де різні частини програми можуть передавати керування одна одній, максимізуючи використання ресурсів. Це дозволяє програмі переключатися між різними завданнями без накладних витрат традиційних потоків. - Цикл подій (Event Loop): Гем надає потужний цикл подій, який керує асинхронними операціями. Цикл подій слідкує за подіями введення/виведення (наприклад, коли дані стають доступними на сокеті або файл стає читабельним) і передає керування відповідним фібрам, коли події трапляються. Це дозволяє програмі ефективно обробляти кілька одночасних операцій введення/виведення без блокування.
- Шаблон реактора (Reactor Pattern): Гем
async
реалізує шаблон реактора, добре відомий патерн проектування для ефективної обробки паралельних подій. Шаблон реактора передбачає використання одного потоку, який слідкує за подіями та передає їх відповідним обробникам. Такий підхід мінімізує накладні витрати на керування потоками та підвищує масштабованість. - Високорівневі абстракції (High-Level Abstractions): Гем надає високорівневі абстракції для популярних асинхронних операцій, таких як TCP/UDP сокети, HTTP клієнти та файлові операції. Ці абстракції спрощують процес розробки, надаючи зручний інтерфейс для виконання асинхронних операцій.
Важливі зауваження:
- Обробка помилок (Error Handling): Завжди переконуйтеся, що у вас є хороша обробка помилок.
- Обмеження за кількістю запитів (Rate Limits): Будьте уважні до обмежень API. Ви не хочете, щоб ваші запити були заблоковані.
- Тестування (Testing): Тщательно тестуйте ваш асинхронний код, щоб переконатися, що все працює як треба.
Ще один жарт про програмістів:
Чому програміст приніс банан на співбесіду?
Тому що вони чули, що робота вимагає сильної бананової руки!
Підсумки
Асинхронна обробка — потужний інструмент, який може зробити ваші Rails додатки швидшими, ефективнішими і масштабованішими. Тому вперед, впроваджуйте асинхронний підхід і спостерігайте, як ваш код "літає"!
Дисклеймер (Disclaimer): Це спрощений приклад. Реальні впровадження можуть бути складнішими, але основні концепції залишаються незмінними.
Перекладено з: Async-ing Around with Ruby on Rails: A Dad Joke Guide