Bài viết trước của chúng tôi trong loạt bài này đã xác định rằng Solid Queue là một lựa chọn tuyệt vời nếu bạn cần một hệ thống xử lý các công việc nền. Nó giảm thiểu sự phụ thuộc bên ngoài — không cần Redis! — bằng cách lưu trữ tất cả công việc trong cơ sở dữ liệu của bạn. Mặc dù vậy, nó vẫn có hiệu suất đáng kinh ngạc.
Nhưng chỉ có hiệu suất thôi thì chưa đủ đối với một hệ thống công việc nền sẵn sàng cho sản xuất. Các nhà phát triển Rails đã mong đợi rất nhiều trong những năm qua. Chúng tôi không chỉ muốn xếp các công việc vào hàng đợi để chạy ở chế độ nền. Chúng tôi muốn lên lịch công việc, chạy chúng theo lịch định kỳ và thậm chí chúng tôi có thể muốn giới hạn số lượng công việc có thể chạy đồng thời. Chúng tôi muốn có nhiều tính năng hơn!
Thật ngạc nhiên, Solid Queue cung cấp tất cả các tính năng đó. Hãy cùng tìm hiểu sâu hơn về Hàng đợi vững chắc và tìm hiểu xem điều đó có thể thực hiện được như thế nào!
Lên lịch công việc với hàng đợi vững chắc cho Ruby on Rails
Đầu tiên, đã đến lúc tóm tắt lại một chút. Hàng đợi vững chắc sử dụng cơ sở dữ liệu của bạn — và chỉ cơ sở dữ liệu của bạn — để lưu trữ dữ liệu công việc. Mọi thứ nó làm đều được hỗ trợ bởi bảng cơ sở dữ liệu này hoặc bảng khác. Lập kế hoạch công việc - nghĩa là chỉ định công việc sẽ thực hiện tại một thời điểm cụ thể nào đó trong tương lai - cũng không khác. Mọi công việc đã lên lịch đều được lưu trữ trong solid_queue_scheduled_executions .
Bảng này gần giống với bảng solid_queue_ready_executions cái bàn. Sự khác biệt duy nhất là việc bổ sung scheduled_at cột cho chúng ta biết khi nào một công việc đã lên lịch sẽ được thực thi. Hãy xác nhận điều đó bằng cách xem điều gì sẽ xảy ra khi chúng ta lên lịch công việc.
Không có bất ngờ ở đó. Hàng đợi vững chắc thêm một hàng mới vào solid_queue_scheduled_executions bảng chứa dữ liệu mà chúng ta mong đợi. Nhưng làm thế nào để chúng ta đi từ một bản ghi đã tồn tại đến việc thực sự thực hiện một công việc vào đúng thời điểm?
Chúng tôi cần một quy trình liên tục thăm dò solid_queue_scheduled_executions cái bàn. Quá trình đó được gọi là Bộ điều phối và nó chịu trách nhiệm thực hiện các công việc đã lên lịch đúng thời hạn. Nó bắt đầu khi Hàng đợi vững chắc khởi động - không cần cấu hình bổ sung. Tuy nhiên, nếu cần, bạn chỉ có thể bắt đầu quá trình điều phối bằng cách chạy Solid Queue với cấu hình cụ thể.
Trong trường hợp bạn thắc mắc về cách giám sát quy trình của Bộ điều phối, thì đó là trách nhiệm của Supervisor được đặt tên phù hợp . Nó theo dõi mọi quy trình đang chạy trong Solid Queue, bao gồm các quy trình công nhân và Bộ điều phối.
Vậy thực chất Dispatcher hoạt động như thế nào? Nó định nghĩa một poll phương thức được gọi trong vòng lặp để liên tục kiểm tra các công việc đã lên lịch. Mã thăm dò được trải rộng trên nhiều lớp và mô-đun, nhưng ở dạng đơn giản hóa nhiều, nó trông như thế này:
Truy vấn để truy xuất các lần thực thi theo lịch trình 'sẵn sàng' rất đơn giản.
Vì vậy, mọi công việc đã lên lịch với scheduled_at trong quá khứ đã sẵn sàng để được gửi đi. Như chúng tôi đã trình bày trong phần một của loạt bài này, khi Solid Queue gửi một công việc, nó sẽ tạo ra một ReadyExecution ghi lại và hủy ScheduledExecution tương ứng ghi lại. ReadyExecution bản ghi sau đó được các quy trình công nhân thông thường chọn và công việc tương ứng sẽ được thực hiện.
Cho đến nay, rất tốt. Công việc theo lịch trình thực sự không phức tạp! Hãy xem xét một thứ phức tạp hơn:các nhiệm vụ định kỳ.
Tác vụ định kỳ
Các tác vụ định kỳ là một tính năng thường được yêu cầu đối với các bộ xử lý công việc ở chế độ nền. Nói một cách đơn giản, chúng là những công việc nền nên chạy theo lịch trình định kỳ. Chúng tương tự như các công việc Cron ở chỗ bạn xác định lịch trình (chẳng hạn như cứ năm phút một lần, mỗi ngày vào buổi trưa, v.v.) về thời điểm công việc sẽ diễn ra.
Trong Hàng đợi vững chắc, bạn định cấu hình công việc định kỳ của mình bằng cách sử dụng config/recurring.yml tập tin. Ví dụ:nếu chúng tôi muốn chạy CleanupData công việc hàng ngày vào buổi trưa, đây là cách chúng tôi sẽ làm.
Solid Queue sử dụng Fugit để phân tích các biểu thức lịch trình, đó là lý do tại sao các lịch trình mà con người có thể đọc được chẳng hạn như 'hàng ngày vào buổi trưa' được cho phép. Khi sử dụng các tác vụ đã lên lịch, bạn xác định loại công việc sẽ được chạy và mọi đối số công việc. Các tác vụ định kỳ SolidQueue tuyệt vời ReadMe cung cấp thêm chi tiết. Chúng ta ở đây để tìm hiểu cách thức hoạt động của nó, vì vậy hãy cùng tìm hiểu sâu hơn.
Các tác vụ định kỳ được biểu thị bằng RecurringTask mô hình được hỗ trợ bởi solid_queue_recurring_tasks tương ứng cái bàn. Các cột trong đó tương ứng với các trường có sẵn trong tệp cấu hình.
Khi bạn khởi động SolidQueue, các bản ghi tác vụ định kỳ sẽ được tạo theo tệp cấu hình tác vụ định kỳ của bạn. Để tạo công việc vào đúng thời điểm, một lần nữa chúng ta cần một quy trình mới — lần này được gọi là Trình lập lịch biểu. Trình lập lịch trình là anh chị em của Trình điều phối mà chúng ta đã biết. Nó hoạt động gần giống như vậy:Một quy trình mới được tạo ra khi Hàng đợi rắn bắt đầu và quy trình này chạy một vòng lặp vô tận. Sự khác biệt giữa Bộ lập lịch và Bộ điều phối là những gì xảy ra trong vòng lặp đó. Nơi Người điều phối truy vấn solid_queue_scheduled_executions bảng, Bộ lập lịch truy vấn solid_queue_recurring_tasks - và sắp xếp công việc vào đúng thời điểm. Vậy, làm cách nào để Người lập lịch biết chính xác thời điểm thích hợp và khi nào nên sắp xếp công việc phù hợp?
Để trả lời câu hỏi đó, chúng ta phải kiểm tra việc thực hiện một cách chặt chẽ. Lớp trình lập lịch trình tạo một RecurringSchedule mới đối tượng xác định schedule phương pháp. Phương thức đó được gọi liên tục cho mỗi tác vụ đã lên lịch. Đây là phiên bản đơn giản hóa:
Hãy gỡ rối mã này. Hàng đợi vững chắc sử dụng Concurrent::ScheduledTask (từ thư viện Ruby đồng thời) để tạo ra một chuỗi mới. Chuỗi đó được lên lịch để chạy vào thời điểm được chỉ định bởi lịch trình của tác vụ định kỳ. Khi luồng đó chạy, trước tiên nó sẽ sinh ra một luồng khác một cách đệ quy để lên lịch cho tác vụ định kỳ tiếp theo. Sau đó, nó sẽ xếp công việc đã lên lịch 'hiện tại' vào hàng đợi.
Hãy xem ví dụ về một nhiệm vụ định kỳ đơn giản để xử lý mọi việc.
Nếu chúng ta bắt đầu Solid Queue lúc 8:30, các biến trong phương thức lịch trình sẽ được gán các giá trị sau. Không phải nguyên văn, nhớ bạn. Chúng tôi đang đơn giản hóa rất nhiều ở đây.
Vì vậy, chuỗi nền của chúng tôi dự kiến sẽ chạy sau 30 phút kể từ bây giờ, tức là lúc 9:00. Khi thời gian đó trôi qua, luồng nền sẽ được thực thi. Nó chạy thread_task.enqueue(at: 9:00) — một phiên bản của CleanupData được xếp hàng đợi để thực thi. Nó cũng tự gọi đệ quy thông qua thread_schedule.schedule . Vì bây giờ là 9:00 nên các biến của lời gọi này đã thay đổi.
Vì vậy, luồng nền được lên lịch chạy lại lúc 10:00 và chu kỳ tiếp tục. Bạn có thể thắc mắc điều gì sẽ xảy ra nếu luồng lập lịch bị tắt, chẳng hạn như trong quá trình triển khai lại hoặc sự cố hệ thống. Điều đó sẽ không làm đảo lộn lịch trình của bạn chứ? May mắn thay, câu trả lời là không. Lịch trình Cron là tĩnh. Một biểu thức như 'Mỗi giờ' luôn phân giải thành 10:00, 11:00, 12:00, v.v., bất kể khi nào Hàng đợi liền khối bắt đầu. Bất kỳ sự gián đoạn nào đối với chuỗi lịch trình đều không thay đổi được điều đó.
Dưới đây là một số chi tiết triển khai khác cần lưu ý. Đầu tiên, mô hình lên lịch cho lần xuất hiện tiếp theo của một nhiệm vụ định kỳ trước khi thực hiện nó được lấy cảm hứng từ GoodJob. Thứ hai, RecurringTask.enqueue không tạo Job mới và ReadyExecution ghi lại như bạn mong đợi. Thay vào đó, nó tạo ra một bản ghi khác, cụ thể là RecurringExecution .
Bản ghi này chỉ nhằm mục đích tránh thực hiện các công việc định kỳ nhiều lần. Nó có chỉ mục trên task_key và run_at với những ràng buộc duy nhất để phục vụ mục đích đó. Một RecurringTask chỉ được xếp hàng nếu không có RecurringExecution trước cho cùng một thời gian và cùng một công việc.
Những người đọc chú ý sẽ nhận thấy rằng đoạn mã này chỉ ra một hạn chế trong Hàng đợi vững chắc. Nghĩa là, nếu bạn không sử dụng Solid Queue làm phụ trợ để chạy các tác vụ kiểu cron của mình — vâng, bạn có thể làm điều đó — Solid Queue không thể đảm bảo rằng các công việc định kỳ chỉ được xếp vào hàng đợi một lần. Nếu bạn rơi vào tình huống như vậy thì bạn nên nhận thức được điều đó.
Bạn cũng có thể thắc mắc điều gì sẽ xảy ra nếu quy trình Lập lịch trình bị chết hoặc bị hủy — ví dụ:trong quá trình triển khai. Vì các lần lặp lại được quản lý bởi một luồng, nên lịch trình ngắt luồng sẽ không bị hủy phải không? May mắn thay, câu trả lời là không.
Kiểm soát đồng thời
Chúng ta hãy xem xét một tính năng cuối cùng của Solid Queue, đó là điều khiển đồng thời. Đôi khi, bạn muốn giới hạn số lượng công việc thuộc một loại nhất định có thể chạy đồng thời. Bạn có thể làm như vậy bằng cách sử dụng Hàng đợi vững chắc với limits_concurrency .
Ở đây, chúng tôi yêu cầu SolidQueue chạy tối đa một phiên bản của MyJob cho mỗi người dùng. Hãy kiểm tra cấu hình chi tiết hơn.
to:Số lượng công việc tối đa bạn muốn chạy đồng thời.key:Một đối số bắt buộc để chỉ định những công việc nào nên được giới hạn cùng nhau. Trong ví dụ của chúng tôi, các công việc có cùng ID người dùng bị giới hạn ở một lần thực thi đồng thời. Bạn có thể sử dụng bất kỳ đối số công việc nào dưới dạngkey, nhưng các hằng số như chuỗi hoặc ký hiệu cũng được phép.duration:Thời gian tối đa mà Solid Queue có thể đảm bảo tính đồng thời sau khi một công việc được đưa vào hàng đợi. Nếu công việc của bạn chạy lâu hơn thế, các biện pháp kiểm soát đồng thời sẽ không được áp dụng và các công việc có thể chồng chéo. Chúng ta sẽ tìm hiểu lý do tại sao sau!group:Bạn có thể sử dụng tùy chọn này để hạn chế sự đồng thời giữa các loại công việc khác nhau.
Nếu bạn muốn tìm hiểu thêm, tôi sẽ giới thiệu cho bạn tài liệu về kiểm soát đồng thời. Kiểm soát đồng thời dễ dàng là tính năng phức tạp nhất của Solid Queue. Nếu các nhiệm vụ theo lịch trình chưa khiến bạn quay cuồng thì việc tìm hiểu cách hoạt động của tính năng này chắc chắn sẽ làm được điều đó.
Hãy bắt đầu với những điều cơ bản. Giống như các tính năng Solid Queue khác, điều khiển đồng thời được hỗ trợ bởi nhiều mô hình khác nhau và các bảng cơ sở dữ liệu tương ứng của chúng. Hai thứ bạn cần đặc biệt lưu ý là Semaphore và BlockedExecution .
Hãy cùng xem Semaphore đầu tiên. Như tên cho thấy, đây là cách triển khai mẫu đèn hiệu đếm. Bất cứ khi nào Solid Queue xếp một công việc với limits_concurrency , đầu tiên nó cố gắng lấy khóa semaphore dựa trên khóa đồng thời. Khóa đồng thời này dựa trên các đối số được truyền cho limits_concurrency , cụ thể là lớp công việc, khóa và tên nhóm - nếu có được cung cấp. Nếu có sẵn semaphore thì công việc sẽ được xếp vào hàng đợi. Nếu không, BlockedExecution thay vào đó, bản ghi sẽ được tạo.
Semaphores có value để hỗ trợ nhiều công việc đồng thời. Bạn có thể coi nó là dung lượng còn lại của semaphore. Việc thu được một semaphore có nghĩa là giảm giá trị đó và giải phóng nó có nghĩa là tăng giá trị đó. Một semaphore được coi là không khả dụng khi giá trị của nó bằng 0. Hãy xem một ví dụ về cách hoạt động của cơ chế khóa đối với một công việc đơn giản.
Hãy xem điều gì sẽ xảy ra nếu chúng ta cố gắng xếp công việc này vào hàng đợi nhiều lần liên tiếp.
- Phiên bản đầu tiên của
MyJobđang bị xếp hàng. Chưa có semaphore nên một semaphore được tạo ra. Giá trị ban đầu của nó làlimit - 1. Vì giới hạn của bạn là 3 nên giá trị ban đầu của semaphore là 2. - Phiên bản thứ hai của
MyJobđang bị xếp hàng. Hàng đợi vững chắc cố gắng lấy được khóa cho công việc đó. Công việc có thể được xếp vào hàng đợi vì giá trị là hai, lớn hơn 0. Giá trị của semaphore giảm xuống 1. - Trường hợp thứ ba của công việc của chúng ta đang được xếp hàng đợi. Chúng tôi lặp lại thủ tục tương tự như trước đây. Giá trị của semaphore hiện bằng 0.
- Phiên bản thứ tư của
MyJobđang bị xếp hàng. Việc thu thập semaphore không thành công vì giá trị của nó hiện bằng 0. MộtBlockedExecutionbản ghi được tạo cho công việc. - Bản đầu tiên của công việc của chúng ta đã kết thúc. Khi hoàn tất, nó giải phóng semaphore, do đó giá trị semaphore một lần nữa là 1.
- Khi hoàn tất, phiên bản công việc đầu tiên cũng gọi một phương thức để giải phóng mọi công việc bị chặn.
- Phiên bản thứ tư của
MyJobđược thả ra và một lần nữa cố gắng lấy được khóa. Giá trị semaphore là một, do đó khóa có thể được lấy và công việc bị chặn được xếp hàng đợi. Giá trị semaphore hiện bằng 0.
Mã để giải phóng một đèn hiệu khi công việc kết thúc rất đơn giản.
Còn một chi tiết nữa mà chúng tôi chưa đề cập đến. Tại sao các ẩn dụ có ngày hết hạn và tại sao chúng ta cần đặt thời lượng khi sử dụng limits_concurrency ?
Hãy xem xét điều gì sẽ xảy ra khi một công việc gặp sự cố mà không giải phóng đèn hiệu của nó — ví dụ:khi một công nhân đang xử lý công việc đó chết. Trừ khi chúng ta thêm một số cơ chế để dọn sạch các ẩn dụ, khóa được giữ bởi công việc đó sẽ được giữ lại mãi mãi. Trong trường hợp xấu nhất, điều này sẽ vĩnh viễn chặn việc xử lý các công việc khác.
Semaphores có thời hạn sử dụng tương ứng với khoảng thời gian được đưa ra trong định nghĩa công việc để tránh tình trạng đó. Nếu một semaphore hết hạn - điều này xảy ra nếu không có công việc nào được xếp hàng - thì semaphore sẽ bị hủy. Chúng tôi đã biết quy trình chịu trách nhiệm cho việc đó — đó là bạn của chúng tôi, Người điều phối . Nó khởi tạo ConcurrencyMaintenance class, thực hiện hai việc:
- Đầu tiên, nó loại bỏ mọi ngữ nghĩa đã hết hạn.
- Thứ hai, nó sẽ kiểm tra xem có công việc nào bị chặn hay không và giải phóng chúng.
Các công việc được phát hành lần lượt nên giới hạn đồng thời sẽ vẫn được giữ nguyên. Tuy nhiên, hãy xem xét điều gì sẽ xảy ra nếu công việc của bạn kéo dài hơn thời lượng nhất định. Trong trường hợp đó, semaphore sẽ được dọn sạch, mặc dù công việc vẫn chạy. Nếu một công việc khác được xếp vào hàng đợi, những công việc đó sẽ chồng chéo lên nhau.
Giám sát hàng đợi vững chắc cho Rails bằng AppSignal
Như chúng tôi đã thiết lập, Solid Queue có thể làm được rất nhiều việc. Tuy nhiên, với tất cả các bộ phận chuyển động này, việc giám sát trở nên quan trọng. May mắn thay, AppSignal cung cấp hỗ trợ tích hợp cho Solid Queue, với các trang tổng quan được tạo sẵn về thời gian thực hiện công việc, thông lượng và tỷ lệ thất bại. Chỉ cần cài đặt AppSignal trong ứng dụng Rails của bạn và bạn đã sẵn sàng.
AppSignal sẽ tự động phát hiện việc bạn sử dụng Solid Queue và tạo một bảng thông tin công việc đang hoạt động chứa biểu đồ cho các số liệu quan trọng, chẳng hạn như tỷ lệ lỗi và thông lượng.

Nếu bạn từng thấy những công việc hoạt động sai — vì chúng chạy chậm hoặc có quá nhiều lỗi — hãy chỉ định trạng thái và người được giao cho chúng để giải quyết vấn đề một cách hiệu quả.

Rõ ràng, bạn không cần phải nhìn vào bảng thông tin cả ngày để tìm hiểu xem có vấn đề gì không. Cảnh báo AppSignal luôn hỗ trợ bạn. Chỉ cần tạo một cảnh báo mới cho các số liệu công việc, chẳng hạn như tỷ lệ thất bại và thời gian thực hiện công việc, là bạn đã sẵn sàng.

Solid Queue thật tuyệt vời khi thêm khả năng xử lý công việc mạnh mẽ vào ứng dụng của bạn mà không gặp rắc rối. AppSignal cũng làm điều tương tự khi theo dõi!
Kết thúc
Chúng tôi đã tìm hiểu rất nhiều nền tảng trong quá trình khám phá các tính năng nâng cao của Solid Queue. Từ các công việc được lên lịch cho đến các chuỗi phụ thuộc phức tạp, mỗi tính năng đều được xây dựng trên nền tảng vững chắc mà chúng ta đã thảo luận trong phần một. Như chúng ta đã thấy, việc xây dựng một chương trình phụ trợ xử lý công việc không phải là điều dễ dàng. Nhưng bằng cách đi sâu vào mã nguồn Solid Queue và hoạt động của nó, chúng tôi đã hiểu và đánh giá cao những thách thức liên quan.
Trong mọi trường hợp, Solid Queue là một bổ sung tuyệt vời cho hệ sinh thái Rails, nhờ thiết kế cơ sở dữ liệu tuyệt vời và khả năng phối hợp quy trình. Nó cung cấp những công cụ bạn cần trong khi vẫn duy trì lời hứa cốt lõi:tính đơn giản và độ tin cậy, không phụ thuộc vào bên ngoài.
Chúc bạn viết mã vui vẻ!