Асинхронний квест з Ruby on Rails: Посібник з жартами для тата

pic

Чому вчені не довіряють атомам? Тому що вони складають все!

Ви знайомі з тим відчуттям, коли ви розробляєте додаток на 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

Leave a Reply

Your email address will not be published. Required fields are marked *