Xây dựng hệ thống gửi thông báo đến ngàn người dùng

I. Hê lô my friends

Hế lu mọi người, chào mừng mọi người đến với channel của mình. Channel giúp các bạn đi từ chưa biết gì về lập trình đến việc viết Hê lô World. 😜😜😜

Mình là Nam, thỉnh thoảng có viết bài linh tinh và comment dạo khắp nơi. Hôm nay nhân một ngày Hà Nội đẹp trời, mình xin phép đặt tay vào bàn phím type ra những dòng này, mong muốn có thể gãi đúng chỗ ngứa của một số bạn đang gặp, hoặc để các bạn gãi thêm vào chỗ ngứa của mình vừa qua.

II. Chỗ ngứa của mình

Để trở thành một công dân "toàn cầu", việc lập kế hoạch cho bản thân mình hằng ngày là đặc biệt quan trọng, việc lên plan cho cả 1 tuần sắp tới, 1 tháng sắp tới, hay ngày mai được mình đặt ra sau mỗi ngày, mỗi tuần, mỗi tháng. Hiện nay trên App Store hay CH Play có rất nhiều ứng dụng dạng như vậy, mình xin phép sẽ không kể tên không lại mang tiếng seeding. Tuy nhiên, đối với mình dễ dùng nhất và miễn phí chắc có lẽ là Calendar của chính Google cung cấp. Mình có thể sử dụng Calendar để lập kế hoạch chi tiết vào ngày mai của mình. Việc sử dụng Calendar giúp mình đồng bộ dữ liệu giữa các nền tảng với nhau, ví dụ bản web và bản IOS mà mình cài đặt trên Iphone. Điều đó rất tiện vì mỗi khi đến giờ mình sẽ nhận được lời quan tâm yêu thương từ phía anh Google 😍 .

Calendar của mình

Như các bạn đã thấy, mình đã lên kế hoạch đơn giản cho 14 và ngày 15, nó giống như việc TODO List vậy, ví dụ ngày 15 mình sẽ đi chơi với Gấu của mình. Hí. 😊😊😊. Vào đúng giờ, Calendar sẽ remind cho mình bằng notification qua web hoặc Iphone, hoặc là gửi mail cho mình trước 30 phút. Việc này giúp mình không thể lỡ việc và rất đúng giờ thực hiện.

Bỗng một ngày, công ty mình cũng xây dựng một ứng dụng dạng như calendar này, và mong muốn của leader đó là sau 1 năm số người dùng sẽ lên đến cả trăm nghìn, triệu người. Vậy làm sao để giải quyết được với số người dùng tăng trưởng rất nhanh vậy mà không phải đập đi xây lại sau 1 năm phát triển? Vì vậy mình cần suy nghĩ làm sao xây dựng hệ thống gửi thông báo/email đến người dùng sử dụng nhiều nền tảng khác nhau như Web/IOS/Android?

III. Chuẩn bị

Trước khi bắt tay vào thực hiện, mình và các my friends cần chuẩn bị 1 số kiến thức sau:

  • Firebase Cloud Message - mình dùng thằng này để gửi thông báo đến 3 nền tàng, dễ implement, và miễn phí nữa.
  • Background Job
  • Queue
  • v.v.

Mình prototype database đơn giản vậy cho các bạn dễ hình dung hơn. Lưu ý là phần tokens sẽ là 1 mảng tokens của người dùng trên các thiết bị khác nhau. Mỗi thiết bị có thể có 1 timezone khác nhau.

const tokens = [
    {
        'timezone': 'Asia/Ho_Chi_Minh',
        'token': 'this is my token'
    }
]

Design Database

IV.Thuở sơ khai

4.1. Trầm tư suy nghĩ

Ban đầu thì mình nghĩ bài toán này đơn giản, lưu 1 bảng task(công việc) và 1 bảng user(người dùng). Sau đó mỗi task mình sẽ tạo 1 cron job chạy đúng vào giờ mà người dùng cài đặt.

Ví dụ: Đi phỏng vấn vào 9h30 AM ngày 14.04.2021 => tạo cron job vào 9h30 AM, rồi check điều kiện:

  • Ngày đó trùng với 14.04.2021 thì mình sẽ gửi thông báo, gửi rồi xoá cron đó đi cho đỡ lãng phí.

4.2. Vấn đề

Vậy có tầm 1 triệu công việc thì tạo 1 cron job rồi cái nào chạy rồi thì xoá, cái nào chưa thì để à, thế có ối dồi ôi không? Quá lãng phí tài nguyên vì số công việc sẽ tăng nhanh rất nhiều so với số người dùng. 2 công việc của 2 người khác nhau xảy ra cùng một thời điểm, thì lại tồn tại 2 cron job khác nhau, rất lãng phí phải không nhỉ?

4.3. Lật ngược thế trận sang một góc nhìn khác

Nhận thấy cách trên không ổn, mình đổi hướng suy nghĩ sang 1 cách khác. Đó là mình sẽ tạo 1 cron job chạy theo từng phút, và check thời gian chạy đó với field scheduleTime để kiểm tra việc có gửi thông báo của công việc đó đến người dùng hay không?.

select * from task where scheduleTime = now.

Có thể nhận thấy cách tiếp cận này giúp mình giảm thiểu cả triệu jobs xuống duy nhất 1 job để chạy. Tuy nhiên, vì việc gửi mail/notification là async task, tức là bạn không thể biết bao giờ nó thành công, vì thế vấn đề mới lại xuất hiện:

  • Làm sao để chạy hết các tasks trong vòng 1 phút và lọc ra đúng tasks có scheduleTime trùng với thời gian hiện tại?
  • Nếu cron đó chạy quá 60s thì xử lý timeout như thế nào?
  • Nếu các cron trồng chéo lên nhau thì sẽ thế nào?
  • Nguy cơ nghẽn cổ chai khi gửi thông báo

4.4. Tiếp tục chỉnh sửa

Buồn quá nhớ đến crush tên Quyên ngày xưa học chung môn Cấu trúc Dữ liệu và giải thuật. Đầu óc tỉnh táo lạ thường và mở mắt to ra, ôi còn Queue, tại sao mình không dùng Queue nhể? Giải thích nhắn gọn là Queue là hàng đợi, FIFO(First In First Out). Hiện nay trên thị trường có rất nhiều mặt hàng về Queue như Kafka, Rabbit, Redis, Cloud Task...Bạn có thể chọn 1 trong những thằng đó, chú ý đọc ưu/nhược điểm để phù hơp với dự án của bạn nha. Thêm nữa, để khắp phục vấn đề timeout, mình sẽ đẩy task vào queue luôn và return kết quả cho cron job. Khỏi lo timeout.

Flow.

Vai trò của các bên trung gian:

  • Cron: cron chạy mỗi phút, ném 1 cái task vô thẳng mặt thẳng queue và nói: "Ê tao cần lấy ra thông báo vào ngay lúc này, và gửi nó đến người dùng cho tao!!!"
  • Queue: Chứa các yêu cầu của thằng Cron hống hách, như Queue nó cũng chả biết làm thế nào, đành phải kêu anh Worker xử lý hộ.
  • Workers: Là các tay sai của Queue, sẽ thực hiện lệnh của Queue, ở đây là việc tìm ra các thông báo và gửi nó đến người dùng theo yêu cầu của thằng Cron đến Queue.
  • Database và FCM quá dễ không thèm giải thích! Just kidding 😆😆😆.

Lưu ý: FCM cho gửi có giới hạn và có độ trễ, vì thế bạn nên batch message và gửi trong 1 request. (Tối đa 500 requests). Tham kháo: Send messages to multiple devices

V. Lời kết

Như vậy mình đã nói qua về một trong những hệ thống khá phổ biến hiện nay. Bài viết mang tính khái quát, còn nhiều vấn đề về xử lý error, retry khi lỗi, scale workers theo chiều ngang hoặc partition database khi dữ liệu tasks quá lớn. Vì thế, để triển khai thành 1 hệ thống hoàn chỉnh thì sẽ tốn rất nhiều chi phí phát triển và các vấn đề phát sinh chứ không đơn giản hoá như bài viết của mình.

Tuy nhiên thì nó cũng là 1 cách overview lại kiến trúc cơ bản của hệ thống, để chúng ta có một cách nhìn tổng quá hơn để khi tiếp cận, chúng ta sẽ giải quyết các bài toán nhỏ hơn bên trong đó, đảm bảo hiệu năng và tài nguyên của hệ thống.

Như đầu bài viết, có thể bài viết sẽ gãi ngứa cho bạn, hoặc là hệ thống mình nói đang có chỗ ngứa mà bạn nhận ra và biết cách gãi nó. Đừng ngại ngần comment bên dưới và trao đổi với mình. Hoặc có thể contact qua email của mình để chúng ta cùng tìm ra cách tốt hơn cho bài toán.

Chúc các bạn buổi tối vui vẻ. Happing coding! Nếu các bạn thấy hữu ích cho mình xin 1 upvote và clipped 😃)

Mọi liên hệ có thể gửi qua:

VI. Tham khảo:


All Rights Reserved