Thứ hai, 28/10/2013 | 00:00 GMT+7

Cách tối ưu hóa Unicorn worker trong ứng dụng Ruby on Rails


Giới thiệu Unicorn


Tín dụng: Github

Nếu bạn là một nhà phát triển Rails, chắc hẳn bạn đã nghe nói đến Unicorn , một server HTTP có thể xử lý nhiều yêu cầu cùng một lúc.

Unicorn sử dụng các quy trình phân nhánh để đạt được sự đồng thời. Vì các tiến trình được phân nhánh về cơ bản là bản sao của nhau, điều này nghĩa là ứng dụng Rails không cần phải an toàn cho stream .

Điều này thật tuyệt vì rất khó đảm bảo rằng mã của chính ta là an toàn cho chuỗi. Nếu ta không thể đảm bảo mã của ta là an toàn theo stream , thì các web server đồng thời như Puma và thậm chí các triển khai Ruby thay thế khai thác đồng thời và song song như JRubyRubinius sẽ không có vấn đề gì.

Do đó, Unicorn cung cấp cho các ứng dụng Rails của ta sự đồng thời ngay cả khi chúng không an toàn cho chuỗi. Tuy nhiên, điều này phải trả giá đắt. Các ứng dụng Rails chạy trên Unicorn có xu hướng ngốn nhiều bộ nhớ hơn. Nếu không chú ý đến việc tiêu thụ bộ nhớ của ứng dụng, bạn có thể thấy mình đang gặp phải một server cloud quá tải.

Trong bài này, ta sẽ tìm hiểu một vài cách để khai thác sự đồng thời của Unicorn, đồng thời kiểm soát mức tiêu thụ bộ nhớ.

Sử dụng Ruby 2.0!


Nếu bạn đang sử dụng Ruby 1.9, bạn nên xem xét nghiêm túc việc chuyển sang Ruby 2.0. Để hiểu tại sao, ta cần hiểu một chút về fork.

Forking và Copy-on-Write (CoW)


Khi một quy trình con được chia nhỏ, nó là bản sao giống hệt như quy trình mẹ. Tuy nhiên, không cần thực hiện sao chép bộ nhớ vật lý thực tế. Vì chúng là các bản sao chính xác, cả quy trình con và quy trình mẹ có thể chia sẻ cùng một bộ nhớ vật lý. Chỉ khi nào được viết - thì ta mới sao chép tiến trình con vào bộ nhớ vật lý.

Vậy điều này liên quan như thế nào đến Ruby 1.9 / 2.0 và Unicorn?

Nhớ lại Unicorn sử dụng forking. Về lý thuyết, hệ điều hành sẽ có thể tận dụng lợi thế của CoW. Thật không may, Ruby 1.9 không làm được điều này. Chính xác hơn, việc triển khai thu gom rác của Ruby 1.9 không làm được điều này. Đây là một version cực kỳ đơn giản - khi trình thu gom rác của Ruby 1.9 khởi động, việc ghi sẽ được thực hiện, do đó khiến CoW trở nên vô dụng.

Nếu không đi vào quá nhiều chi tiết, có thể nói rằng trình thu gom rác của Ruby 2.0 đã sửa lỗi này và bây giờ ta có thể khai thác CoW.

Điều chỉnh cấu hình của Unicorn


Có một số cài đặt mà ta có thể điều chỉnh trong config/unicorn.rb để tăng hiệu suất nhiều nhất có thể từ Unicorn.

worker_processes

Điều này đặt số lượng quy trình công nhân sẽ chạy . Điều quan trọng là phải biết một quá trình chiếm bao nhiêu bộ nhớ. Điều này là để bạn có thể ngân sách một cách an toàn số lượng công nhân, để không làm cạn kiệt bộ nhớ RAM của VPS của bạn.

timeout

Điều này nên được đặt ở một số nhỏ: thường là 15 đến 30 giây là một con số hợp lý. Cài đặt này đặt khoảng thời gian trước khi công nhân hết thời gian. Lý do bạn muốn đặt một con số tương đối thấp là để ngăn một yêu cầu kéo dài không cho các yêu cầu khác được xử lý.

preload_app

Điều này phải được đặt thành true . Đặt điều này thành true giảm thời gian khởi động để bắt đầu các quy trình của Unicorn worker. Điều này sử dụng CoW để tải trước ứng dụng trước khi tạo các quy trình công nhân khác. Tuy nhiên, có một gotcha lớn . Ta phải đặc biệt chú ý rằng mọi socket (chẳng hạn như kết nối database ) được đóng và mở lại đúng cách. Ta thực hiện việc này bằng cách sử dụng before_forkafter_fork .

Đây là một ví dụ:

before_fork do |server, worker|   # Disconnect since the database connection will not carry over   if defined? ActiveRecord::Base     ActiveRecord::Base.connection.disconnect!   end    if defined?(Resque)     Resque.redis.quit     Rails.logger.info('Disconnected from Redis')   end end  after_fork do |server, worker|   # Start up the database connection again in the worker   if defined?(ActiveRecord::Base)     ActiveRecord::Base.establish_connection   end    if defined?(Resque)     Resque.redis = ENV['REDIS_URI']     Rails.logger.info('Connected to Redis')   end end 

Trong ví dụ này, ta đảm bảo kết nối được đóng và mở lại khi công nhân được chia nhánh. Ngoài các kết nối database , ta cần đảm bảo các kết nối khác yêu cầu socket cũng được xử lý tương tự. Phần trên bao gồm cấu hình cho Resque .

Thuần hóa mức tiêu thụ bộ nhớ của công nhân Unicorn


Rõ ràng, đó không phải là tất cả cầu vồng và kỳ lân (ý định chơi chữ!). Nếu ứng dụng Rails của bạn bị rò rỉ bộ nhớ - Unicorn sẽ làm cho nó tồi tệ hơn.

Mỗi quy trình được phân nhánh này đều tiêu tốn bộ nhớ, vì chúng là bản sao của ứng dụng Rails. Do đó, mặc dù có nhiều nhân viên hơn nghĩa là ứng dụng của ta có thể xử lý nhiều yêu cầu đến hơn, nhưng ta bị ràng buộc bởi dung lượng RAM vật lý mà ta có trên hệ thống của bạn .

Ứng dụng Rails rất dễ bị rò rỉ bộ nhớ. Ngay cả khi ta quản lý để cắm tất cả các rò rỉ bộ nhớ, vẫn có bộ thu gom rác ít lý tưởng hơn để đối phó (tôi đang đề cập đến việc triển khai MRI).

Ứng dụng Rails bị rò rỉ bộ nhớ

Ở trên cho thấy một ứng dụng Rails đang chạy Unicorn bị rò rỉ bộ nhớ.

Theo thời gian, mức tiêu thụ bộ nhớ sẽ tiếp tục tăng lên. Sử dụng nhiều công nhân Unicorn sẽ chỉ đơn giản là tăng tốc độ tiêu thụ bộ nhớ, đến mức không còn RAM để dự phòng. Ứng dụng sau đó sẽ tạm dừng - dẫn đến nhiều user và khách hàng không hài lòng.

Điều quan trọng cần lưu ý là đây không phải là lỗi của Unicorn. Tuy nhiên, đây là vấn đề mà bạn sớm muộn gì cũng phải đối mặt.

Nhập Unicorn Worker Killer


Một trong những giải pháp dễ dàng nhất mà tôi đã gặp là viên ngọc sát thủ kỳ lân .

Từ README :

unicorn-worker-killer gem cung cấp khả năng tự động khởi động lại các công nhân Unicorn dựa trên

1) số lượng yêu cầu tối đa và
2) kích thước bộ nhớ xử lý (RSS), mà không ảnh hưởng đến bất kỳ yêu cầu nào.

Điều này sẽ cải thiện đáng kể sự ổn định của trang web bằng cách tránh cạn kiệt bộ nhớ không mong muốn tại các node ứng dụng.

Lưu ý tôi giả định bạn đã cài đặt và chạy Unicorn.

Bước 1:

Thêm unicorn-worker-killer vào Gem file . Đặt cái này bên dưới viên ngọc unicorn .

group :production do    gem 'unicorn'   gem 'unicorn-worker-killer' end 

Bước 2:

Chạy bundle install .

Bước 3:

Đến phần thú vị. Định vị và mở file config.ru của bạn.

# --- Start of unicorn worker killer code ---  if ENV['RAILS_ENV'] == 'production'    require 'unicorn/worker_killer'    max_request_min =  500   max_request_max =  600    # Max requests per worker   use Unicorn::WorkerKiller::MaxRequests, max_request_min, max_request_max    oom_min = (240) * (1024**2)   oom_max = (260) * (1024**2)    # Max memory size (RSS) per worker   use Unicorn::WorkerKiller::Oom, oom_min, oom_max end  # --- End of unicorn worker killer code ---  require ::File.expand_path('../config/environment',  __FILE__) run YourApp::Application 

Đầu tiên, ta kiểm tra xem ta đang ở trong môi trường production . Nếu vậy, ta sẽ tiếp tục và thực thi đoạn mã sau đó.

unicorn-worker-killer giết công nhân với 2 điều kiện: Yêu cầu tối đa và Bộ nhớ tối đa.

Yêu cầu tối đa

Trong ví dụ này, một nhân viên sẽ bị dừng nếu nó đã xử lý từ 500 đến 600 yêu cầu. Lưu ý đây là một phạm vi. Điều này giảm thiểu sự xuất hiện khi nhiều hơn 1 công nhân bị ngừng việc đồng thời.

Bộ nhớ tối đa

Ở đây, một nhân viên sẽ bị dừng nếu nó tiêu thụ từ 240 đến 260 MB bộ nhớ. Đây là một phạm vi vì lý do tương tự như trên.

Mỗi ứng dụng đều có yêu cầu bộ nhớ duy nhất. Bạn nên có một thước đo sơ bộ về mức tiêu thụ bộ nhớ của ứng dụng trong quá trình hoạt động bình thường. Bằng cách đó, bạn có thể đưa ra ước tính tốt hơn về mức tiêu thụ bộ nhớ tối thiểu và tối đa cho nhân viên của bạn .

Khi bạn đã cấu hình mọi thứ đúng cách, khi triển khai ứng dụng, bạn sẽ nhận thấy hành vi bộ nhớ ít thất thường hơn nhiều:

Ứng dụng Rails với Unicorn Worker Killer

Lưu ý các đường gấp khúc trong đồ thị. Đó là viên ngọc đang làm công việc của nó!

Kết luận

Unicorn cung cấp cho ứng dụng Rails của bạn một cách dễ dàng để đạt được sự đồng thời, cho dù nó có an toàn hay không. Tuy nhiên, đi kèm với đó là chi phí tiêu thụ RAM tăng lên. Cân bằng mức tiêu thụ RAM là hoàn toàn cần thiết cho sự ổn định và hiệu suất của ứng dụng của bạn.

Ta đã thấy 3 cách để điều chỉnh công nhân Unicorn của bạn để đạt hiệu suất tối đa:

  1. Sử dụng Ruby 2.0 cung cấp cho ta một trình thu gom rác được cải tiến nhiều cho phép ta khai thác ngữ nghĩa copy-on-write.

  2. Điều chỉnh các tùy chọn cấu hình khác nhau trong config/unicorn.rb .

  3. Sử dụng unicorn-worker-killer để giải quyết vấn đề một cách duyên dáng bằng cách giết và khởi động lại công nhân khi họ quá căng thẳng.

Tài nguyên


<div class = “author”> Người gửi: <a href=p>http://benjamintanweihao.github.io/[> Benjamin Tan </div>


Tags:

Các tin liên quan