Mình chưa được làm hệ thống to lắm, mà đọc cũng nhiều với thỉnh thoảng phỏng vấn/tự làm mấy cái system design vớ vẩn nên nghĩ trả lời như này:
1. Xử lý hàng triệu giao dịch
Bạn hình dung hệ thống phần mềm nhìn chung có 2 phần: stateful với stateless.
- Stateful tức là dữ liệu phải được lưu lại (database)
- Stateless tức là đơn giản nhận lệnh xong xử lý trả về (API server), không cần lưu đi đâu
Việc tăng độ chịu tải với các thành phần stateless khá... đơn giản: cứ việc làm horizontal scale/duplicate các phần stateless lên. Dễ thấy nhất là nếu API server bị load cao thì duplicate ra nhiều API server, xong đặt 1 cái load balancer đằng trước.
Tuy nhiên, vấn đề chủ yếu ở các phần stateful: do phải đảm bảo dữ liệu sẽ được lưu xuống, hoặc dữ liệu sau phụ thuộc trạng thái hiện tại (ví dụ như chỉ cho phép chuyển tiền nếu như hiện tại có đủ tiền), nên việc scale sẽ khó hơn. Lúc này tạm thời xét trong hệ thống phần mềm có 2 loại "lệnh": read và write.
- Read là nhấc dữ liệu ra
- Write là ghi dữ liệu vào
Muốn scale được việc read thì dùng cache. Ví dụ đơn giản thì mình có thể nói đến việc nấu bếp: gọi món là việc read, ông đầu bếp trả món là phần stateful. Nếu như mỗi lần gọi món đều bắt ông đầu bếp nấu thức ăn thì nhọc cho ổng nếu có nhiều món => có thể bảo một tay phụ bếp "bắt chước" nấu sẵn, và phục vụ ra. Tay phụ bếp là cache. Cốt lõi sẽ nằm ở việc có một người "phụ" giúp ông đầu bếp/thành phần stateful tiết kiệm thời gian nấu/tính toán.
Muốn scale được việc write thì có 2 cách:
- Buffer: để cho việc write diễn ra từ từ, tránh dẫn đến quá tải. Cái này mình nghĩ không cần giải thích thêm
- Partition/Sharding: bản chất là tách dữ liệu và lệnh trong phần stateful để đảm bảo 2 lệnh write có thể diễn ra cùng lúc. Ví dụ có 2 lệnh: tài khoản A +100, tài khoản B +200. Do 2 lệnh của 2 tài khoản không liên quan nên có thể diễn ra cùng lúc, nên hoàn toàn có thể tách ra 2 "cục" dữ liệu, 1 cục giữ tài khoản A và cục còn lại giữ tài khoản B. Lấy một ví dụ khác là 2 lệnh: tài khoản A +100, tài khoản A -200. Do lệnh sau phụ thuộc lệnh trước, nên bắt buộc phải cho cả hai chạy tuần tự
Một cái nữa mình thấy có thể giúp write, cũng là good practice trong thiết kế dữ liệu: bạn nên xác định xem có dữ liệu nào là immutable/append-only, sau đó đảm bảo cho các thành phần/dữ liệu khác chỉ việc dựa vào dữ liệu immutable/append-only để tính toán ra. Ví dụ như ngân hàng sẽ lưu các giao dịch tiền vào/ra thì là immutable/append-only, sau đó tính số dư thì chỉ việc dùng các giao dịch đó. Cái này giúp cho scalability ở việc: write vào append-only thì... nhanh, vì bản chất chỉ là thêm một dòng ở cuối. Đoạn đảm bảo cho dữ liệu append-only và các dữ liệu khác được đồng bộ có lúc sẽ phức tạp (ví dụ như giao dịch +100 -90 xong phải đảm bảo số dư hiện tại là 10), nhưng mình nghĩ nên cẩn thận tí là được
---
Mấy cái sau mình rảnh sẽ thử biên tiếp nhé. Giờ đang thấy mình viết dài quá rồi