Tổng quan về Sucker Punch
Sucker Punch là thư viện xử lý bất đồng bộ cho Ruby, được xây dựng trên nền tảng concurrent-ruby và chịu ảnh hưởng đáng kể từ Sidekiq cùng girl_friday. Thư viện này empower các nhà phát triển triển khai các tác vụ nền một cách hiệu quả, giúp cải thiện đáng kể tốc độ phản hồi của ứng dụng và trải nghiệm người dùng. Bài viết này sẽ đi sâu vào các tính năng nâng cao của Sucker Punch, bao gồm thực thi trì hoãn, công việc lồng nhau và xử lý ngoại lệ tùy chỉnh.
1. Thực thi trì hoãn - Kiểm soát chính xác thời điểm chạy công việc
Trong nhiều kịch bản thực tế, chúng ta cần trì hoãn việc thực thi một công việc đến một thời điểm cụ thể trong tương lai thay vì chạy ngay lập tức. Sucker Punch cung cấp cơ chế linh hoạt để đáp ứng yêu cầu này.
Để implement tính năng trì hoãn, chúng ta có thể sử dụng phương thức async_in với tham số thời gian tính bằng giây:
class PaymentProcessor
include SuckerPunch::Job
def perform(order_id, amount)
# Xử lý thanh toán
process_payment(order_id, amount)
end
end
# Thực thi sau 5 phút
PaymentProcessor.async_in(300, order_id, amount)
# Thực thi sau 1 giờ
PaymentProcessor.async_in(3600, order_id, amount)
Cơ chế này đặc biệt hữu ích trong các trường hợp như gửi email xác nhận sau khi đơn hàng được tạo, xử lý thanh toán định kỳ, hoặc dọn dẹp dữ liệu tạm thời sau một khoảng thời gian nhất định.
2. Công việc lồng nhau - Xây dựng luồng công việc phức tạp
Khi ứng dụng phát triển, chúng ta thường cần xây dựng các luồng công việc phụ thuộc lẫn nhau. Tính năng lồng nhau cho phép một công việc kích hoạt các công việc khác, tạo ra chuỗi xử lý liên hoàn.
class OrderFulfillment
include SuckerPunch::Job
def perform(order_id)
order = Order.find(order_id)
# Bước 1: Xác nhận tồn kho
inventory_result = verify_inventory(order.items)
if inventory_result[:available]
# Bước 2: Gọi job tính phí vận chuyển
ShippingCalculator.async(order_id, inventory_result[:weight])
# Bước 3: Gọi job gửi notification
NotificationJob.async(order_id, :order_confirmed)
else
# Bước 4: Xử lý khi hết hàng
OutOfStockHandler.async(order_id)
end
end
end
class ShippingCalculator
include SuckerPunch::Job
def perform(order_id, weight)
# Tính toán phí vận chuyển
shipping_cost = calculate_shipping(weight)
# Cập nhật đơn hàng
Order.update(order_id, shipping_cost: shipping_cost)
end
end
Bằng cách chia nhỏ logic thành các job riêng biệt, code trở nên dễ bảo trì hơn. Mỗi job chỉ phụ trách một nhiệm vụ cụ thể, tuân theo nguyên tắc Single Responsibility.
3. Xử lý ngoại lệ tùy chỉnh
Trong môi trường production, việc xử lý lỗi một cách chủ động là vô cùng quan trọng. Sucker Punch cho phép developer customize hoàn toàn behavior khi có exception xảy ra.
3.1 Cơ chế mặc định
Khi không có custom handler nào được đăng ký, Sucker Punch sử dụng hàm xử lý mặc định. Cơ chế này ghi log thông tin lỗi vào console hoặc file log, giúp developer có thể debug trong quá trình development.
3.2 Đăng ký handler tùy chỉnh
Để implement custom exception handler, chúng ta sử dụng assignment SuckerPunch.exception_handler với một block hoặc proc:
# Xử lý ngoại lệ với logging chi tiết
SuckerPunch.exception_handler = lambda do |error, job_class, job_args|
# Ghi log chi tiết
Rails.logger.error "[#{job_class}] Failed with: #{error.message}"
Rails.logger.error "Arguments: #{job_args.inspect}"
Rails.logger.error error.backtrace.join("\n")
# Gửi alert đến monitoring system
ErrorTracker.capture(error, {
job_class: job_class,
args: job_args,
timestamp: Time.current
})
end
Hoặc với class để tái sử dụng:
class SuckerPunchErrorHandler
def self.call(exception, klass, args)
case exception
when TimeoutError
handle_timeout_error(exception, klass, args)
when PaymentError
handle_payment_error(exception, klass, args)
else
handle_generic_error(exception, klass, args)
end
end
private
def self.handle_timeout_error(ex, klass, args)
# Retry với exponential backoff
klass.async_in(60, *args)
end
def self.handle_payment_error(ex, klass, args)
# Alert đội ngũ finance
FinanceAlert.notify(ex, args)
end
def self.handle_generic_error(ex, klass, args)
Bugsnag.notify(ex, meta_data: { job: klass, args: args })
end
end
SuckerPunch.exception_handler = SuckerPunchErrorHandler
Với cách tiếp cận này, hệ thống có thể tự động retry các job thất bại do timeout, alert đội ngũ liên quan khi có lỗi nghiêm trọng, hoặc gửi thông tin lỗi đến các công cụ monitoring như Bugsnag, Sentry.
Best Practices
- Background workers là stateless: Không lưu trạng thái trong instance variable của job vì không đảm bảo persistence.
- Set timeout hợp lý: Tránh các job chạy quá lâu ảnh hưởng đến resource của hệ thống.
- Logging chi tiết: Luôn ghi log đủ thông tin để debug khi có sự cố.
- Monitor performance: Theo dõi số lượng job đang chờ, thời gian xử lý trung bình để phát hiện bottleneck.