Thông báo: không mất, không gửi trùng
1/8
Bài 1 / 8~6 phútKhông mất, không gửi trùngMiễn phí… lượt xem

Thông báo — tổng quan case không mất, không gửi trùng

OTP phải tới trong 15 giây, năm triệu tin khuyến mãi dồn vào 15 phút, nhà cung cấp không chống trùng: từ ràng buộc tới ba quyết định, mỗi cái kèm cái giá.

TL;DR: Case này gửi ba loại tin qua một nhà cung cấp ngoài không chống trùng: OTP phải tới trong 15 giây, tin giao dịch tới trong một phút, và năm triệu tin khuyến mãi dồn vào 15 phút. Timeout 2 giây sinh khoảng 25.000 lượt gửi mập mờ mỗi đợt khuyến mãi, mỗi lượt có thể thành tin trùng hoặc tin mất. Bạn sẽ tự mô phỏng chỗ vỡ đó trên Postgres của mình, rồi tự chọn ba quyết định trên ba trục: mức giao nhận cho từng loại tin, khoá chống trùng, và cách ly luồng gửi. Mỗi lựa chọn đều kèm một cái giá đo được.

Worker gọi API gửi tin, đợi 2 giây không thấy trả lời, và thử lại. Người dùng nhận hai tin "Bạn vừa thanh toán 1.250.000 đ", tưởng bị trừ tiền hai lần, gọi tổng đài. Đổi sang không thử lại thì lần sau có người không nhận được mã OTP nào. Module này đi từ chỗ "thử lại cho chắc" tới chỗ biết chính xác tin trùng và tin mất sinh ra ở đâu, và bạn chịu phía nào cho loại tin nào.

Bài toán và bảng ràng buộc

Hai chữ viết tắt dùng suốt case. OTP (one-time password) là mã xác thực dùng một lần, gửi qua SMS. NCC là nhà cung cấp dịch vụ gửi tin bên ngoài, loại bạn gọi qua API rồi nó mới gửi tới điện thoại người dùng. Push là thông báo đẩy thẳng vào app trên máy, đi qua NCC push thay vì nhà mạng.

Bảng dưới là bản rút gọn của bảng ở bài 01. Các bài sau trích lại đúng bộ số này, không đặt số mới.

Ràng buộcGiá trị
OTP qua SMSĐỉnh 200 lượt mỗi giây, tới trong 15 giây
Tin giao dịch qua pushĐỉnh 1.000 lượt mỗi giây, tới trong 1 phút
Tin khuyến mãi qua push5.000.000 tin trong 15 phút
NCC chống trùngKhông
Độ trễ NCCp50 150 ms, p99,5 2 giây, p99,9 4 giây
Timeout phía bạn2 giây

Percentile đọc thế này: p99,5 bằng 2 giây nghĩa là 99,5% lượt gọi trả lời nhanh hơn 2 giây, còn 0,5% chậm hơn.

Dòng "NCC không chống trùng" và dòng "timeout 2 giây" cộng lại thành gốc của cả case. Timeout cắt đúng 0,5% lượt gọi chậm nhất, và với 0,5% đó bạn chỉ còn hai lựa chọn: gọi lại thì có thể trùng, bỏ qua thì có thể mất. Đường nào cũng tốn.

Sau module này bạn sẽ

  • Ước lượng được số lượt gửi mỗi giây theo loại, số lượt gửi mập mờ do timeout và cái giá của một tin mất so với một tin trùng, từ bảng ràng buộc tự đặt, rồi so tải đỉnh với trần ghi job đo trên máy mình.
  • Chẩn đoán được nguồn sinh tin trùng (timeout rồi retry) và tin mất (kẹt trạng thái đang gửi, hết lượt retry) trong vòng nhận job, gọi nhà cung cấp rồi đánh dấu xong.
  • Chọn được giữa gửi tối đa một lần (at-most-once) và gửi ít nhất một lần (at-least-once) cho từng loại tin, bằng cách so tỉ lệ mất nhân giá mất với tỉ lệ trùng nhân giá trùng.
  • Thiết kế được khoá chống trùng chặn job lặp phía mình, kèm ranh giới của nó: phần trùng do timeout nằm ngoài tầm khoá đó và chỉ thu hẹp được bằng khoá gửi kèm NCC.
  • Thiết kế được cách ly luồng gửi theo loại để OTP vẫn tới trong hạn khi một trong hai NCC (push hoặc SMS) chậm giữa đợt gửi khuyến mãi.
  • Biện luận được vì sao một hệ thống thông báo thật chọn khác thiết kế của bạn dựa trên ràng buộc của họ.

Lộ trình sáu bài, mỗi bài một vai

  1. Bài 01, ràng buộc và ước lượng: chốt bảng số, tính lượt gửi mập mờ và tỉ lệ giá giữa tin mất với tin trùng. Chưa có thiết kế nào, chỉ có số.
  2. Bài 02, gửi rồi đánh dấu: thiết kế đơn giản nhất, bạn tự mô phỏng timeout và worker chết để đếm tin trùng lẫn tin mất.
  3. Bài 03, mất hay trùng: quyết định thứ nhất, trục "loại tin này chịu phía nào".
  4. Bài 04, khoá chống trùng: quyết định thứ hai, trục "chặn trùng ở chặng nào, và chặng nào ngoài tầm tay".
  5. Bài 05, cách ly luồng gửi: quyết định thứ ba, trục "tin nào được đi trước khi NCC chậm".
  6. Bài 06, đối chiếu thực tế: các hệ thống thông báo thật chống trùng ở đâu, và ràng buộc nào của họ khác bảng của bạn.

Bản đồ module: bảng ràng buộc dẫn tới phương án gửi rồi đánh dấu vừa trùng vừa mất, từ đó toả ra ba trục quyết định song song là mất hay trùng, chặn trùng ở đâu, tin nào đi trước, rồi gom về đối chiếu thực tế và ráp thiết kế

Ba quyết định không nối đuôi nhau, chúng cùng mọc từ chỗ vỡ của bài 02. Thứ tự 03 rồi 04 vẫn có lý do: phải biết loại nào được thử lại sau timeout thì mới biết khoá chống trùng còn phải gánh phần trùng nào, và phần nào nằm ngoài tầm tay bạn. Bài 05 đứng riêng một trục, vì một OTP đúng một lần mà tới sau 14 phút thì cũng vô dụng như mất.

Bài 07 ráp ba quyết định thành một thiết kế hoàn chỉnh và gom các cái giá vào một bảng.

Cần chuẩn bị gì

Case này dựa trên ba khoá, và <Recall> lại chúng thay vì dạy lại:

Về công cụ, bạn cần Postgres chạy bằng Docker, psql và pgbench (đi kèm bản cài Postgres, hoặc dùng ngay trong container). Số đo khác máy, nên bài dạy theo tỉ lệ. Máy bạn cho số khác là bình thường.

Thời gian

Cả module mất khoảng 1,5 giờ đọc, cộng chừng 1 giờ chạy script và tự quyết ở các bài 02 đến 05.

BàiPhút đọc
01 · Ràng buộc và ước lượng13
02 · Gửi rồi đánh dấu13
03 · Mất hay trùng theo loại13
04 · Khoá chống trùng13
05 · Cách ly luồng gửi13
06 · Đối chiếu thực tế12
07 · Tổng kết7

Cách học ở tuyến này

Ba thói quen, lặp lại ở mọi bài:

  • Tự quyết trước khi đọc lời giải. Gặp <DesignChoice> thì chọn một phương án rồi mới đọc phần phân tích.
  • Hỏi "cái giá nào ràng buộc của tôi gánh được", đừng hỏi "đâu là đáp án đúng". Case này nói rõ điều đó hơn case trước: cùng một hệ, OTP và tin khuyến mãi chọn hai mức giao nhận ngược nhau.
  • Tự đo. Mỗi bài có một script nhỏ chạy trên Postgres của bạn, và con số của chính bạn thuyết phục hơn con số trong bài.

Bắt đầu với bài 01.

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Ràng buộc và ước lượng: bao nhiêu lượt gửi tin mập mờ?