Ứng dụng gọi xe là một trong những bài toán hệ thống hóc búa nhất vì nó đòi hỏi tính thời gian thực (real-time) liên tục kết hợp với môi trường mạng di động rất dễ biến động.
Để hệ thống này đứng vững, chúng ta phải kết hợp 4 yếu tố "sống còn" sau đây từ bộ công cụ Load Balancer:
Layer 4 Load Balancer (Tầng 4): Để truyền tọa độ GPS liên tục từng giây, ứng dụng thường sử dụng các giao thức duy trì kết nối liên tục (như WebSockets) hoặc truyền tải nhanh (như UDP) thay vì HTTP tiêu chuẩn. Layer 4 phân luồng ở cấp độ cổng mạng (IP/Port) cực kỳ nhanh, độ trễ thấp và ít tốn tài nguyên hơn so với việc phải bóc tách gói tin ở Layer 7.
Thuật toán Least Connections (Ít kết nối nhất): Mỗi cuốc xe kéo dài 15-30 phút, tạo ra một kết nối dài hạn (long-lived connection). Nếu dùng Round Robin, một máy chủ có thể xui xẻo phải gánh toàn bộ các cuốc xe dài, dẫn đến cạn kiệt tài nguyên. Least Connections đảm bảo máy chủ nào đang giữ ít cuốc xe nhất sẽ được giao nhiệm vụ tiếp theo.
Lưu trữ trạng thái tập trung (Redis) thay vì Sticky Session: Khi khách hàng đi qua hầm hoặc rớt mạng 4G, địa chỉ IP của họ thay đổi và Cookie có thể không còn tác dụng. Việc lưu toàn bộ trạng thái cuốc xe ở một cụm bộ nhớ đệm tốc độ cao (như Redis) là bắt buộc. Khi khách hàng có sóng trở lại, dù Load Balancer đẩy họ vào một máy chủ hoàn toàn mới, máy chủ này chỉ cần chọc vào Redis là lấy lại được ngay vị trí hiện tại của tài xế mà không làm gián đoạn chuyến đi.
Auto-scaling (Tự động mở rộng) cực nhạy: Lưu lượng gọi xe có thể tăng vọt gấp nhiều lần chỉ trong vài phút khi trời đổ mưa lớn hoặc tan tầm. Sự phối hợp giữa Load Balancer và Auto-scaling quyết định việc hệ thống có bị sập diện rộng hay không.
🚀 Thử thách thực chiến (Level 2): Hệ thống App Giao Đồ Ăn & Đặt Trà Sữa
Quên các hệ thống kỹ thuật khô khan đi, chúng ta chuyển sang một bài toán gần gũi hơn nhưng độ phức tạp kiến trúc lại không hề nhỏ. Dưới đây là 2 cửa ải mới dành cho bạn.
⚔️ Bài toán số 4: App Giao Đồ Ăn
App giao đồ ăn của bạn cần tính phí ship. Rắc rối ở chỗ, công thức tính tiền thay đổi liên tục:
Thuật toán linh hoạt: Ngày nắng tính giá A, trời mưa tính giá B, giờ cao điểm tính giá C. Bạn cần đóng gói các công thức này thành những class độc lập để app có thể tráo đổi thuật toán tính tiền ngay lập tức (lúc runtime) tùy vào thời tiết.
Khởi tạo giấu kín: Bạn cần một "nhà máy" tự động đọc tình hình thời tiết và đẻ ra đúng cái đối tượng thuật toán tính tiền đó, tuyệt đối không rải rác từ khóa new hay switch-case lộn xộn trong hàm thanh toán chính.
Câu hỏi: Để thỏa mãn, bạn sẽ kết hợp 2 Design Pattern nào (1 Hành vi để tráo đổi thuật toán + 1 Khởi tạo để đẻ ra object)?
☠️ Bài toán số 5: App Đặt Trà Sữa
Hệ thống xử lý đơn hàng trà sữa có luồng chạy vô cùng phức tạp với 3 bước:
Khởi tạo siêu nhiều tham số: Một ly trà sữa có quá nhiều tùy chọn: Kích cỡ (S, M, L), % đá, % đường, loại trà, v.v. Việc gọi một hàm new TraSua(size, da, duong, tra,...) dài dằng dặc là thảm họa. Cần một cơ chế để "lắp ráp" ly trà sữa này theo từng bước (step-by-step) một cách sạch sẽ.
Cộng dồn tính năng (Mặc thêm áo): Khách hàng cực thích thêm topping. Họ có thể gọi: Thêm Trân châu + Thêm Kem Cheese + X2 Phô mai. Mỗi lần thêm là giá tiền và tên món phải thay đổi. Bạn cần một cơ chế linh hoạt để "bọc" thêm các lớp topping này vào ly trà sữa gốc lúc runtime, thay vì phải tạo ra hàng tá class con kiểu TraSuaTranChauKemCheese.
Chuỗi kiểm duyệt đơn hàng: Trà sữa đã pha xong trên app, nhưng trước khi trừ tiền, đơn phải đi qua một "đường ống" kiểm tra: Trạm 1 (Kiểm tra quán còn mở không) ➡️ Trạm 2 (Kiểm tra kho còn nguyên liệu không) ➡️ Trạm 3 (Kiểm tra số dư ví khách). Nếu trạm nào thấy lỗi (VD: hết tiền), nó chặn và báo lỗi ngay. Nếu ổn, nó tự đẩy thẻ qua trạm tiếp theo.
Câu hỏi: Hãy gọi tên 3 Design Pattern (thuộc 3 nhóm: Khởi tạo, Cấu trúc, Hành vi) tương ứng hoàn hảo cho 3 bước trên!
Giả sử bạn vừa được nhận vào vị trí Kỹ sư Kiến trúc Phần mềm cho một công ty khởi nghiệp về Nhà thông minh (Smart Home) 🏠. Hệ thống của công ty đang rất lộn xộn và Giám đốc kỹ thuật nhờ bạn cấu trúc lại nó.
Bài toán số 1
Hệ thống mạng của ngôi nhà cần một đối tượng BoDieuKhienTrungTam (Central Hub) để quản lý mọi thứ. Để tránh việc các lệnh điều khiển bị xung đột, Giám đốc yêu cầu: Trong toàn bộ vòng đời của ứng dụng, chỉ được phép có duy nhất một và chỉ một đối tượng BoDieuKhienTrungTam tồn tại. Bất kỳ đoạn code nào cố tình gọi khởi tạo lần thứ hai đều phải bị ngăn chặn và hệ thống chỉ trả về đối tượng đã tạo từ trước.
Theo bạn, chúng ta cần sử dụng Design Pattern nào (trong nhóm Khởi tạo) để giải quyết yêu cầu khắt khe này?
Bài toán số 2
Ngôi nhà của bạn vừa được lắp một Cảm biến khói 💨. Giám đốc yêu cầu: Khi phát hiện có khói, Cảm biến này phải báo động ngay lập tức cho 3 nơi: Ứng dụng điện thoại của chủ nhà, Chuông báo động, và Hệ thống phun nước.Tuy nhiên, trong tương lai có thể nhà sẽ lắp thêm nhiều thiết bị khác cũng muốn nhận thông báo này. Để code của Cảm biến khói không bị phình to mỗi khi thêm thiết bị mới, Giám đốc muốn các thiết bị phải tự "Đăng ký" (Subscribe) vào Cảm biến. Khi có khói, Cảm biến chỉ việc gửi thông điệp "Thông báo" (Notify) cho danh sách tất cả những ai đã đăng ký.
Theo bạn, Design Pattern nào (thuộc nhóm Hành vi) được sinh ra để giải quyết chính xác cơ chế "Đăng ký - Lắng nghe thông báo" này?
Bài toán số 3
Hệ thống nhà thông minh của bạn được lập trình để điều khiển tất cả các bóng đèn theo một chuẩn chung BongDenThongMinh với hai hàm đơn giản là batDen() và tatDen().
Tuy nhiên, phòng thu mua vừa nhập về một lô bóng đèn thông minh cực xịn của Xiaomi. Khổ nỗi, bộ thư viện mã nguồn đi kèm của Xiaomi lại không dùng hàm giống chúng ta, nó xài một hàm hoàn toàn khác tên là kichHoatNguonSang(int mucDoSang).
Giám đốc yêu cầu: Không được sửa code lõi của hệ thống (vì nó đang chạy ổn định cho hàng nghìn nhà khác rồi), và tất nhiên chúng ta cũng không có quyền sửa mã nguồn thư viện của hãng Xiaomi. Chúng ta cần tạo ra một class trung gian, đứng ở giữa để "dịch" lệnh batDen() của hệ thống thành lệnh kichHoatNguonSang(100) cho cái bóng đèn Xiaomi kia hiểu.
Theo bạn, trong nhóm Cấu trúc (Structural), Pattern nào đóng vai trò như một "phiên dịch viên" hay một "cục chuyển đổi" để hai đầu mối không tương thích có thể cắm vừa vào nhau?
Rất vui vì bài viết hữu ích và được bạn lưu lại để chia sẻ cho team. Chúc anh em trong team code thật mượt và không bị kẹt lại ở những bẫy ngầm như này nhé😁😁
Hahaaa chuẩn quá bạn ơi🤣 Lúc deadline dí rồi thì "code chạy được trước 5h" chính là nguyên tắc tối thượng, SOLID cứ để hôm sau refactor rồi tính tiếp. Cảm ơn bạn đã chia sẻ và ủng hộ bài viết nhé
THẢO LUẬN
NGUYỄN HUY HOÀNG Software Engineer
📞 Phone: 0941 280 073
📧 Email: hhoang02052004@gmail.com
NGUYỄN HUY HOÀNG Software Engineer
📞 Phone: 0941 280 073
📧 Email: hhoang02052004@gmail.com
NGUYỄN HUY HOÀNG Software Engineer
📞 Phone: 0941 280 073
📧 Email: hhoang02052004@gmail.com
Ứng dụng gọi xe là một trong những bài toán hệ thống hóc búa nhất vì nó đòi hỏi tính thời gian thực (real-time) liên tục kết hợp với môi trường mạng di động rất dễ biến động.
Để hệ thống này đứng vững, chúng ta phải kết hợp 4 yếu tố "sống còn" sau đây từ bộ công cụ Load Balancer:
Layer 4 Load Balancer (Tầng 4): Để truyền tọa độ GPS liên tục từng giây, ứng dụng thường sử dụng các giao thức duy trì kết nối liên tục (như WebSockets) hoặc truyền tải nhanh (như UDP) thay vì HTTP tiêu chuẩn. Layer 4 phân luồng ở cấp độ cổng mạng (IP/Port) cực kỳ nhanh, độ trễ thấp và ít tốn tài nguyên hơn so với việc phải bóc tách gói tin ở Layer 7.
Thuật toán Least Connections (Ít kết nối nhất): Mỗi cuốc xe kéo dài 15-30 phút, tạo ra một kết nối dài hạn (long-lived connection). Nếu dùng Round Robin, một máy chủ có thể xui xẻo phải gánh toàn bộ các cuốc xe dài, dẫn đến cạn kiệt tài nguyên. Least Connections đảm bảo máy chủ nào đang giữ ít cuốc xe nhất sẽ được giao nhiệm vụ tiếp theo.
Lưu trữ trạng thái tập trung (Redis) thay vì Sticky Session: Khi khách hàng đi qua hầm hoặc rớt mạng 4G, địa chỉ IP của họ thay đổi và Cookie có thể không còn tác dụng. Việc lưu toàn bộ trạng thái cuốc xe ở một cụm bộ nhớ đệm tốc độ cao (như Redis) là bắt buộc. Khi khách hàng có sóng trở lại, dù Load Balancer đẩy họ vào một máy chủ hoàn toàn mới, máy chủ này chỉ cần chọc vào Redis là lấy lại được ngay vị trí hiện tại của tài xế mà không làm gián đoạn chuyến đi.
Auto-scaling (Tự động mở rộng) cực nhạy: Lưu lượng gọi xe có thể tăng vọt gấp nhiều lần chỉ trong vài phút khi trời đổ mưa lớn hoặc tan tầm. Sự phối hợp giữa Load Balancer và Auto-scaling quyết định việc hệ thống có bị sập diện rộng hay không.
🚀 Thử thách thực chiến (Level 2): Hệ thống App Giao Đồ Ăn & Đặt Trà Sữa
Quên các hệ thống kỹ thuật khô khan đi, chúng ta chuyển sang một bài toán gần gũi hơn nhưng độ phức tạp kiến trúc lại không hề nhỏ. Dưới đây là 2 cửa ải mới dành cho bạn.
⚔️ Bài toán số 4: App Giao Đồ Ăn
App giao đồ ăn của bạn cần tính phí ship. Rắc rối ở chỗ, công thức tính tiền thay đổi liên tục:
Thuật toán linh hoạt: Ngày nắng tính giá A, trời mưa tính giá B, giờ cao điểm tính giá C. Bạn cần đóng gói các công thức này thành những class độc lập để app có thể tráo đổi thuật toán tính tiền ngay lập tức (lúc runtime) tùy vào thời tiết.
Khởi tạo giấu kín: Bạn cần một "nhà máy" tự động đọc tình hình thời tiết và đẻ ra đúng cái đối tượng thuật toán tính tiền đó, tuyệt đối không rải rác từ khóa new hay switch-case lộn xộn trong hàm thanh toán chính.
Câu hỏi: Để thỏa mãn, bạn sẽ kết hợp 2 Design Pattern nào (1 Hành vi để tráo đổi thuật toán + 1 Khởi tạo để đẻ ra object)?
☠️ Bài toán số 5: App Đặt Trà Sữa
Hệ thống xử lý đơn hàng trà sữa có luồng chạy vô cùng phức tạp với 3 bước:
Khởi tạo siêu nhiều tham số: Một ly trà sữa có quá nhiều tùy chọn: Kích cỡ (S, M, L), % đá, % đường, loại trà, v.v. Việc gọi một hàm new TraSua(size, da, duong, tra,...) dài dằng dặc là thảm họa. Cần một cơ chế để "lắp ráp" ly trà sữa này theo từng bước (step-by-step) một cách sạch sẽ.
Cộng dồn tính năng (Mặc thêm áo): Khách hàng cực thích thêm topping. Họ có thể gọi: Thêm Trân châu + Thêm Kem Cheese + X2 Phô mai. Mỗi lần thêm là giá tiền và tên món phải thay đổi. Bạn cần một cơ chế linh hoạt để "bọc" thêm các lớp topping này vào ly trà sữa gốc lúc runtime, thay vì phải tạo ra hàng tá class con kiểu TraSuaTranChauKemCheese.
Chuỗi kiểm duyệt đơn hàng: Trà sữa đã pha xong trên app, nhưng trước khi trừ tiền, đơn phải đi qua một "đường ống" kiểm tra: Trạm 1 (Kiểm tra quán còn mở không) ➡️ Trạm 2 (Kiểm tra kho còn nguyên liệu không) ➡️ Trạm 3 (Kiểm tra số dư ví khách). Nếu trạm nào thấy lỗi (VD: hết tiền), nó chặn và báo lỗi ngay. Nếu ổn, nó tự đẩy thẻ qua trạm tiếp theo.
Câu hỏi: Hãy gọi tên 3 Design Pattern (thuộc 3 nhóm: Khởi tạo, Cấu trúc, Hành vi) tương ứng hoàn hảo cho 3 bước trên!
Thử thách thực chiến (Boss Fight)
Giả sử bạn vừa được nhận vào vị trí Kỹ sư Kiến trúc Phần mềm cho một công ty khởi nghiệp về Nhà thông minh (Smart Home) 🏠. Hệ thống của công ty đang rất lộn xộn và Giám đốc kỹ thuật nhờ bạn cấu trúc lại nó.
Bài toán số 1
Hệ thống mạng của ngôi nhà cần một đối tượng BoDieuKhienTrungTam (Central Hub) để quản lý mọi thứ. Để tránh việc các lệnh điều khiển bị xung đột, Giám đốc yêu cầu: Trong toàn bộ vòng đời của ứng dụng, chỉ được phép có duy nhất một và chỉ một đối tượng BoDieuKhienTrungTam tồn tại. Bất kỳ đoạn code nào cố tình gọi khởi tạo lần thứ hai đều phải bị ngăn chặn và hệ thống chỉ trả về đối tượng đã tạo từ trước.
Theo bạn, chúng ta cần sử dụng Design Pattern nào (trong nhóm Khởi tạo) để giải quyết yêu cầu khắt khe này?
Bài toán số 2
Ngôi nhà của bạn vừa được lắp một Cảm biến khói 💨. Giám đốc yêu cầu: Khi phát hiện có khói, Cảm biến này phải báo động ngay lập tức cho 3 nơi: Ứng dụng điện thoại của chủ nhà, Chuông báo động, và Hệ thống phun nước.Tuy nhiên, trong tương lai có thể nhà sẽ lắp thêm nhiều thiết bị khác cũng muốn nhận thông báo này. Để code của Cảm biến khói không bị phình to mỗi khi thêm thiết bị mới, Giám đốc muốn các thiết bị phải tự "Đăng ký" (Subscribe) vào Cảm biến. Khi có khói, Cảm biến chỉ việc gửi thông điệp "Thông báo" (Notify) cho danh sách tất cả những ai đã đăng ký.
Theo bạn, Design Pattern nào (thuộc nhóm Hành vi) được sinh ra để giải quyết chính xác cơ chế "Đăng ký - Lắng nghe thông báo" này?
Bài toán số 3
Hệ thống nhà thông minh của bạn được lập trình để điều khiển tất cả các bóng đèn theo một chuẩn chung BongDenThongMinh với hai hàm đơn giản là batDen() và tatDen(). Tuy nhiên, phòng thu mua vừa nhập về một lô bóng đèn thông minh cực xịn của Xiaomi. Khổ nỗi, bộ thư viện mã nguồn đi kèm của Xiaomi lại không dùng hàm giống chúng ta, nó xài một hàm hoàn toàn khác tên là kichHoatNguonSang(int mucDoSang). Giám đốc yêu cầu: Không được sửa code lõi của hệ thống (vì nó đang chạy ổn định cho hàng nghìn nhà khác rồi), và tất nhiên chúng ta cũng không có quyền sửa mã nguồn thư viện của hãng Xiaomi. Chúng ta cần tạo ra một class trung gian, đứng ở giữa để "dịch" lệnh batDen() của hệ thống thành lệnh kichHoatNguonSang(100) cho cái bóng đèn Xiaomi kia hiểu.
Theo bạn, trong nhóm Cấu trúc (Structural), Pattern nào đóng vai trò như một "phiên dịch viên" hay một "cục chuyển đổi" để hai đầu mối không tương thích có thể cắm vừa vào nhau?
Trong photoshop mình tưởng có resize hàng loạt và cho logo hàng loạt mà nhỉ
NGUYỄN HUY HOÀNG Software Engineer
📞 Phone: 0941 280 073
📧 Email: hhoang02052004@gmail.com
NGUYỄN HUY HOÀNG Software Engineer
📞 Phone: 0941 280 073
📧 Email: hhoang02052004@gmail.com
NGUYỄN HUY HOÀNG Software Engineer
📞 Phone: 0941 280 073
📧 Email: hhoang02052004@gmail.com
Hello sếp, long time no see 👋
"3. Kỹ sư có bị AI đá ra chuồng gà?" 😂😂
Bài viết hay quá cảm ơn bạn đã chia sẻ !
thanks e đã theo dõi 😍
😁😁 đơn giản nhưng không phải ai cũng hiểu bản chất, rất vui vì nó có ích với b
Rất vui vì bài viết hữu ích và được bạn lưu lại để chia sẻ cho team. Chúc anh em trong team code thật mượt và không bị kẹt lại ở những bẫy ngầm như này nhé😁😁
@bullpig Mỗi người mỗi quan điểm, miễn sao mình tìm được thứ mình cần ở trong đó là được mà đúng không😁😁enjoy thôi
dài cổ chưa, để lên tiếp nè😁😁
Hahaaa chuẩn quá bạn ơi🤣 Lúc deadline dí rồi thì "code chạy được trước 5h" chính là nguyên tắc tối thượng, SOLID cứ để hôm sau refactor rồi tính tiếp. Cảm ơn bạn đã chia sẻ và ủng hộ bài viết nhé
Cảm ơn b nhé @bullpig bài thơ rất hay=)))