Cách sử dụng task queue để nâng cao khả năng back end

Tram Ho

Đặt vấn đề:

Chúng ta sẽ bắt đầu bằng việc xem xét thông qua ví dụ mình sẽ nêu ra sau đây, hãy tưởng tượng bạn là một kỹ sư phần mềm trong công ty, phòng makerting muốn phát triển một công cụ để hỗ trợ các chiến dịch marketing qua email. Bạn được yêu cầu thiết kế một chức năng cho phép lọc những người dùng thoả mãn một số điều kiện nhất định và thực hiện gửi email đến họ với nội dung được cho trước. Giải pháp chúng ta đưa ra ban đầu có thể như sau:

Như vậy, mỗi khi ai đó dùng phần mềm của bạn để gửi email, bạn sẽ xử lý từng email một cho đến hết rồi trả về kết quả. Giả sử nếu việc gửi mỗi email mất 0.2 giây và bạn cần gửi cho 10.000 khách hàng, khi đó sẽ phải mất 2000 giây, tương đương 32 phút để thực thi xong và kết quả mới được trả về. Chưa kể tới 32 phút là một thời gian quá dài, kết nối giữa client và server sẽ bị timeout. Một giải pháp cải tiến ở đây là ta sẽ gửi email theo batch, giả sử mỗi batch có thể gửi 1000 email cùng lúc và cũng vẫn tốn 0.2 giây cho mỗi lần thực thi, như vậy ta cần gửi 10 batch thời gian phản hồi giảm xuống còn 2 giây. Vậy nếu input đầu vào lớn hơn thay vì gửi tới 10.000 khách hàng mà gửi tới 1.000.000 khách hàng thì sao? Dễ dàng tính được thời gian xử lý sẽ lên đến 200 giây. Với thời gian xử lý lâu như vậy cho một request thì UX của ứng dụng sẽ rất tệ. Từ khi “bấm nút” người dùng phải chờ hàng trăm giây đợi trong lúc server xử lý, chẳng may trong lúc đấy người dùng có reload hay tắt đi rồi bật lại thì mọi thứ trở nên thật rắc rối.
Tình hình trở nên khó kiểm soát khi có thể rất nhiều user sử dụng tính năng này cùng lúc, việc server bị quá tải vì phải xử lý đồng thời nhiều tác vụ nặng là điều sẽ xảy ra. Đó chính là vấn đề với hướng xử lý đồng bộ hay còn gọi là synchronous, khi mà phải chờ tác vụ thực thi xong thì response mới được trả về. Nên có một hướng tiếp cận khác để giải quyết vấn đề trên hiệu quả hơn đó chính là xử lý bất đồng bộ — asynchronous.

Giải pháp

Với bài toán gửi mail, sau khi người dùng submit task, hệ thống sẽ không thực hiện việc gửi mail ngay mà trả về thông báo rằng công việc đã được tiếp nhận và sẽ được xử lí sớm trong tương lai, trong thời gian này người dùng có thể tiếp tục làm những công việc khác và khi có thay đổi về trạng thái hoặc công việc được xử lý xong người dùng sẽ nhận được thông báo. Quá trình thực sự gửi mail sẽ được thực hiện phía sau. Task queue chính là một kỹ thuật xử lý bất đồng bộ như thế. Các thành phần, cơ chế hoạt động của một hệ thống task queue như thế nào, chúng ta sẽ cùng tìm hiểu trong bài viết hôm nay nhé.

Định nghĩa

Task queue là một hệ thống giúp cho một ứng dụng có thể thực thi các tác vụ (tasks) một cách bất đồng bộ bên ngoài phạm vi thời gian của một user request thông thường. Đối với các hệ thống lớn, đôi khi việc nhận và xử lý các yêu cầu từ phía người dùng hoặc các tác vụ cần nhiều thời gian, do đó mô hình đồng bộ (synchronous) trong cơ chế request-response không còn phù hợp, thay vào đó là mô hình xử lý bất đồng bộ (asynchronous). Task queue thường được dùng trong các bài toán như: thực thi tác vụ tốn nhiều thời gian (long-running task), các tác vụ cần delay thời gian xử lý (delayed task) hay xử lý tác vụ cần tính toán phức tạp (compute-intensive task). Để có cái nhìn rõ ràng hơn, chúng ta hãy cùng nhau tìm hiểu về kiến trúc và cơ chế hoạt động của hệ thống task queue ngay sau đây.

Kiến trúc và cơ chế hoạt động

Về cơ bản, một hệ thống bất đồng bộ sử dụng task queue sẽ có những thành phần như sau:

  • Producer: Service gửi các tác vụ đến queue.
  • ** Message broker** : Đây chính là nơi chứa queue mà chúng ta nhắc đến xuyên suốt bài viết này. Nhiệm vụ của broker là nhận tác vụ từ producer, lưu chúng vào queue và gửi đến consumer tương ứng, giữ vai trò trung gian quản lý các tác vụ, và làm cầu nối giữa producer-consumer. Một số message broker được sử dụng phổ biến hiện nay có thể kể đến RabbitMQ, Redis hay Kafka.
  • Consumer: Service nhận và xử lý tác vụ từ queue.

Thông thường, producer và consumer là hai thành phần tách biệt như ví dụ sẽ mô tả dưới đây. Tuy nhiên cũng có thể implement để một service vừa là producer, vừa là consumer bằng cách task được gửi từ producer đến broker, rồi broker gửi ngược lại về service đang chạy producer và tác vụ sẽ được xử lý tại đây. Rất linh hoạt đúng không?

Ngoài ra, để producer và consumer giao tiếp được với nhau, thông thường chúng sẽ thống nhất sử dụng chung định dạng message, đó có thể là JSON, pickle hoặc một định dạng khác. Khi đó producer sẽ serialize task dưới định dạng chung và gửi đến broker, broker lưu các task dưới dạng này vào queue, rồi sau đó consumer deserialize task và tiến hành thực thi.

Các trường hợp nên sử dụng task queue và ví dụ thực tế

Qua phần bên trên chắc hẳn các bạn đã phần nào hình dung được cách thức hoạt động của task queue nói chung và cách cài đặt task queue sử dụng Celery với Python nói riêng. Vậy câu hỏi đặt ra là với những trường hợp nào thì chúng ta nên sử dụng task queue. Dưới đây là một vài trường hợp sử dụng phổ biến:

  • Các tác vụ tính toán phức tạp (compute-intensive task). Những tác vụ này khi thực thi có thể chiếm dụng nhiều tài nguyên của hệ thống (CPU, memory…) dễ dàng gây quá tải cho server. Việc sử dụng task queue giúp các tác vụ được phân phối phù hợp với năng lực xử lý của worker. Một ví dụ dễ hình dung cho trường hợp sử dụng này thể hiện ở tính năng upload video lên Youtube hay Facebook. Bạn có để ý rằng sau khi một video được upload thành công thì Facebook hay Youtube sẽ cần mất một vài phút để hậu xử lý video không? Trong khoảng thời gian ấy, video sẽ được kiểm duyệt bởi rất nhiều thuật toán phức tạp khác nhau. Thay vì chạy lần lượt thì các thuật toán sẽ được đưa vào và xử lý bởi task queue. Quá trình kiểm duyệt kết thúc là lúc bạn nhận được thông báo rằng video đã sẵn sàng.
  • Các tác vụ tốn nhiều thời gian xử lý (long-running task). Chính là ví dụ đã đưa ra ở phần đầu, sử dụng task queue giúp rút ngắn thời gian xử lý request, hệ thống phản hồi nhanh hơn, đem lại trải nghiệm người dùng tốt hơn. Một ví dụ khác có thể thấy trong các ứng dụng đặt xe. Rõ ràng tại thời điểm bạn gửi yêu cầu đặt xe, hệ thống cần phải mất một thời gian tìm kiếm tài xế gần đó, gửi yêu cầu đặt xe cho tài xế và chờ tài xế phản hồi. Quy trình này không diễn ra liên tục và mất nhiều thời gian vì còn phụ thuộc vào phản hồi của tài xế, task queue có thể được sử dụng ở đây để quá trình này được xử lý hiệu quả.
  • Các tác vụ cần delay thời gian xử lý (delayed task). Đôi khi có những tác vụ ta muốn delay tới một khoảng thời gian nhất định trong tương lai. Ta có thể đưa chúng vào task queue, đến thời điểm đã hẹn tác vụ sẽ được lấy ra xử lý. Một ví dụ cho trường hợp này là xử lý timeout của các bài thi, tại thời điểm bắt đầu làm bài ta sẽ đưa vào task queue một task chấm điểm bài làm và delay nó một khoảng thời gian bằng thời gian tối đa người dùng có thể dùng để làm bài. Tại thời điểm timeout, hệ thống sẽ tự động chấm bài (nếu chưa submit trước đó) mà không cần phải chờ hành động phát sinh từ phía người dùng.
  • Các tác vụ có độ ưu tiên thấp (low priority task). Với những tác vụ bên lề tác vụ chính, không yêu cầu phải thực thi ngay ta cũng có thể đưa chúng vào queue qua đó giảm thời gian xử lý, rút ngắn thời gian chờ của người dùng. Chẳng hạn như tác vụ gửi email xác nhận sau khi đăng ký tài khoản thành công. Rõ ràng tác vụ này có độ ưu tiên thấp hơn so với việc tạo tài khoản trong hệ thống nên có thể được đưa vào task queue. Đó là lý do vì sao tại với nhiều hệ thống, bạn sẽ chỉ nhận được email thông báo xác nhận khoảng một vài giây sau khi tài khoản đăng ký thành công.

Đánh giá và kết luận

Như bạn có thể thấy, task queue sẽ đem đến nhiều lợi ích nếu được sử dụng đúng cách. Bằng việc xử lý các tác vụ bất đồng bộ, task queue giúp chúng ta được unblock, cải thiện response time qua đó đem lại trải nghiệm người dùng tốt hơn.

Cũng bằng việc đưa các tác vụ vào queue và xử lý sau, task queue giúp ta chủ động hơn trong việc xử lý chúng. Các tác vụ này sẽ được lấy ra từ queue tùy theo khả năng của các worker. Trong trường hợp quá nhiều tác vụ được yêu cầu thực thi cùng lúc, task queue sẽ giúp tránh quá tải hệ thống. Vì worker xử lý tác vụ có thể tách ra thành một service riêng, ta cũng dễ dàng scale lên khi cần thiết mà không cần phải scale toàn bộ hệ thống.

Tuy nhiên việc lạm dụng task queue một cách không cần thiết sẽ làm tăng độ phức tạp của hệ thống, qua đó làm tăng chi phí vận hành. Bản thân ứng dụng cũng phải xử lý những trường hợp phức tạp hơn, chẳng hạn việc phải quản lý trạng thái của task và thông báo cho người dùng khi cần thiết, xử lý task thực thi bị lỗi, cơ chế retry…

Qua bài viết này hi vọng các bạn có thể hiểu thêm task queue, những ứng dụng của nó và áp dụng no thật tốt. Happy coding !!

Link tham khảo

https://medium.com/@thao_gotit

https://www.quora.com/What-is-task-queue

https://www.fullstackpython.com/task-queues.html

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo