- vừa được xem lúc

Thiết Kế Hệ Thống Airbnb

0 0 49

Người đăng: System Design VN

Theo Viblo Asia

Đặt Vấn Đề

Hè đến, nhu cầu du lịch tăng cao, kéo theo nhu cầu về đặt nhà nghỉ, khách sạn cũng tăng cao. Đặt phòng theo cách truyền thống sẽ có quy trình như sau: chúng ta tìm kiếm khách sạn, nhà nghỉ trên báo chí, google, sau đó gọi điện để check và đặt phòng. Khi thực hiện theo cách truyền thống, du khách sẽ gặp một số vấn đề như sau:

  • Khó tìm được phòng đúng với yêu cầu (giá, vị trí, cơ sở vật chất, …). Lý do thứ nhất là chưa có những bộ lọc (filter) chuyên dụng. Thứ hai là nhiều nhà nghỉ, khách sạn chưa marketing tốt, chưa tiếp cận được nhiều khách.
  • Quy trình rườm rà, mất nhiều thời gian là một cản trở. Bên cạnh đó, vấn đề đối với phía nhà nghỉ, khách sạn là khi không trong mùa du lịch, họ cần quảng cáo những chiến dịch giảm giá để kích cầu du lịch.

Những nền tảng đặt phòng như Airbnb, Agoda, Booking.com, … sinh ra nhằm giải quyết những vấn đề trên. Bài này mình và các bạn sẽ cùng nhau thiết kế hệ thống Airbnb.

pexels-cheryl-prince-7824827.jpeg Đại bàng đầu trắng (Bald Eagle) là biểu tượng và linh vật quốc gia của Hoa Kỳ. Chúng là những tay thợ săn có kỹ năng cao. Ngoài ra, chúng còn nổi tiếng về hành vi khám phá như bao quát được vùng lãnh thổ rộng để tìm thức ăn hoặc chỗ làm tổ. Khi mùa đông tới, chúng di cư từng Mỹ Bắc xuống phía nam để trú đông.

Phân Tích Yêu Cầu & Phạm Vi

Airbnb là một hệ thống lớn, chứa rất nhiều logic nghiệp vụ phức tạp như cách tính giá phòng theo mùa, hoàn trả (refund), giảm giá (coupon), portal quản lý cho chủ/nhân viên khách sạn, … Bài viết này mình chỉ tập trung phân tích một số năng quan trọng như sau:

Yêu Cầu Tính Năng

Đầu đọc:

  • Tìm kiếm và xem thông tin khách sạn
  • Kiểm tra còn phòng trong khoảng thời gian từ ngày X đến ngày Y
  • Tra cứu lịch sử đặt phòng

Đầu ghi:

  • Đặt phòng

Yêu Cầu Phi Tính Năng

Giờ ta làm một số phép tính để nắm được tính chất của hệ thống. Giả sử:

  • Hệ thống của chúng ta phục vụ 10000 khách sạn, trung bình mỗi khách sạn có 100 phòng → có 1 triệu phòng tất cả.
  • Trung bình 70% phòng được đặt và mỗi phòng đặt trong 3 ngày → Số lượng yêu cầu đặt phòng trong 1 ngày = (1 triệu x 0.7) / 3 = 233333 (reservations)
  • Yêu cầu đặt phòng trong 1 giây = 233333 / (24 x 60 x 60) ~ 2.7 làm tròn lên ~ 3 yêu cầu đặt phòng trong 1 giây
  • Để đặt phòng gồm 3 bước chính:
    • Tìm kiếm và xem thông tin khách sạn
    • Xem thông tin thanh toán
    • Đặt phòng
  • Tại mỗi bước có 10% khách sẽ thực hiện bước tiếp theo. Do đó, QPS (Query Per Second) tại bước “Xem thông tin thanh toán" = 3 x 10 = 30. QPS tại bước “Tìm kiếm và xem thông tin khách sạn” = 30 x 10 = 300.

Ta có thể thấy, đây là một hệ thống read-heavy, write-less (đọc nhiều hơn ghi). Với giả sử này, quy mô của hệ thống chưa lớn nhưng để thiết kế có tính mở rộng, có thể chịu tải vào mùa du lịch. Cũng như phục vụ mục đích nghiên cứu thì mình đưa ra một thiết như sau:

Thiết Kế Tổng Quát

airbnb-system-design.png

Kiểu kiến trúc microservices được sử dụng cho hệ thống đặt phòng vì nhằm khắc phục những hạn chế của kiểu kiến trúc monolithic. Airbnb đã chuyển (migrate) từ monolithic vào năm 2018. Với lý do là khi hệ thống phình to ra, nó trở nên khó maintain, không ổn định và không tin cậy (a single point of failure).

Thiết kế gồm 4 layer chính:

  1. API Gateway: Kiểm tra quyền và điều hướng request.
  2. Data Access Layer: Do dữ liệu bị phân mảng ở layer dưới, ta cần tổng hợp dữ liệu ở tầng này cho những tính năng, use case cụ thể. Ví dụ: để xem thông tin khách sạn, Listing Presentation Service sẽ lấy thông tin khách sạn từ Hotel Service, lấy thông tin đánh giá ở Review Service, … rồi tổng hợp lại trả về Front End hiển thị.
  3. Platform Layer: chứa các statefule service tương tác với các vùng dữ liệu tương ứng. Trên thực tế, sẽ có rất nhiều service khác nữa. Chúng ta cần dành thời gian để review, quy hoạch, tránh đẻ ra nhiều service, phân mảnh về mặt logic và dữ liệu khiến cho hệ thống phức tạp và dữ liệu không thống nhất (inconsistency).
  4. Database: dữ liệu được quản lý bởi các team và stateful service tương ứng.
    • Đối với Reservation Service, Pricing Service, ta nên sử dụng cơ sở dữ liệu có quan hệ (RDBMS) như MySQL. Vì 3 lý do sau:
      • RDBMS hoạt động tốt với read-heavy và write less.
      • RDBMS đảm bảo tính ACID [4] cần thiết cho chức năng đặt phòng.
      • Do data model đều rất rõ ràng nên dễ dàng sử dụng RDBMS.
    • Đối với các service khác như Hotel Service, Review Service có thể sử dụng database khác phù hợp với use case như search, filter, …

Thiết Kế Chi Tiết

Giờ chúng ta đi sâu hơn, tìm hiểu một số vấn đề về mặt kỹ thuật.

Nhà Đông Con

Vấn đề dễ thấy nhất đầu tiên, service có thể được “đẻ" ra nhiều ở cả Platform Layer và Data Access Layer nhưng việc phát triển và triển khai 1 service mới sẽ tối nhiều effort (công sức). Để giảm tối thiểu effort cho quá trình này, Airbnb đã tìm ra những phần công việc chung (common workload) để dựng một số framework in-house nhằm tự động hoá quá trình phát triển và triển khai.

  • Đối với các service ở tầng Data Access, có nhiều đoạn code chung như lấy dự liệu từ tầng Platform (do API ở tầng Platform đều được thiết kế theo 1 chuẩn), data transformation, kiểm tra quyền, … từ đó tạo ra 1 framework dùng để generate code từ các annotation.
  • Đối với deployment, 1 framework được tạo ra dựa trên tư tưởng của GitOps [9]. Quản lý cấu hình resource, môi trường, alerts và service phụ thuộc của service cần triển khai ở trong 1 thư mục Git. Ngoài ra, framework cung cấp tính năng command line dùng để tự động sinh cấu hình và deploy service ở các môi trường khác nhau.

Để tối ưu việc đọc dữ liệu, ở tầng Data Access, thứ nhất ta có thể cache lại kết quả của những request về listing khách sạn, … Ngoài ra, việc gọm request (batching) từ tầng Data Access sẽ hạn chế được số lượng request được đẩy xuống tầng Platform. Ví dụ, ta có request đầu tiên R1 tìm khách sạn có id là H1 findHotelById(H1). Request R1 sẽ chưa được đẩy xuống tầng Platform ngay. Sau đó request R2 đến, tìm khách sạn H2. Trước đó, ta cần xác định timeout của mỗi batch. Đến thời điểm timeout, 2 request được gọm lại thành 1 request findHotelsByIds(H1, H2) và đẩy xuống tầng Platform.

Bạn có thể xem chi tiết giải pháp ở reference [1], [2].

Làm Thừa Còn Hơn Thiếu Ăn

Trên thực tế, khách sẽ đặt phòng theo loại phòng, chứ không đặt theo tên phòng hay ID của phòng. Do đó ta sẽ 2 bảng về số lượng phòng trống (room_inventory) và yêu cầu đặt trước (reservation) như sau:

Bảng room_inventory

Column Key
hotel_id PK
room_type_id PK
date PK
total_inventory
total_reserved

Bảng reservation

Column Key
reservation_id PK
hotel_id PK
room_type_id PK
start_date
end_date
user_id
status

Khi 1 yêu cầu đặt phòng được thực hiện thành công, hệ thống cần tạo ra 1 bảng ghi ở bảng reservation, đồng thời cập nhật số phòng đã đặt ở bảng room_inventory. 2 bảng này sẽ được đặt cùng 1 database schema để chúng tận dụng được transaction của RDBMS và tránh không xử lý distributed transaction giữa các service.

Để thuận tiện cho việc kiểm tra còn phòng trong khoảng thời gian từ ngày X đến ngày Y trong tương lai. Sau khi 1 khách sạn được khởi tạo, hệ thống tạo ra số bảng ghi trong 2 năm cho tất cả loại phòng của khách sạn đó trong bảng room_inventory. Giả sử, trung bình mỗi khách sạn có 15 loại phòng. Suy ra, số bảng ghi cho 2 năm trong bảng room_inventory = 10000 x 15 x 2 x 365 = 109,500,000. Con số này có thể tăng lên theo mỗi năm thế nên ta có thể đánh partition theo hotel_id cho bảng room_inventory để tối ưu performance.

Việc tạo trước các bản ghi sẽ giúp cơ chế xử lý concurrency (sẽ được đề cập ở phần tới) đơn giản hơn. Lưu ý, khi khách sạc cập nhật loại phòng hoặc hết kỳ 2 năm, ta sẽ sinh job tạo hoặc sửa các bản ghi tương ứng ở bảng room_inventory.

Có một fact trong các ngành như khách sạn hay hàng không, đó là người ta hay áp dụng chiến lược cho phép đặt quá (overbooking) số lượng sẵn có. Mục đích để tối ưu lợi nhuận từ những trường hợp huỷ phòng, huỷ vé. Nghĩa là khách sạn X có 100 phòng, nhưng hệ thống lại cho phép đặt quá 10%, nói cách khác là có thể đặt tối đa 110 phòng của khách sạn X. Trong trường hợp cũng có 10% trường hợp huỷ phòng thì khách sạn X vẫn sẽ phục vụ hết công suất 100%. Chiến lược này có ưu nhược điểm, phía khách sạn phải cân nhắc, dựa vào dữ liệu thông kế để đưa ra phần trăm overbooking hợp lý (các bạn có thể xem thêm ở reference [5],[6]). Do đó, hệ thống chúng ta cần hỗ trợ cấu hình phần trăm overbooking này.

Như vậy, ta có logic kiểm tra còn phòng trong code như sau:

if (total_reversed + num_of_rooms_to_reverse <= total_inventory * (1 + overbooking))

Ăn Tranh

Ta có 2 vấn đề về concurrency cần được giải quyết:

  1. Một user clicks nút đặt phòng nhiều lần cùng thời điểm
  2. Nhiều user đặt cùng 1 loại phòng tại cùng thời điểm

Để giải quyết vấn đề 1, ta có thể thêm Idempotency Key. Idempotency Key có thể là request_id được sinh ở Frontend, hoặc là reversation_id được sinh từ Backend. Trước đó, cột Idempotency key đã được khai báo ràng buộc unique trong bảng reservation. Như vậy, khi có 2 request có cùng request_id đến tầng Database thì 1 trong 2 request (request đến sau) sẽ bị reject do đã tồn tại bản ghi unique trước đó.

Tư tưởng để giải quyết được vấn đề 2 là cơ chế locking. Ở tầng Database, ta có 3 kỹ thuật ở thực hiện cơ chế này: Pessimistic Locking, Optimistic Locking và CHECK Constraint.

Mình xin phép dẫn reference về 3 kỹ thuật vì bài viết đã khá dài rồi. Các bạn xem thêm về Pessimistic Locking và Optimistic Locking tại reference [7], CHECK Constraint tại reference [8] đã trình bày đúng và chi tiết.

Mình tổng hợp lại ưu nhược điểm của 3 kỹ thuật trên như sau:

Ưu Điểm Nhược Điểm
Pessimistic Locking + Ngăn được việc update vùng dữ liệu đang được update hoặc đã được update
+ Dễ implement
+ Phù hợp với yêu cầu về tính consistency cao
- Có thể sinh ra deadlock nếu tầng app viết không đúng logic
- Ảnh hưởng tới hiệu năng của DB nếu nhiều transaction bị lock quá lâu → khó scale
Optimistic Locking + Ngăn được việc update với dữ liệu không cũ, không phù hợp
+ Dễ implement
+ Không cần lock bản ghi. Khi tần suất conflicts thấp thì Optimistic Locking ít ảnh hưởng hơn tới performance của DB hơn so với Pessimistic Locking
- Khi có nhiều request cùng lúc, chỉ có 1 request thành công, còn lại thất bại → trải nghiệm người dùng không tốt
- Khi có số lượng concurrency lớn, performance của DB vẫn bị ảnh hưởng.
CHECK Constraint + Dễ implement
+ Tương tự với Optimistic Locking, phù hợp với tần suất conflicts thấp
- Tương tự với Optimistic Locking, khi tần suất conflicts cao thì performance giảm đáng kể
- Không thể version-control như code
- Không phải DB nào cũng support tính năng này

Tuỳ thuộc vào scale và yêu cầu của hệ thống để bạn chọn kỹ thuật locking cho phù hợp. Với trường hợp này, sần suất conflict của hệ thống thấp nên Optimistic Locking là 1 sự lựa chọn hợp lý.

Ngoài ra, nhóm mình có 1 bài viết khác đề cập tới vấn đề concurrency: Thiết Kế Hệ Thống Bán Vé - https://viblo.asia/p/thiet-ke-he-thong-ban-ve-ticketing-system-design-GyZJZnjZJjm

Các bạn tham khảo nhé.

Tổng kết

Chúng ta đã cùng nhau thiết kế được 1 hệ thống Airbnb ở mức đơn giản, nắm được một số nghiệp vụ và kỹ thuật về:

  • Phát triển và triển khai nhiều service
  • Nghiệp vụ về overbooking
  • Một số kỹ thuật cơ bản giải quyết 2 vấn đề concurrency

Cám ơn anh em đã đọc bài viết 🙏

Nếu anh em thấy hay thì cho mình xin 1 upvote và 1 share nhé.

Cám ơn anh em rất nhiều 🙏

Tham Khảo

[1] Airbnb at Scale: From Monolith to Microservices - https://www.infoq.com/presentations/airbnb-scalability-transition/?topicPageSponsorship=25253fe5-e346-4907-afa1-87e0c77904ae

[2] The Human Side of Airbnb’s Microservice Architecture - https://www.youtube.com/watch?v=yGOtTd-l_3E

[3] Book: System Design Interview - Alex Xu & Sahn Lam

[4] ACID - https://www.ibm.com/docs/en/cics-ts/5.4?topic=processing-acid-properties-transactions

[5] What is an overbooking strategy in hotels and what are its advantages? - https://www.mews.com/en/blog/hotel-overbooking-strategy

[6] Why do airlines sell too many tickets? - https://www.youtube.com/watch?v=ZFNstNKgEDI

[7] Optimistic vs. Pessimistic Locking - https://vladmihalcea.com/optimistic-vs-pessimistic-locking/

[8] CHECK Constraint - https://vladmihalcea.com/mysql-custom-sql-check-constraints/

[9] What is GitOps? - https://www.redhat.com/en/topics/devops/what-is-gitops


✅ Facebook: https://fb.com/groups/systemdesign.vn

✅ Discord: https://discord.gg/SyekpYxdzz

⚠️ Nhóm đang tuyển writer và graphic designer nếu anh em có hứng thú thì comment ở dưới giúp mình nhé. Khi tham gia anh em có quyền lợi sau:

  • Trau dồi kỹ năng viết, kỹ năng chuyên môn
  • Được chia sẻ tài liệu nghiên cứu
  • Kết nối, học hỏi từ anh em trong nhóm

Bình luận

Bài viết tương tự

- vừa được xem lúc

Các bước xây dựng chương trình / hệ thống thành công

Làm sao để xây dựng chương trình/hệ thống hoàn thiện và đúng đắn? Câu hỏi này nhiều bạn sẽ trả lời quá dễ, chương trình đúng yêu cầu là đúng, khách hàng hài lòng là được. Đôi khi bạn cũng không biết trong một đống thứ, tính năng, tốc độ, độ chính xác, giao diện mình nên ưu tiên hoàn thiện cái nào.

0 0 45

- vừa được xem lúc

5 định luật mọi lập trình viên nên biết

Đây là bài dịch từ trang medium.com. Mời các bạn xem bài gốc tại đây: https://medium.com/swlh/5-laws-every-software-developer-should-know-d28c197cce4f.

0 0 92

- vừa được xem lúc

Usability là gì? Những lưu ý khi thiết kế Usability

Usability là một yếu tố quan trọng trong sự thành bại của sản phẩm. Thật đáng tiếc khi sản phẩm làm ra ưu việt về tính năng, nhưng lại không được người dùng tiếp nhận, đơn giản chỉ vì khó sử dụng.

0 0 35

- vừa được xem lúc

What is a Software Development Lifecycle?

Software development is designing, documenting, programming, testing, debugging, and continually maintaining for creating frameworks, applications, and software components. These steps are collectivel

0 0 32

- vừa được xem lúc

System Design (Thiết Kế Hệ Thống) là gì? Cần thu thập những thông tin gì để có thể bắt tay vào thiết kế?

Bài viết này sẽ giúp anh em trả lời một số câu hỏi:. . System Design là gì. Một thiết kế như nào được cho là hiệu quả.

0 0 28

- vừa được xem lúc

Chia sẻ kiến thức về lập trình: Công cụ hỗ trợ, phương pháp phát triển phần mềm và kỹ năng lập trình (Phần 2)

Sau bài viết trước về các ngôn ngữ lập trình chính như Python, JavaScript, Java, C++, Ruby và Swift, hôm nay chúng ta sẽ đào sâu vào một chủ đề cũng không kém phần quan trọng: các công cụ hỗ trợ lập t

0 0 32